Thread creation and thread nature

In the last section, I learned the process creation in detail, and learned the difference between fork and vfork through examples. In this section, we will learn how to create threads, which only involve the application layer threads. We will learn how to create kernel threads later.

Creation of application thread

The creation of application thread must be understood by everyone. Use the pthread? Create library function to create an application thread. Take a simple example.

Let's take a look at the parameters of pthread_create, through man pthread_create

NAME
       pthread_create - create a new thread
 
SYNOPSIS
       #include <pthread.h>
 
       int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);
 
       Compile and link with -pthread.

Pthread "create" is to create a new thread. The parameter thread is thread ID, which can be returned through pthread "self". This thread ID follows POSIX standard and is different from the thread ID defined in linux kernel. It will be explained by an example later

Attr is the attribute that creates this thread. You can initialize the attr parameter through the pthread ﹣ attr ﹣ init function.

Start? Routine is the callback of the thread. When the thread is created successfully, the function pointer will be called, and arg is the parameter of the function pointer.

#include <pthread.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/syscall.h>
 
static void* thread_call(void* arg)
{
 printf("create thread success! arg=%d\n",arg);
 return NULL;
}
 
int main()
{
 int ret;
 pthread_t thread;
 
 ret = pthread_create(&thread, NULL, thread_call, 100 );
 if(ret == -1)
     printf("create thread faild!\n");
 
 ret = pthread_join(thread, NULL);
 if(ret == -1)
     printf("pthread join failed!\n");
 
 return ret;
}

Run the result. You need to specify - pthread when compiling

root@ubuntu:zhuxl$ ./a.out
create thread success!, arg=100

Next, let's look at an example to confirm the difference between pid, tid, and pthread_self

#include <pthread.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/syscall.h>
 
static pid_t gettid(void)
{
   return syscall(SYS_gettid);
}
 
static void* thread_call(void* arg)
{
    printf("thread_create success!\n");
    printf("thread_call pid=%d, tid=%d, threadID=%ld\n",getpid(), gettid(), pthread_self());
    while(1);
    return NULL;
}
 
int main()
{
    int ret;
    pthread_t thread;
 
    printf("pid=%d, tid=%d, threadID=%ld\n",getpid(), gettid(), pthread_self());
 
    ret = pthread_create(&thread, NULL, thread_call, NULL );
    if(ret == -1)
        printf("create thread faild!\n");
 
    ret = pthread_join(thread, NULL);
    if(ret == -1)
        printf("pthread join failed!\n");
 
    return ret;
}

Operation result:

root@ubuntu:zhuxl$ ./a.out
pid=101104, tid=101104, threadID=140298706515776
thread_create success!
thread_call pid=101104, tid=101105, threadID=14029869802060

As can be seen from the above results

  • All threads in the process have the same pid
  • To get all the linear IDS in a process, you need to use the tid number. You can use the gett id function
  • The ID returned by pthread [self] is different from that returned by gettid.
  • The ID returned by pthread_self follows POSIX standard, while the ID returned by gettid() is customized by linux kernel

We can see the relationship between process and thread through PS

root@ubuntu:$ ps -eLf
UID         PID   PPID    LWP  C NLWP STIME TTY          TIME CMD
root      101104  90968 101104  0    2 20:27 pts/0    00:00:00 ./a.out
root      101104  90968 101105 99    2 20:27 pts/0    00:03:40 ./a.out

It can be seen that the view of PID through ps is the same, which is obtained through getpid. LWP(light weight process) is a lightweight process, that is, thread. You can see that the ID of the thread is different.

It can be further confirmed by man gettid

NAME
       gettid - get thread identification
 
SYNOPSIS
       #include <sys/types.h>
 
       pid_t gettid(void);
 
       Note: There is no glibc wrapper for this system call; see NOTES.
 
DESCRIPTION
       gettid()  returns  the  caller's  thread  ID  (TID).  In a single-threaded process, the thread ID is equal to the process ID (PID, as
       returned by getpid(2)).  In a multithreaded process, all threads have the same PID, but each one  has  a  unique  TID.   For  further
       details, see the discussion of CLONE_THREAD in clone(2).

In a single thread, the threadID and processID are the same, which are obtained through the getpid function. In a multithreaded process, all threads have the same PID, but each thread page has a unique TID

 

Let's take a look at it through man pthread_self

Thread  IDs  are guaranteed to be unique only within a process.  A thread ID may be reused after a terminated thread has been joined,
or a detached thread has terminated.
 
The thread ID returned by pthread_self() is not the same thing as the kernel thread ID returned by a call to gettid(2).

The ID returned through pthread_self and the kernel thread ID returned from gettid are different things.

 

Pthread "create real call

We use strace to track the system call that pthread_create calls last.

root@ubuntu:zhuxl$ strace ./a.out
execve("./a.out", ["./a.out"], 0x7fffdb498be0 /* 56 vars */) = 0
brk(NULL)                               = 0x56046931d000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=99218, ...}) = 0
mmap(NULL, 99218, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f31ab97f000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0000b\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=144976, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f31ab97d000
mmap(NULL, 2221184, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f31ab552000
mprotect(0x7f31ab56c000, 2093056, PROT_NONE) = 0
mmap(0x7f31ab76b000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19000) = 0x7f31ab76b000
mmap(0x7f31ab76d000, 13440, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f31ab76d000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260\34\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2030544, ...}) = 0
mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f31ab161000
mprotect(0x7f31ab348000, 2097152, PROT_NONE) = 0
mmap(0x7f31ab548000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f31ab548000
mmap(0x7f31ab54e000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f31ab54e000
close(3)                                = 0
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f31ab97a000
arch_prctl(ARCH_SET_FS, 0x7f31ab97a740) = 0
mprotect(0x7f31ab548000, 16384, PROT_READ) = 0
mprotect(0x7f31ab76b000, 4096, PROT_READ) = 0
mprotect(0x5604674c3000, 4096, PROT_READ) = 0
mprotect(0x7f31ab998000, 4096, PROT_READ) = 0
munmap(0x7f31ab97f000, 99218)           = 0
set_tid_address(0x7f31ab97aa10)         = 102191
set_robust_list(0x7f31ab97aa20, 24)     = 0
rt_sigaction(SIGRTMIN, {sa_handler=0x7f31ab557cb0, sa_mask=[], sa_flags=SA_RESTORER|SA_SIGINFO, sa_restorer=0x7f31ab564890}, NULL, 8) = 0
rt_sigaction(SIGRT_1, {sa_handler=0x7f31ab557d50, sa_mask=[], sa_flags=SA_RESTORER|SA_RESTART|SA_SIGINFO, sa_restorer=0x7f31ab564890}, NULL, 8) = 0
rt_sigprocmask(SIG_UNBLOCK, [RTMIN RT_1], NULL, 8) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
gettid()                                = 102191
getpid()                                = 102191
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
brk(NULL)                               = 0x56046931d000
brk(0x56046933e000)                     = 0x56046933e000
write(1, "pid=102191, ttid=102191, threadI"..., 50pid=102191, ttid=102191, threadID=139851308967744
) = 50
mmap(NULL, 8392704, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7f31aa960000
mprotect(0x7f31aa961000, 8388608, PROT_READ|PROT_WRITE) = 0
clone(child_stack=0x7f31ab15ffb0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0x7f31ab1609d0, tls=0x7f31ab160700, child_tidptr=0x7f31ab1609d0) = 102192
thread_create success!
thread_call pid=102191, ttid=102192, threadID=139851300472576

It can be seen that the final call to the clone system call will be made. The fork & VFORK final page we learned before are all called clone system calls.

The difference between the three is that the flag parameter is different.

Name flag parameter
fork CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD
vfork CLONE_VM | CLONE_VFORK | SIGCHLD
pthread_create
CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID

Clone? XX meaning of each field

The meaning of each field of clone? XX is defined in the clone system call, which can be viewed through man clone

field English interpretation of man Chinese explanation
CLONE_VM

If CLONE_VM is set, the calling process and the child process run in the same memory space.

If  CLONE_VM  is  not set, the child process runs in a separate copy of the memory space of the calling process

This means how to set the clone? VM to share mm resources on behalf of child processes and parent processes

If clone? VM is not set, the parent-child process operates in each child's memory space

CLONE_VFORK

If CLONE_VFORK is set, the execution of the calling process is suspended until the child releases its virtual memory resourcesvia a call to execve(2) or _exit(2) (as with vfork(2)).

If clone? VFORK is set, the parent process is suspended until the child finishes running and exits, and the parent process CIA can run
CLONE_FS

If CLONE_FS is set, the caller and the child process share the same filesystem information.

If CLONE_FS is not set, the child process works on a copy of the filesystem information of the calling process

If clone? FS is set, the parent and child processes share the same file system resources

If clone? FS is not set, the child process will make a copy of the file system resources made by the parent

CLONE_FILES

If  CLONE_FILES  is  set, the calling process and the child process share the same file descriptor table

If CLONE_FILES is not set, the child process inherits a copy of all file descriptors opened in the calling process

If clone file is set, the parent and child processes share the same file resources

If clone file is not set, the child process will copy a copy of the file resource information of the parent process

There are only a few simple lists here, and many others are not listed. Interested students can check the meaning of each field through man clone

Here we can draw a conclusion: clone? VM, files, FS, SIGHAND and so on. If these resource settings represent that the parent-child processes share the same resources. If it is not set, the child process will make a copy action for the resource of the parent process.

A deeper understanding of fork, VFORK, pthread "create

As we said before in the basic concept of process, process is the basic unit of resource allocation, and thread is the latest unit of system scheduling. Since a process is the unit of resource allocation, there are many resources in that process.

If you want to create a child process, you need to see how to give the resources of the parent process to the child process.

fork creates a subprocess

If fork is used to create a child process, the resources of the parent process are provided to the child process through copy. It involves the COW technology, which splits when both parents and children write resources.

For example, when the mother leaves her son, the son will live in the same room with his parents when he is young. When the son grows up slowly, he needs a separate room. At this time, he will build a separate room for his son, which is equivalent to division.

 

vfork creates a subprocess

When vfork creates a child process, it will finally call the system call of clone. The parameter passed is clone? VM, clone? vfork. This means that the parent-child process shares memory resources.

The biggest difference between vfork and fork is that they share mm resources. As long as one party modifies mm resources, the other party will see.

Pthread? Create to create a thread

As mentioned above, if there are multiple threads in a process, they will share the resources of the process. Threads are also known as lightweight processes (lwps). All the lightweight is that all resources and parent processes are shared. When scheduling, the context switching time is less.

Since the threads share all the resources of the parent process, when the linux kernel creates a thread through pthread ﹣ create, the parameters that are ultimately passed to the clone are clone ﹣ VM, clone ﹣ files, clone ﹣ FS, etc., which shows that all the resources are shared, so threads can be realized.

This is how linux implements user threads. In this way, the parent-child process shares all the resources and all the resources, which is the thread.

 

However, linux kernel has no concept of thread. Only task struct structure is recognized in the kernel. As long as it is task struct structure, it can participate in scheduling. So there is no distinction between processes and threads in the kernel.

 

160 original articles published, 93 praised, 350000 visitors+
His message board follow

Tags: Linux Ubuntu Attribute glibc

Posted on Fri, 14 Feb 2020 05:28:44 -0500 by Charlie9809