SIGCHLD信号
只要子进程信号发生改变,就会产生SIGCHLD信号。
借助SIGCHLD信号回收子进程
回收子进程只跟父进程有关。如果不使用循环回收多个子进程,会产生多个僵尸进程,原因是因为这个信号不会循环等待。
cpp
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<sys/wait.h>
#include<signal.h>
void catch_child(int signo) //有子进程终止,发送sigchld信号时,该函数会被内核回调
{
pid_t wpid;
int status;
//while((wpid = wait(NULL))!= -1);
while((wpid = waitpid(-1 , &status , 0))!= 0 ){
if(WISEXITED(status))
printf("catch child:%d , status: %d\n" ,wpid , WEXITSTATUS(status));}
}
int main(int argc , char *argv[])
{
pid_t pid;
int i ;
//阻塞
sigset_t set ; // 防止在父进程创建sa_mask之前子进程先死亡。
sigemptyset(&set);
sigaddset(&set , SIGCHLD);
sigprocmask(SIG_BLOCK , &set , NULL);
for(i = 0 ; i < 5 ; i++){
if((pid = fork()) == 0)
break;
}
if(i == 5){
struct sigaction act;
act.sa_handler = catch_child;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGCHLD , &act , NULL);
//解除阻塞
sigprocmask(SIG_UNBLOCK , &set , NULL);
printf("I am parent , %d\n" , getpid());
}else{
printf("I am child , %d\n" , getpid());
}
return 0 ;
}
中断系统调用(非重点)
系统调用分为两类:慢速系统调用和其他系统调用。
慢速系统调用:可能会使进程永远阻塞的一类。eg. read write wait and so on .
其他系统调用:getpid()....
sa_flags 用来设置被信号中断后系统调用是否重启。
进程组和会话
进程组也称之为作业。
创建会话
会话是多个进程组的集合。
注意事项
创建会话不能是进程组组长,该进程变成新会话首进程。
创建新会话丢弃原有的控制终端,该会话没有控制终端。
建立新会话时,先调用fork,父进程终止,子进程调用setsid()。
getsid函数
获取进程所属的会话id
cpp
pid_t getsid(pid_t pid);
成功 返回会话id
失败 -1
ps ajx命令查看系统中的进程
组长进程不能成为新会话首进程,新会话首进程必定会成为组长进程。
setsid函数
创建会话,以自己的ID设置进程组ID,同时也是新会话的ID
cpp
pid_t setsid(void)
成功:返回调用进程的会话id;
失败:-1
调用setsid函数的进程,既是新的会长,也是新的组长。
守护进程(daemon精灵,进程)
守护进程时Linux中的后台服务进程,通常运行与操作系统后台,脱离控制终端,一般不与用户直接交互 , 周期性的等待某个事件发生或周期性执行某一动作,不受用户登入注销影响。一般使用d结尾的名字。
守护进程创建步骤:
1、fork子进程,让父进程终止。
2、子进程调用setsid创建新会话。
3、通常根据需要,改变工作目录位置,chdir() , 防止目录被卸载
4、通常根据需要,重设置umask文件权限掩码,影响新文件的创建权限
5、通常根据需要,关闭/重定向 文件描述符
6、守护进程 业务逻辑 while()
cpp
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<sys/stat.h>
#include<fcntl.h>
int main(int argc , char *argv[])
{
pid_t pid ;
int fd ;
pid = fork();
if(pid > 0){
exit(0); //正常终止父进程
}else{
pid = setsid(); //创建新会话
if(pid == -1){
perror("set error");
exit(1);
}
int ret = chdir("/home/qqq"); //改变工作目录位置
if(ret == -1){
perror("chdir error");
exit(1);
}
umask(0022); // 设置掩码权限
close(STDIN_FILENO); // 关闭文件描述符
fd = open ("/dev/null" , O_RDWR);
if(fd == -1){
perror("open error");
exit(1);
}
dup2(fd , STDOUT_FILENO); //重定向
dup2(fd , STDERR_FILENO);
while(1); // 模拟守护进程做的事情
}
return 0 ;
}
这里由于我不明白为什么需要将标准输入关闭,并且将标准输出和标准错误重定向,我上网查了一些资料:
1、为什么要关闭STDIN_FILENO?
守护进程是在后台运行的,不应该与终端交互,因此不需要标准输入,为防止错误,直接关闭。
2、为什么要将STDOUT and STDERR --->fd 0 ("/dev/null")
"/dev/null"是一个特殊设备,写的什么东西进去都会被吃掉,读取的时候什么也看不到,守护进程不应该输出信息到终端,因此重定向给 fd(此时是 /dev/null)本质上就是为了干净、不占资源、避免潜在错误。
线程(不要将线程和信号混合用)
线程 : LWP,light weight process 轻量级的进程 , 本质仍是进程(Linux环境下)
进程: 独立的进程地址空间 , 有PCB
线程:有独立的PCB,但没有独立的地址空间(共享)
Linux下 线程是最小的执行单位 , 进程是最小分配资源单位,可以看成只有一个线程的进程。
ps -Lf 进程id --->线程号 LWP --->CPU执行的最小单位。
线程可以看作寄存器和栈的集合。
线程共享资源
文件描述符表 、 每种信号的处理方式 、 当前工作目录 、 用户ID和组ID 、 内存地址空间、全局变量
线程非共享资源
线程id 、 处理器现场和栈指针(内核栈) 、独立的栈空间(用户栈) 、 error变量 、 信号屏蔽字
线程的优缺点
优点:提高程序的并发性 , 开销小 , 数据通信、共享数据方便
缺点:库函数不稳定 , 调试、编写困难、GDB不支持 , 对信号支持不友好
优点突出,缺点不明显,能使用线程使用线程
线程控制原语
pthread_self函数
获取线程ID ,注意这里的线程ID和线程号LWP不一样。
线程id是在进程地址空间内部,用来表示线程身份id号
cpp
pthread_t pthread_self(void);
成功返回 线程id
pthread_create函数
cpp
int pthread_create(pthread_t * tid ,const pthread_attr_t *attr , void *(*start_rountn)(void*) , void *arg);
pthread_t * tid:传出参数,传出创建子线程的id。
const pthread_attr_t *attr:线程属性 , 传NULL表示使用默认属性。
void *(*start_rountn)(void*):子线程回调函数,创建成功,pthread_create()函数返回时,该回调函数
会被自动调用。
void *arg:回调函数的参数,没有的话使用NULL
返回值 成功 0
失败errno
循环创建5个子线程。
cpp
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
void* func(void * arg)
{
int i = (int) arg;
printf("I am %dth child , pid:%d , tid:%lu\n" , i+1 , getpid() , pthread_self());
return NULL;
}
int main(int argc , char* argv[])
{
pthread_t tid;
int i ,ret;
for(i = 0 ; i < 5 ; i++){
ret = pthread_create(&tid , NULL , func , (void*)i);
if(ret == -1){
perror("pthread_create error");
exit(1);
}
}
sleep(i);
printf("I am main , pid:%d , tid:%lu\n" , getpid() , pthread_self());
return 0 ;
}
pthread_exit函数
将当前线程退出。
cpp
void pthread_exit(void *retval);
retval:退出值。无退出值时,NULL
exit() ; 退出当前进程
return ; 返回到调用者那里去
pthread_exit() ; 将调用该函数的线程退出
pthread_join函数
阻塞等待线程退出,获取线程退出状态。回收子线程。
cpp
int pthread_join(pthread_t thread, void **retval);
返回值 成功 0
失败 -1
pthread_cancel函数
杀死(取消)线程。相当于kill函数。但是需要取消点(保存点) 。如果子线程没有到达取消点,那么该函数无效,我们可以在程序中手动添加一个取消点,使用pthread_testcancel()。成功被cancel函数杀死的进程,可以使用pthread_join回收。
cpp
int pthread_cancel(pthread_t thread);
返回值: 成功 0
失败 errno