当前位置: 首页 > news >正文

Linux多进程编程(下)

 二十一、fifo实现非血缘关系进程通信

#include<stdio.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/type.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>void sys_err(char* str) {perror(str);exit(1);
}int main() {int fd, i;char buf[4096];if (argc < 2) {printf("Enter like this: ./a.out fifoname\n");return -1;}fd = open(argv[1], O_WRONLY);if (fd < 0) {sys_err("open error");}i = 0;while (1) {sprintf(buf, "hello happygame %d\n", i++);write(fd, buf, strlen(buf));sleep(1);}close(fd);return 0;
}//以上是写端的代码
//执行程序要输入fifo命名管道参数,如myfifo
#include<stdio.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/type.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>void sys_err(char* str) {perror(str);exit(1);
}int main() {int fd, len;char buf[4096];if (argc < 2) {printf("Enter like this: ./a.out fifoname\n");return -1;}fd = open(argv[1], O_WRONLY);if (fd < 0) {sys_err("open error");}while (1) {len = read(fd, buf, sizeof(buf));write(STDOUT_FILENO, buf, len);sleep(3);}close(fd);return 0;
}//以上是读端的代码
//usleep是微秒睡眠
//执行程序要输入fifo命名管道参数

以上实现了一读端多写端如果是一写端多读端就会出现接收数据混乱(如下图)两者都需要多个bash窗口来实现,而且各个进程毫无血缘关系


 二十二、mmap、munmap函数

存储映射I/O(Memory-mapped I/O)使一个磁盘文件与存储空间中的一个缓冲区相映射。
这个映射工作可以通过mmap函数来实现

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset):
创建共享内存映射
参数:addr:指定映射区的首地址。通常传NULL,表示让系统自动分配length:共享内存映射区的大小。(<= 文件的实际大小)prot:共享内存映射区的读写属性。PROT_READ、PROT_WRITE、PROT_READ | PROT_WRITEflags:标注共享内存的共享属性。MAP_SHARED、MAP_PRIVATEfd:用于创建共享内存映射区的那个文件的文件描述符。offset:默认0,表示映射文件全部。偏移位置。需是4K的整数倍。返回值:成功:映射区的首地址。失败:MAP_FAILED,errno
int munmap (void *addr, size_t length);    
释放映射区。
addr:mmap的返回值
length:大小

 二十三、mmap建立映射区

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/mman.h>
#include<fcntl.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>void sys_err(const char* str) {perror(str);exit(1);
}int main(int argc, char* argv[]) {char* p = NULL;int fd;fd = open("testmap", O_RDWR | O_CREAT | O_TRUNC, 0644);if (fd == -1) {sys_err("open error");}//lseek(fd, 10, SEEK_END);//write(fd, "\0", 1);ftruncate(fd, 20);	//这个函数扩展空间可顶替上两行,需要写权限才能扩展int len = lseek(fd, 0, SEEK_END);p = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (p == MAP_FAILED) {sys_err("mmap error");}//使用p对文件进行读写操作strcpy(p, "hello mmap");	//写操作printf("---%s---\n", p);	//读操作int ret = munmap(p, len);	//释放映射区if (ret == -1) {sys_err("munmap error");}close(fd);return 0;
}//如下图:读出了---hello mmap,同时在testmap文件里面写入了hellommap


 二十四、mmap使用注意事项

使用注意事项:
1.用于创建映射区的文件大小为0,实际指定非0大小创建映射区,出"总线错误"。2.用于创建映射区的文件大小为0,实际指定0大小创建映射区,出"无效参数"错误。3.用于创建映射区的文件读写属性为只读。映射区属性为读、写。出"无效参数"错误。4.创建映射区需要读文件,要read权限。
权限为共享时:mmap的读写权限应 <= 文件的open权限,但不能两个都是写。
(接上:最好两者都有读写权限,因为文件有写权限才能ftruncate扩展空间,映射区有写权限才能strcpy)。5.文件描述符fd,在mmap创建映射区完成即可关闭。后续访问文件,用地址访问。
(接上:在创建完映射区后马上close(fd)无影响)6.offset必须是4096的整数倍。(MMU映射的最小单位4k)7.对申请的映射区内存,不能越界访问。8.munmap用于释放的地址,必须是mmap申请返回的地址。9.映射区访问权限为"私有" MAP_PRIVATE,对内存所做的所有修改,只在内存有效,不会反应到物理磁盘上。10.映射区访问权限为"私有" MAP_PRIVATE,只需要open文件时,有读权限,用于创建映射区即可。mmap函数的保险调用方式:1. fd = open("文件名", O_RDWR);2.mmap(NULL, 有效文件大小, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

1.

2.

3.

4.

5.

6.

7.

8.

9.

10.


 二十五、父子进程间mmap通信

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/wait.h>int var = 100;int main(void) {int* p;pid_t pid;int fd;fd = open("temp", O_RDWR | O_CREAT | O_TRUNC, 0644);if (fd < 0) {perror("open error");exit(1);}ftruncate(fd, 4);p = (int*)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);//p = (int*)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);if (p == MAP_FAILED) {perror("mmap error");exit(1);}close(fd);		//映射区创建完毕,即可关闭文件pid = fork();if (pid == 0) {*p = 2000;	//写共享文件var = 1000;printf("child, *p = %d, var = %d\n", *p, var);}else {sleep(1);printf("parent, *p = %d, var = %d\n", *p, var);	//读共享文件wait(NULL);int ret = munmap(p, 4);		//释放映射区if (ret == -1) {perror("munmap error");exit(1);}}return 0;
}//执行结果如下图:
//var值不同是因为变量读时共享,写时覆盖(相当于父子两份各自的数据,互不干涉)

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/wait.h>int var = 100;int main(void) {int* p;pid_t pid;int fd;fd = open("temp", O_RDWR | O_CREAT | O_TRUNC, 0644);if (fd < 0) {perror("open error");exit(1);}ftruncate(fd, 4);//p = (int*)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);p = (int*)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);if (p == MAP_FAILED) {perror("mmap error");exit(1);}close(fd);		//映射区创建完毕,即可关闭文件pid = fork();if (pid == 0) {*p = 7000;	//写共享文件var = 1000;printf("child, *p = %d, var = %d\n", *p, var);}else {sleep(1);printf("parent, *p = %d, var = %d\n", *p, var);	//读共享文件wait(NULL);int ret = munmap(p, 4);		//释放映射区if (ret == -1) {perror("munmap error");exit(1);}}return 0;
}//执行结果如下图:
//由于mmap权限是MAP_PRIVATE,所以父子的p值不同,不共用


 二十六、无血缘关系进程mmap通信

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<unistd.h>
#include<errno.h>struct student {int id;char name[256];int age;
};void sys_err(const char* str) {perror(str);exit(1);
}int main(int argc, char* argv[]) {struct student stu = { 1, "xiaoning", 18 };struct student* p;int fd;fd = open("test_map", O_RDWR | O_CREAT | O_TRUNC, 0664);    //trunc写一次清空一次if (fd = -1) {sys_err("open error");}ftruncate(fd, sizeof(stu));p = mmap(NULL, sizeof(stu), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (p == MAP_FAILED) {sys_err("mmap error");}close(fd);while (1) {memcpy(p, &stu, sizeof(stu));stu.id++;sleep(1);}munmap(p, sizeof(stu));return 0;
}//写端进程
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<unistd.h>
#include<errno.h>struct student {int id;char name[256];int age;
};void sys_err(const char* str) {perror(str);exit(1);
}int main(int argc, char* argv[]) {struct student stu;struct student* p;int fd;fd = open("test_map", O_RDONLY);if (fd = -1) {sys_err("open error");}p = mmap(NULL, sizeof(stu), PROT_READ, MAP_SHARED, fd, 0);if (p == MAP_FAILED) {sys_err("mmap error");}close(fd);while (1) {printf("id = %d, name = %s, age = %d\n", p->id, p->name, p->age);sleep(1);}munmap(p, sizeof(stu));return 0;
}//读端代码
//由于先打开写端,已经写入几个数据,读的时候已经错过了前面的数据

无血缘关系进程间通信。
map:数据可以重复读取。
fifo:数据只能一次读取。例如:mmap写端进程sleep(2),读端进程sleep(1),
读端会读出两个重复的数据,因为1s写端还没有新的数据写入


 二十七、mmap匿名映射区

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/wait.h>int var = 100;int main(void) {int* p;pid_t pid;int fd;fd = open("temp", O_RDWR | O_CREAT | O_TRUNC, 0644);if (fd < 0) {perror("open error");exit(1);}int ret = unlink("temp");if (ret == -1) {perror("unlink error");exit(1);}ftruncate(fd, 4);p = (int*)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);//p = (int*)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);if (p == MAP_FAILED) {perror("mmap error");exit(1);}close(fd);		//映射区创建完毕,即可关闭文件pid = fork();if (pid == 0) {*p = 7000;	//写共享文件var = 1000;printf("child, *p = %d, var = %d\n", *p, var);}else {sleep(1);printf("parent, *p = %d, var = %d\n", *p, var);	//读共享文件wait(NULL);int ret = munmap(p, 4);		//释放映射区if (ret == -1) {perror("munmap error");exit(1);}}return 0;
}//unlink使得temp文件不存在,通信时不需要额外创建一个通信文件

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/wait.h>int var = 100;int main(void) {int* p;pid_t pid;//想指定多大就多大p = (int*)mmap(NULL, 40, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);//p = (int*)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);if (p == MAP_FAILED) {perror("mmap error");exit(1);}pid = fork();if (pid == 0) {*p = 7000;	//写共享文件var = 1000;printf("child, *p = %d, var = %d\n", *p, var);}else {sleep(1);printf("parent, *p = %d, var = %d\n", *p, var);	//读共享文件wait(NULL);int ret = munmap(p, 4);		//释放映射区if (ret == -1) {perror("munmap error");exit(1);}}return 0;
}

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/wait.h>int var = 100;int main(void) {int* p;pid_t pid;int fd = open("dev/zero", O_RDWR);	//用于更早的unix操作系统,因为它没有ANONYMOUSp = (int*)mmap(NULL, 490, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (p == MAP_FAILED) {perror("mmap error");exit(1);}pid = fork();if (pid == 0) {*p = 7000;	//写共享文件var = 1000;printf("child, *p = %d, var = %d\n", *p, var);}else {sleep(1);printf("parent, *p = %d, var = %d\n", *p, var);	//读共享文件wait(NULL);int ret = munmap(p, 4);		//释放映射区if (ret == -1) {perror("munmap error");exit(1);}}return 0;
}

注意:以上所有匿名映射区只适用于父子进程,无血缘关系进程如兄弟进程不行,
因为父子进程共享fd文件描述符和mmap映射区

二十八、信号的概述

信号共性:
简单、不能携带大量信息、满足条件才发送。信号的特质:
信号是软件层面上的“中断”,一旦信号产生,无论程序执行到什么位置,必须立即停止运行,
处理信号,处理结束,再继续执行后续指令。所有信号的产生及处理全部都是由【内核】完成的。各种手段来驱使内核产生信号,如下图:

递达:信号递送并且到达进程。
未决:产生和递达之间的状态。主要由于阻塞(屏蔽)导致该状态。
信号的处理方式:
1.执行默认动作,每一个信号都有属于他自己的默认处理方式
2.忽略(丟弃)
3.捕捉(调用户处理函数)
阻塞信号集:
信号屏蔽字:将某些信号加入集合,对他们设置屏蔽(被阻塞)。未决信号集:
1.信号产生,未决信号集中描述该信号的位立刻翻转为1,表示信号处于未决状态。
当信号被处理对应位翻转回为0。这一时刻往往非常短暂。2.信号产生后由于某些原因(主要是阻塞)不能抵达。这类信号的集合称之为未决信号集。
在屏蔽解除前,信号一直处于未决状态。
下图信号未决信号集和信号屏蔽字默认都是0,
当有个信号产生对应的未决信号字变为1未决信号集合和信号屏蔽字本质都是位图(0和1)

如下图,当信号被处理掉后,对应的未决信号变为0

如下图,当信号被屏蔽,对应的信号屏蔽字就变为1,
此时相当于递达的道路被阻断


二十九、常规信号

1-31的信号是常规信号

每个信号有其必备4要素,分别是:
1.编号
2.名称
3.事件
4.默认处理动作

默认动作:
Term:终止进程
Ign:忽略信号(默认即时对该种信号忽略操作)
Core:终止进程,生成Core文件。(查验进程死亡原因,用于gdb调试)
Stop:停止(暂停)进程
Cont:继续运行进程特别强调 9)SIGKILL和 19)SIGSTOP信号,不允许忽略和捕捉,
只能执行默认动作。甚至不能将其设置为阻塞。另外需清楚,只有每个信号所对应的事件发生了,该信号才会被递送(但不一定递达),不应乱发信号!。

三十、kill函数

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>
#include<signal.h>void sys_err(const char* str) {perror(str);exit(1);
}int main(int argc, char* argv[]) {pid_t pid = fork();if (pid > 0) {printf("parent, pid = %d\n", getpid());while (1);}else if (pid = 0) {printf("child pid = %d, ppid = %d\n", getpid(), getppid());sleep(2);kill(getppid(), SIGKILL);}return 0;
}

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>
#include<signal.h>void sys_err(const char* str) {perror(str);exit(1);
}int main(int argc, char* argv[]) {pid_t pid = fork();if (pid > 0) {while (1) {printf("parent, pid = %d\n", getpid());sleep(1);}}else if (pid = 0) {sleep(2);printf("child pid = %d, ppid = %d\n", getpid(), getppid());kill(getppid(), SIGCHLD);    //会忽略}return 0;
}

int kill (pid_t pid, int sig);sig:不推荐直接使用数字,应使用宏名,因为不同操作系统信号编号可能不同,但名称一致。pid>0:发送信号给指定的进程。pid=0:发送信号给与调用kill函数进程属于同一进程组的所有进程。pid<-1:取pid的绝对值发给对应进程组。pid=-1:发送给进程有权限发送的系统中所有进程。
kill -9 -10698        //杀死10698进程组的所有进程
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>
#include<signal.h>void sys_err(const char* str) {perror(str);exit(1);
}int main(int argc, char* argv[]) {pid_t pid = fork();if (pid > 0) {while (1) {printf("parent, pid = %d\n", getpid());sleep(1);}}else if (pid = 0) {printf("child pid = %d, ppid = %d\n", getpid(), getppid());sleep(10);kill(0, SIGKILL);}return 0;
}//父子进程同属一个进程组,都将被杀死

权限保护:super用户(root)可以发送信号给任意用户,普通用户是不能向系统用户发送信号的。
kill-9(root用户的pid)是不可以的。同样,普通用户也不能向其他普通用户发送信号,终止其进程。只能向自己创建的进程发送信号。普通用户基本规则是:发送者实际或有效用户ID=接收者实际或有效用户ID。例如下图:普通用户不能杀死系统进程


三十一、alarm函数

设置定时器(闹钟)。在指定seconds后,内核会给当前进程发送 14)SIGALRM信号。
进程收到该信号,默认动作终止。
使用自然计时,阻塞等待照样在计时alarm函数:
定时发送SIGALRM给当前进程。
unsigned int alarm(unsigned int seconds):seconds:定时秒数返回值:上次定时剩余时间。无错误现象。time命令:查看程序执行时间。
实际时间=用户时间+内核时间+等待时间。-> 优化瓶颈:IO(减少等待时间)
alarm(5) → 3sec → alarm(4)(返回2) → 
5sec → alarm(5)(返回0)(由于到时间了此时也已发送了信号) → alarm(0)(返回5)
//测试计算机1秒钟数多少个数#include<stdio.h>
#include<unistd.h>int main(){int i;alarm(1);for(i = 0; i++){printf("%d\n", i);}return 0;
}//执行结果如下图:
//结果因机器而异

time ./alarm    //time来显示程序执行时间//大部分时间浪费在IO(等待时间)(打印到屏幕)

打印的东西写入到文件,就减少了打印时间(等待时间)


三十二、setitimer函数

setitimer函数:
设置定时器(闹钟)。可代替alarm函数。精度微秒us,可以实现周期定时。
int setitimer (int which, const struct itimerval *new_value, struct itimerval *old_value);参数:which:指定定时方式1.自然定时:ITIMER_REAL: → 14)SIGLARM    计算自然时间。2.虚拟空间计时(用户空间): ITIMER_VIRTUAL → 26)SIGVTALRM    只计算进程占用cpu的时间3.运行时计时(用户+内核): ITIMER_PROF → 27)SIGPROF    计算占用cpu及执行系统调用的时间。参数:new_value:定时秒数old_value:传出参数,上次定时剩余时间。struct itimerval{struct timeval it_interval; /* next value */struct timeval it_value;    /* current value */
};struct timeval {time_t tv_sec;          /* seconds */suseconds_t tv_usec;    /* microseconds */
};返回值:成功:0失败:-1 errno
struct itimerval new_t:
struct itimerval old_t:new_t.it_interval.tv_sec = 1;
new_t.it_interval.tv_usec = 0;new_t.it_value.tv_sec= 0;
new_t.it_value.tv_usec = 0;setitimer(&new_t,&old_t);
#include<stdio.h>
include<sys/time.h>
#include<signal.h>void myfunc(int signo){printf("hello world\n");
}int main(void){struct itimerval it, oldit;signal(SIGALRM, myfunc);    //注册SIGALRM信号的捕捉处理函数。it.it_value.tv_sec =2;it.it_value.tv_usec =0;it.it_interval.tv_sec =5;it.it_interval.tv_usec =0;if(setitimer(ITIMER_REAL, &it, &oldit) == -1){perror("setitimer error");return -1;}while(1);return 0;
}//该程序2秒后会打印hello world,之后每隔5秒打印hello world

三十三、信号集操作函数

用set信号集来改变阻塞信号集
信号集操作函数。
sigset_t set;                                 自定义信号集。
sigemptyset(sigset_t *set);                   清空信号集
sigfillset(sigset_t *set);                    全部置1
sigaddset(sigset_t *set, int signum);         将一个信号添加到集合中
sigdelset(sigset_t* set, int signum);         将一个信号从集合中移除
sigismember(const sigset_t *set, int signum); 判断一个信号是否在集合中 在->1,不在->0设置信号屏蔽字和解除屏蔽:
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
how:    SIG_BLOCK:       设置阻塞SIG_UNBLOCK:     取消阻塞SIG SETMASK:     用自定义set替换mask(会全部替换,不建议)
set:    自定义set
oldset:    旧的mask。查看未决信号集:
int sigpending(sigset_t *set):
set:传出的未决信号集。
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>void sys_err(const char* str)
{perror(str);exit(1);
}void print_set(sigset_t *set) {int i;for (i = 1; i < 32; i++) {if (sigismember(set, i)) {putchar('1');}else {putchar('0');}printf("\n");}
}int main(int argc, char* argv[]) {sigset_t set, oldset, pedset;int ret = 0;sigemptyset(&set);sigaddset(&set, SIGINT);	//SIGINT是ctrl+c的信号//sigaddset(&set, SIGKILL);	//无法阻塞kill信号,外部杀死该进程照样杀死ret = sigprocmask(SIG_BLOCK, &set, &oldset);if (ret == -1) {sys_err("sigprocmask error");}while (1) {ret = sigpending(&pedset);if (ret == -1) {sys_err("sigpending error");}print_set(&pedset);sleep(1);}return 0;
}//如下图,按下Ctrl+c之后第二个位置的0变为1
//之后再ctrl+c也无法暂停该进程,因为被屏蔽


三十四、signal实现信号捕捉

//signal函数用于注册一个信号捕捉函数#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>void sys_err(const char* str)
{perror(str);exit(1);
}void sig_catch(int signo) {printf("catch you: %d\n", signo);return;
}int main(int argc, char* argv[]) {signal(SIGINT, sig_catch);while (1);return 0;
}//给ctrl+c设置了信号捕捉,每ctrl+c一次就输出一次
//由于没给ctrl+\设置信号捕捉,所以直接退出


三十五、sigaction实现信号捕捉

#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>void sys_err(const char* str)
{perror(str);exit(1);
}void sig_catch(int signo) {if (signo == SIGINT) {printf("catch you: %d\n", signo);}else if (signo == SIGQUIT) {printf("---------catch you: %d\n", signo);}return;
}int main(int argc, char* argv[]) {struct sigaction act, oldact;act.sa_handler = sig_catch;		//设置回调函数名sigemptyset(&(act.sa_mask));	//清空sa_mask屏蔽字(sig_catch函数执行期间屏蔽某个信号)act.sa_flags = 0;				//默认值int ret = sigaction(SIGINT, &act, &oldact);		//注册信号捕捉函数if (ret == -1) {sys_err("sigaction error");}ret = sigaction(SIGQUIT, &act, &oldact);	//捕捉CTRL+'\'if (ret == -1) {sys_err("sigaction error");}while (1);return 0;
}


三十六、内核实现信号捕捉过程


三十七、借助SIGCHLD信号回收子进程

#include<stdio.h>
#include<signal.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>void sys_err(const char* str)
{perror(str);exit(1);
}//有子进程终止,发送SIGCHLD信号时,该函数会被内核回调
void catch_child(int signo) {pid_t wpid;int status;//循环回收多个子进程,避免同时多个子进程死亡,多个SIGCHLD信号//不循环的话只能回收一个信号,导致有子进程成为僵尸进程//while ((wpid = wait(NULL)) != -1)while ((wpid = waitpid(-1, &status, 0)) != -1) {if (WIFEXITED(status)) {printf("catch child id = %d, ret = %d\n", wpid, WEXITSTATUS(status));}}return;
}int main(int argc, char* argv[]) {pid_t pid;//从此处开始阻塞,防止父进程还没注册信号,子进程就先死亡sigset_t set;sigemptyset(&set);sigaddset(&set, SIGCHLD);sigprocmask(SIG_BLOCK, &set, NULL);int i;for (i = 0; i < 15; i++) {if ((pid = fork()) == 0) {break;}}if (i == 15) {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'm parent, pid = %d\n", getpid());while (1);	//防止父进程先结束}else {printf("I'm child pid = %d\n", getpid());return i;}return 0;
}


三十八、慢速系统调用中断

慢速系统调用:如果在阻塞期间收到一个信号,该系统调用就被中断(如信号),不再继续执行;也可以设定系统调用是否重启。如,read、write、pause、wait...其他系统调用:getpid、getppid、fork..可修改sa_flags参数来设置被信号中断后系统调用是否重启。
SA_INTERRURT 不重启。SA_RESTART 重启。

三十九、会话

创建一个会话需要注意以下6点注意事项:
1.调用进程不能是进程组组长,该进程变成新会话首进程(session header)
2.该进程成为一个新进程组的组长进程。
3.需有 root 权限(ubuntu 不需要)
4.新会话丢弃原有的控制终端,该会话没有控制终端。(没有窗口,不能人机交互)
5.该调用进程是组长进程,则出错返回。
6.建立新会话时,先调用 fork,父进程终止,子进程调用setsid
setsid函数:创建一个会话,并以自己的ID设置进程组ID,同时也是新会话的ID。pid_t setsid(void);    成功:返回调用进程的会话ID;失败:-1,设置errno调用了setsid函数的进程,既是新的会长,也是新的组长。如下图:
进程ID、进程组ID、会话ID一样(三线合一)

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>int main() {pid_t pid;if ((pid = fork()) < 0) {perror("fork error");exit(1);}else if (pid == 0) {printf("child process PID is %d\n", getpid());printf("Group ID of child is %d\n", getpgid());printf("Session ID of child is %d\n", getsid());sleep(10);setsid();	//子进程非组长进程,故其成为新会话首进程,且成为组长进程。printf("Changed:\n");printf("child process PID is %d\n", getpid());printf("Group ID of child is %d\n", getpgid());printf("Session ID of child is %d\n", getsid());sleep(20);exit(0);}return 0;
}


四十、守护进程

守护进程,是Linux中的后台服务进程,通常周期性地执行某种任务或等待处理某些发生的事件。
一般采用以d结尾的名字。Linux后台的一些系统服务进程,没有控制终端,不能直接和用户交互。
如:预读入缓输出机制的实现;ftp服务器;nfs服务器等。不受用户登录、注销的影响,一直在运行,除非kill掉创建守护进程,最关键的一步是调用setsid 函数创建一个新的Session。
守护进程创建步骤:
1.fork子进程,让父进程终止。
2.子进程调用setsid()创建新会话
3.改变工作目录位置chdir()
4.重设umask文件权限掩码
5.关闭/重定向文件描述符
6.守护进程业务逻辑。while()关闭文件描述符是因为0 1 2的fd默认是开启的,用不到,浪费资源
#include<stdio.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>void sys_err(const char* str) {perror(str);exit(1);
}int main(int argc, char *argv[]) {pid_t pid;int ret, fd;pid = fork();if (pid > 0) {		//父进程终止exit(0);}pid = setsid();		//创建新会话if (pid == -1) {sys_err("setsid error");}ret = chdir("/home/happygame");	//更改工作目录位置if (ret == -1) {sys_err("chdir error");}umask(0022);	//改变文件访问权限掩码close(STDIN_FILENO);			//关闭0号文件描述符fd = open("/dev/null", O_RDWR);	//fd --> 0(可用fd最小的)if (fd == -1) {sys_err("open error");}dup2(fd, STDOUT_FILENO);	//0 1 2的fd重定向到/dev/null空洞里面dup2(fd, STDERR_FILENO);while (1);		//模拟守护进程,一直在执行服务return 0;
}//ps aux查看该服务
http://www.dtcms.com/a/411808.html

相关文章:

  • django 网站开发视频教程百度竞价是seo还是sem
  • 平谷网站建设服务阿里邮箱企业版登录入口
  • [光学原理与应用-472]:半导体芯片生产、测试的产业链中使用到哪些种类的激光器,技术原理,主要供应商。
  • JxBrowser 8.11.0 版本发布啦!
  • 达梦数据库---实时主备配置
  • 东莞电子网站建设哪儿有上线了做网站多少钱
  • 深度学习复现:CIFAR-10 数据集任务的实现
  • CTF之文件包含
  • 做的比较好看的国内网站陕西省建设网三类人员考试时间
  • 建设商业网站长春做网站建设的公司
  • 电子商务网站建设参考书电子商务的分类
  • MySQL笔记---数据类型
  • 电商运营学习网站重庆新闻第一眼
  • 建站网络建立科技开发公众号登录微信公众号登录
  • macos制作可以启动的iso引导文件
  • 成都住建局官网平台查询响应式网站建设有利于seo
  • 南京中建乡旅建设投资有限公司网站滕州市做淘宝网站的
  • 金科做的网站产业园区运营公司
  • Java银行
  • 2025 云栖大会|全面聚焦 AI,定义“智能化融合通信”新时代
  • 【学习心得】离线Linux安装conda环境教程
  • 网站开发 项目接单wordpress 仪表盘 渗透
  • AWG线径电流、电阻对照表
  • 网站降权投诉app定制哪里找
  • 学习笔记:Vue Router 中的嵌套路由详解[特殊字符]概述
  • Python包制作 - 以DLT645-2007通讯规约库为例
  • Claude Code + Holopix AI | 轻松复刻 “虚假广告“-丧尸射击小游戏
  • 网站左侧的导航是怎么做的如何选择盐城网站开发
  • win7如何建设免费网站网站建设栏目图片
  • 技术博客 SEO 优化指南:从 0 到 1 提升搜索流量​