今天记录一下最近在学习进程和线程方面的只是中,所遇到的一些问题。
父进程先于子进程结束,子进程就变成了孤儿进程,它会由init进程收养,成为init进程的子进程,本来它父进程的ID应该为init的ID也就是1.但如果你在Ubuntu的图形界面的终端下执行下面的程序,你会发现孤儿进程的ID并不是1.
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
pid_t pid;
pid = fork();
switch(pid)
{
case 0:
while(1)
{
printf("A background process, PID: %d, ParentID: %d\n",getpid(),getppid()); //输出子进程以及它父进程的ID
sleep(3);
}
case -1:
perror("Process creation failed\n");
exit(-1);
default:
printf("I am parent process, my pid is %d \n",getpid()); //输出父进程的ID
exit(0);
}
return 0;
}
如果在命令行界面下执行这段代码,则输出的ppid是1,上网百度后,下面给出自己的理解。
首先说一下init的ID为啥是1.idle进程ID为0,其前身是系统创建的第1个进程,idle通过kernel_thread创建了两个内核线程,其中一个就是kernel_init(内核态),进程id为1.然后1号内核进程(kernel_init)调用init函数并演化成1号用户进程(init)。
因为Ubuntu是使用system来管理系统的发行版,systemd的源码中调用了prctl(PR_SET_CHILD_SUBREAPER,1)系统调用,这个函数的作用就是让当前进程像init进程一样收养孤儿进程,所以在图形界面下孤儿进程的ppid为systemd的进程号。但我在deepin的图形界面下却没有遇到这个问题。
第二个问题是在创建守护进程使,为什么要fork()两次并退出父进程。下面是创建一个守护进程的过程
#include <stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/param.h>
#include<sys/stat.h>
#include<unistd.h>
#include<signal.h>
#include<time.h>
#include<syslog.h>
int init_daemon()
{
int pid;
int i;
//忽略终端I/O信号,STOP信号
signal(SIGTTOU,SIG_IGN);
signal(SIGTTIN,SIG_IGN);
signal(SIGTSTP,SIG_IGN);
signal(SIGHUP,SIG_IGN);
pid = fork();
if(pid > 0)
exit(0); //结束父进程,使子进程成为后台进程
else if(pid < 0)
return -1;
//建立一个新的进程组,使子进程成为这个进程组的首进程,以使该进程脱离所有终端
setsid();
//再次建立一个子进程,退出父进程,保证该进程不是进程组长,同时让该进程无法再打开一个新的终端
pid = fork();
if(pid > 0)
exit(0);
else if(pid < 0)
return -1;
//关闭所有从父进程继承的不再需要的文件描述符
for(i = 0; i < NOFILE; close(i++));
//改变工作目录,使进程不与任何文件系统联系
chdir("/");
//将文件屏蔽字设置为 0
umask(0);
//忽略SIGCHLD信号
signal(SIGCHLD,SIG_IGN);
return 0;
}
int main(int argc, char *argv[])
{
time_t now;
init_daemon();
syslog(LOG_USER|LOG_INFO,"测试守护进程! \n");
while(1)
{
sleep(3);
time(&now);
syslog(LOG_USER|LOG_INFO,"系统时间:\t%s\t\t\n",ctime(&now));
}
return 0;
}
下面是我的理解:
第一次fork退出是为了让子进程成为孤儿进程并且保证子进程不是会话组组长(进程ID和进程组ID相同的进程),这样才能调用setsid函数来脱离所有终端。但调用setsid后进程会成为会话组组长,那么这个进程就可以重新申请打开一个终端,这时候就需要再fork一次,将调用fork的进程退出,保证子进程不是进程组长,这样就不能重新打开一个终端了。
第三个问题就是在创建线程池的时候,在线程任务中,当判断线程任务是否为0使,要用while循环,而不是if语句来判断。
pthread_mutex_lock(&(pool-lpthread>mutex));
// 若等待队列为 0 则处于阻塞状态
while(pool-lpthread>cur_tasks == 0 && !pool-lpthread>stop) {
pthread_cond_wait(&(pool-lpthread>cond),&(pool-lpthread>mutex));
}
假设任务A给向等待这的B发出signal并解锁,B进入到判断等待队列是否为0,然后进入pthread_cond_wait函数,wait函数先解锁,然后再接受信号,最后再加锁。如果在B的wait函数刚解开锁之后,C跑去执行pthread_cond_wait函数,然后从B哪里抢到了A发出的signal,接着将这个任务执行掉。而B在没接受到信号后,如果这个时候是if的话,它就会接着去执行下边的代码,这样就会使程序出错,如果是while循环的话,它接写来会去判断任务队列是否为0,接着就会继续阻塞着等待下一个signal。所以这里必须用while。