Daemons and Threads

Daemons and Threads

daemon process

Daemon (elf) process is a background service process in Linux , which is usually independent of the control terminal and periodically performs certain tasks or waits to process certain events that occur . Generally use the name ending with d, such as vsftpd

Some system service processes in the Linux background have no control terminal and cannot directly interact with users. Not affected by user login and logout, they are always running, they are all daemon processes. Such as: the realization of pre-reading and buffering output mechanism; ftp server; nfs server, etc.

Process Groups and Sessions

ps ajxView process group ID and session ID

process group

A process group is a collection of one or more processes, and each process belongs to a process group. The process group is introduced to simplify the management of processes. When the parent process creates a child process, the default child process and the parent process belong to the same process group

process group IDThe first process ID (leader process). If the parent process creates multiple child processes, the parent process and multiple child processes belong to the same group, and since the parent process is the first process in the process group, the parent process is the group leader of the group, and the group leader IDparent process id

You can use kill -SIGKILL - process group ID (negative) to kill all processes in the entire process group

As long as there is one process in the process group, the process group exists, regardless of whether the group leader process is terminated

Process group lifetime: from process group creation to last process exit

conversation

  • A session is a collection of one or more process groups
  • The process creating the session cannot be the process group leader
  • The process that creates the session becomes the leader process of a process group and also becomes the leader of the session
  • Need to have root privileges (ubuntu not required)
  • The newly created session discards the original controlling terminal
  • When establishing a new session, call fork first, the parent process terminates, and the child process calls the setsid function

image-20211124222937063

Create a daemon model

  1. fork child process, parent process exits

The child process inherits the process group ID of the parent process, but has a new process ID, which ensures that the child process is not the leader ID of a process group, which is a necessary prerequisite for the call of the setsid function to be done below

  1. The child process calls the setsid function to create a new session

After calling this function

  • This process becomes the first process of the new session and is the president of the session
  • Become the leader process of a new process group, is the leader of the process group
  • Not affected by the controlling terminal
  1. Change the current working directory chdir

For example: a.out is on the U disk, start this program, the current working directory of this program is this U disk, if the U disk is unplugged, the current working directory of the process will disappear, and a.out will not work normally.

  1. Reset file mask mode & ~umask
  • The child process will inherit the mask of the parent process
  • Increase the flexibility of subprocess program operation
  • umask(0000);
  1. close file descriptor
  • The daemon is not affected by the controlling terminal and can be shut down to free resources
  • close(STDIN_FILENO);
  • close(STDOUT_FILENO);
  • close(STDERR_FILENO);
  1. Perform core work: the core code logic of the daemon process

example

Write a daemon process to get the system time every 2S and write this time to the disk file

//创建守护进程
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/time.h>
#include<signal.h>
#include<time.h>
#include<fcntl.h>
#include<sys/stat.h>

void myfunc(int signo)
{
    
    
    //打开文件
    int fd=open("mydemon.log",O_RDWR|O_CREAT,0755);
    if(fd<0)
    {
    
    
        return;
    }
    //获取当前系统时间
    time_t t;
    time(&t);
    char *p=ctime(&t);

    //将时间写入文件
    write(fd,p,strlen(p));
    close(fd);
    return;
}

int main()
{
    
    
    //父进程fork子进程,父进程退出
    pid_t pid=fork();
    if(pid<0||pid>0)
    {
    
    
        exit(1);
    }

    //子进程调用setsid函数创建会话
    setsid();

    //改变当前工作目录
    chdir("/usr/local/src/test01");

    //改变文件掩码
    umask(0000);

    //关闭标准输入、输出、错误输出文件描述符
    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    close(STDERR_FILENO);

    //核心操作
    
    //注册信号处理函数
    struct sigaction act;
    act.sa_handler=myfunc;
    act.sa_flags=0;
    sigemptyset(&act.sa_mask);
    sigaction(SIGALRM,&act,NULL);

    //设置时钟
    struct itimerval tm;
    tm.it_interval.tv_sec=2;
    tm.it_interval.tv_usec=0;
    tm.it_value.tv_sec=3;
    tm.it_value.tv_usec=0;
    setitimer(ITIMER_REAL,&tm,NULL);

    while(1)
    {
    
    
        sleep(1);
    }
}

thread

what is thread

Lightweight process (LWP: light weight process), the essence of a thread in a Linux environment is still a process

Process: has an independent address space, has a PCB, which is equivalent to living alone

Thread: There is a PCB, but no independent address space, and multiple threads share the process space, which is equivalent to shared renting

image-20211125133010494

Under the Linux operating system, a thread is the smallest execution unit, and a process is the smallest resource allocation unit

ps -Lf pidView the LWP number of the specified thread

image-20211125133201619

Whether creating a process fork or creating a thread pthread_create, the underlying implementation calls the same kernel function clone

All thread operation functions pthread_* are library functions, not system calls

The Linux kernel does not distinguish between processes and threads, only at the user level

thread shared resource

  • file descriptor table
  • How each signal is handled
  • current working directory
  • User ID and Group ID
  • Memory address space (.text/.data/.bss/heap/shared library)

Threads do not share resources

  • thread ID
  • Processor threads and stack pointers (kernel stack)
  • Independent stack space (user space stack)
  • errno variable
  • signal mask word
  • scheduling priority

advantage

  • Improve program concurrency
  • low cost
  • Data communication and data sharing are convenient

shortcoming

  • library function, unstable
  • gdb debugging, writing difficulties
  • Fortunately, the support is not good

pthread_create

//创建一个新线程

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *),void *arg);

//成功,返回0 失败,返回错误号

//pthread_t:传出参数,保存系统为我们分配好的线程ID
//attr:通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。
//start_routine:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。
//arg:线程主函数执行期间所使用的参数。

//由于pthread_create的错误码不保存在errno中,因此不能直接用perror()打印错误信息,可以先用strerror()把错误码转换成错误信息再打印

//如果任意一个线程调用了exit或_exit,则整个进程的所有线程都终止,由于从main函数return也相当于调用exit,为了防止新创建的线程还没有得到执行就终止,我们在main函数return之前延时1秒,这只是一种权宜之计,即使主线程等待1秒,内核也不一定会调度新创建的线程执行
//创建子线程
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<pthread.h>

void* mythread(void *arg)
{
    
    
    printf("child thread,pid==[%d],id==[%ld]\n",getpid(),pthread_self());
}

int main()
{
    
    
    //创建子线程
    pthread_t thread;
    int ret=pthread_create(&thread,NULL,mythread,NULL);
    if(ret!=0)
    {
    
    
        printf("pthread_create error,[%s]\n",strerror(ret));
        return -1;
    }
    printf("main thread,pid==[%d],id==[%ld]\n",getpid(),pthread_self());
    sleep(1);
    return 0;
}

image-20211125135610004

Loop to create multiple sub-threads

//循环创建子线程,并且打印是第几个子线程
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<pthread.h>

void* mythread(void *arg)
{
    
    
    int i=*(int*)arg;
    printf("[%d]:child thread,pid==[%d],id==[%ld]\n",i,getpid(),pthread_self());
}

int main()
{
    
    
    int n=5;
    int i=0;
    //创建子线程
    pthread_t thread[5];
    for(i=0;i<5;i++)
    {
    
    
        int ret=pthread_create(&thread[i],NULL,mythread,&i);
        if(ret!=0)
        {
    
    
            printf("pthread_create error,[%s]\n",strerror(ret));
            return -1;
        }
    }
    printf("main thread,pid==[%d],id==[%ld]\n",getpid(),pthread_self());
    sleep(1);
    return 0;
}

image-20211125140218745

i are all 5

When creating a sub-thread, use the loop factor as a parameter to pass to the sub-thread, so that the main thread and multiple sub-threads will share the variable i (the variable i is defined in the main function and is always valid throughout the process), so in the view of the sub-thread The variable i is legal stack memory space

So why is the value printed by each sub-thread at the end 5?

It is because the main thread may continuously create 5 sub-threads within one cpu time slice. At this time, the value of the variable i becomes 5. When the main thread loses the cpu time slice, the sub-thread gets the cpu time slice, and the sub-thread accesses is the value of the memory space of the variable i, so the printed value is 5

image-20211125140408189

Solution

Multiple sub-threads cannot share the same memory space. Each sub-thread should access different memory spaces. You can define an array in the main thread: int arr[5];, and then pass different array elements when creating threads. In this way, each child thread accesses a different memory space, so that the correct value can be printed

image-20211125140505329

If the main thread exits earlier than the child thread, the child thread may not be executed, because the main thread exits, the entire process space will be recycled, and the child thread has no living space, so it will not be executed

Threads (including the main thread and sub-threads) can share the same variable, including global variables or non-global variables (but non-global variables must be within their effective lifetime)

//循环创建子线程,并且打印是第几个子线程
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<pthread.h>

void* mythread(void *arg)
{
    
    
    int i=*(int*)arg;
    printf("[%d]:child thread,pid==[%d],id==[%ld]\n",i,getpid(),pthread_self());
}

int main()
{
    
    
    int n=5;
    int i=0;
    int a[5];
    //创建子线程
    pthread_t thread[5];
    for(i=0;i<5;i++)
    {
    
    
        a[i]=i+1;
        int ret=pthread_create(&thread[i],NULL,mythread,&a[i]);
        if(ret!=0)
        {
    
    
            printf("pthread_create error,[%s]\n",strerror(ret));
            return -1;
        }
    }
    printf("main thread,pid==[%d],id==[%ld]\n",getpid(),pthread_self());
    sleep(1);
    return 0;
}

image-20211125140638259

pthread_exit

It is forbidden to call the exit function in the thread, otherwise the entire process will exit. Instead, call the pthread_exit function. This function is to make a thread exit. If the main thread calls the pthread_exit function, the entire process will not exit and will not affect the execution of other threads.

//将单个线程退出

void pthread_exit(void *retval);	

//retval表示线程退出状态,通常传NULL

//pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了,栈空间就会被回收

pthread_join

//阻塞等待线程退出,获取线程退出状态。其作用,对应进程中的waitpid() 函数

int pthread_join(pthread_t thread, void **retval); 

//成功:0;失败:错误号

//thread:线程ID
//retval:存储线程结束状态,整个指针和pthread_exit的参数是同一块内存地址
//循环创建子线程,并且打印是第几个子线程
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<pthread.h>

int g_var=9;

void* mythread(void *arg)
{
    
    
    printf("child thread,pid==[%d],id==[%ld]\n",getpid(),pthread_self());
    printf("[%p]\n",&g_var);
    pthread_exit(&g_var);
}

int main()
{
    
    
    //创建子线程
    pthread_t thread;
    int ret=pthread_create(&thread,NULL,mythread,NULL);
    if(ret!=0)
    {
    
    
        printf("pthread_create error,[%s]\n",strerror(ret));
        return -1;
    }
    printf("main thread,pid==[%d],id==[%ld]\n",getpid(),pthread_self());
   
    //回收子线程
    void* p=NULL;
    pthread_join(thread,&p);
    int a=*(int*)p;
    printf("child exit status:[%d],[%p]\n",a,p);
    return 0;
}

image-20211125142919651

pthread_detach

Thread separation state: Specify this state, and the thread will actively disconnect from the main control thread. After the thread ends, its exit status is not acquired by other threads, but is automatically released directly by itself. Network, multi-threaded server commonly used

If the process has this mechanism, no zombie process will be generated. The generation of zombie process is mainly due to the fact that after the process dies, most resources are released, and some residual resources still exist in the system, causing the kernel to think that the process still exists

You can use the pthread_create function parameter 2 (thread attributes) to set thread separation

The pthread_detach function is called after the thread is created

//实现线程分离

int pthread_detach(pthread_t thread);	

//成功:0;失败:错误号

//一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL错误。也就是说,如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了
//循环创建子线程,并且打印是第几个子线程
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<pthread.h>

void* mythread(void *arg)
{
    
    
   while(1)
   {
    
    
       int a;
       int b;

       //设置取消点
       pthread_testcancel();
   }
}

int main()
{
    
    
    //创建子线程
    pthread_t thread;
    int ret=pthread_create(&thread,NULL,mythread,NULL);
    if(ret!=0)
    {
    
    
        printf("pthread_create error,[%s]\n",strerror(ret));
        return -1;
    }
    printf("main thread,pid==[%d],id==[%ld]\n",getpid(),pthread_self());
   
    //取消子线程
    pthread_cancel(thread);

    pthread_join(thread,NULL);
    return 0;
}

pthread_cancel

//杀死(取消)线程。其作用,对应进程中 kill() 函数

int pthread_cancel(pthread_t thread);	

//成功:0;失败:错误号

//线程的取消并不是实时的,而有一定的延时。需要等待线程到达某个取消点(检查点)
//取消点:是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用creat,open,pause,close,read,write..... 执行命令man 7 pthreads可以查看具备这些取消点的系统调用列表。可粗略认为一个系统调用(进入内核)即为一个取消点。还以通过调用pthread_testcancel函数设置一个取消点

void pthread_testcancel(void);

pthread_equal

//比较两个线程ID是否相等

int pthread_equal(pthread_t t1, pthread_t t2);

thread properties

The separation state of a thread determines how a thread terminates itself. There are two states:

  • Non-detached state: The default attribute of a thread is a non-detached state. In this case, the original thread waits for the created thread to end. Only when the pthread_join() function returns, the created thread is terminated and the system resources occupied by itself can be released
  • Detached state: The detached thread is not waited by other threads. After running by itself, the thread is terminated and the system resources are released immediately. You should choose the appropriate separation state according to your own needs
//定义线程属性类型类型的变量
pthread_attr_t  attr;	

//对线程属性变量进行初始化
int pthread_attr_init (pthread_attr_t* attr);

//设置线程为分离属性
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
//attr: 线程属性
//detachstate:
//PTHREAD_CREATE_DETACHED(分离)
//PTHREAD_CREATE_JOINABLE(非分离)
//这一步完成之后调用pthread_create函数创建线程,则创建出来的线程就是分离线程;其实上述三步就是pthread_create的第二个参数做准备工作

//释放线程属性资源
int pthread_attr_destroy(pthread_attr_t *attr);
//参数:线程属性

thread synchronization

Thread synchronization means that when a thread issues a function call, the call does not return until the result is obtained. At the same time, other threads cannot call this function to ensure data consistency.

example

Create two threads, let the two threads share a global variable int number, and then let each thread count 5000 times, and see how much the number value is printed out at the end

After many tests, the final results show that there may be cases where the number value is less than 5000*2=10000

Reason: If sub-thread A finishes executing the cur++ operation and has not assigned the value of cur to number, it loses the CPU execution right, and sub-thread B gets the cpu execution right, and sub-thread B finally executes number=cur, and then loses The right to execute the CPU; at this time, the child thread A regains the right to execute the CPU, and executes the number=cur operation, which will overwrite the value that thread B just wrote back to number, causing the value of number to not meet the expected value

Reasons for data confusion

  • Resource Sharing
  • Scheduling is random (the order in which threads operate on shared resources is uncertain)
  • Lack of necessary synchronization mechanism between threads

How to solve

  • Atomic operation: the operation is either not done or completed
  • Using Mutexes: Simulating Atomic Operations

mutex

Linux provides a mutex mutex (also known as a mutex). Each thread tries to lock the resource before operating on it. The operation can only be performed after successful locking, and the operation is completed and unlocked.

Resources are still shared, and there is still competition between threads, but the access to resources is turned into a mutually exclusive operation through "locks", and then time-related errors will no longer occur

When thread 1 accesses the shared resource, it must first determine whether the lock is locked. If it is locked, it will block and wait; Other threads have the opportunity to acquire the lock

At the same time, only one thread can hold the lock, as long as the thread has not completed the operation, the lock will not be released

After using the mutex, the two threads changed from parallel operation to serial operation, and the efficiency was reduced, but the problem of data inconsistency was solved

main function

//pthread_mutex_t 类型
//本质是一个结构体,为简化理解,应用时可简单当成整数看待
pthread_mutex_t mutex; 
//变量mutex只有两种取值1、0


//pthread_mutex_init函数
//初始化一个互斥锁(互斥量) ---> 初值可看作1
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
//mutex:传出参数,调用时应传 &mutex	
//attr:互斥锁属性。是一个传入参数,通常传NULL,选用默认属性(线程间共享)
//restrict关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成。不能通过除本指针以外的其他变量或指针修改互斥量mutex的两种初始化方式:
//静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了static关键字修饰),可以直接使用宏进行初始化
pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;
//动态初始化:局部变量应采用动态初始化
pthread_mutex_init(&mutex, NULL);


//pthread_mutex_destroy函数
//销毁一个互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
//mutex—互斥锁变量


//pthread_mutex_lock函数
//对互斥所加锁,可理解为将mutex--
int pthread_mutex_lock(pthread_mutex_t *mutex);
//mutex—互斥锁变量


//pthread_mutex_unlock函数
//对互斥所解锁,可理解为将mutex ++
int pthread_mutex_unlock(pthread_mutex_t *mutex);


//pthread_mutex_trylock函数
//尝试加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);
//mutex—互斥锁变量

lock and unlock

The lock tries to lock. If the lock is unsuccessful, the thread will be blocked until other threads holding the mutex are
unlocked. Wake up, depending on priority, scheduling. Default: block first, wake up first

Guess you like

Origin blog.csdn.net/blll0/article/details/121537807