二十一、fifo实现非血缘关系进程通信
cpp
#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
cpp
#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命名管道参数

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

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

cpp
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_WRITE
flags:标注共享内存的共享属性。MAP_SHARED、MAP_PRIVATE
fd:用于创建共享内存映射区的那个文件的文件描述符。
offset:默认0,表示映射文件全部。偏移位置。需是4K的整数倍。
返回值:
成功:映射区的首地址。
失败:MAP_FAILED,errno
cpp
int munmap (void *addr, size_t length);
释放映射区。
addr:mmap的返回值
length:大小
二十三、mmap建立映射区
cpp
#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使用注意事项
cpp
使用注意事项:
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通信
cpp
#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值不同是因为变量读时共享,写时覆盖(相当于父子两份各自的数据,互不干涉)

cpp
#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通信
cpp
#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;
}
//写端进程
cpp
#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;
}
//读端代码
//由于先打开写端,已经写入几个数据,读的时候已经错过了前面的数据

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

二十七、mmap匿名映射区
cpp
#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文件不存在,通信时不需要额外创建一个通信文件

cpp
#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;
}

cpp
#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操作系统,因为它没有ANONYMOUS
p = (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;
}

cpp
注意:以上所有匿名映射区只适用于父子进程,无血缘关系进程如兄弟进程不行,
因为父子进程共享fd文件描述符和mmap映射区
二十八、信号的概述
cpp
信号共性:
简单、不能携带大量信息、满足条件才发送。
信号的特质:
信号是软件层面上的"中断",一旦信号产生,无论程序执行到什么位置,必须立即停止运行,
处理信号,处理结束,再继续执行后续指令。
所有信号的产生及处理全部都是由【内核】完成的。
各种手段来驱使内核产生信号,如下图:

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

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

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

二十九、常规信号
cpp
1-31的信号是常规信号

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



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

cpp
#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;
}

cpp
int kill (pid_t pid, int sig);
sig:不推荐直接使用数字,应使用宏名,因为不同操作系统信号编号可能不同,但名称一致。
pid>0:发送信号给指定的进程。
pid=0:发送信号给与调用kill函数进程属于同一进程组的所有进程。
pid<-1:取pid的绝对值发给对应进程组。
pid=-1:发送给进程有权限发送的系统中所有进程。
cpp
kill -9 -10698 //杀死10698进程组的所有进程
cpp
#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;
}
//父子进程同属一个进程组,都将被杀死

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


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

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

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

三十二、setitimer函数
cpp
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
cpp
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);
cpp
#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
三十三、信号集操作函数

cpp
用set信号集来改变阻塞信号集
cpp
信号集操作函数。
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:
传出的未决信号集。
cpp
#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实现信号捕捉
cpp
//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实现信号捕捉
cpp
#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信号回收子进程

cpp
#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;
}

三十八、慢速系统调用中断
cpp
慢速系统调用:如果在阻塞期间收到一个信号,该系统调用就被中断(如信号),不再继续执行;
也可以设定系统调用是否重启。如,read、write、pause、wait...
其他系统调用:getpid、getppid、fork..
可修改sa_flags参数来设置被信号中断后系统调用是否重启。
SA_INTERRURT 不重启。SA_RESTART 重启。
三十九、会话
cpp
创建一个会话需要注意以下6点注意事项:
1.调用进程不能是进程组组长,该进程变成新会话首进程(session header)
2.该进程成为一个新进程组的组长进程。
3.需有 root 权限(ubuntu 不需要)
4.新会话丢弃原有的控制终端,该会话没有控制终端。(没有窗口,不能人机交互)
5.该调用进程是组长进程,则出错返回。
6.建立新会话时,先调用 fork,父进程终止,子进程调用setsid
cpp
setsid函数:
创建一个会话,并以自己的ID设置进程组ID,同时也是新会话的ID。
pid_t setsid(void);
成功:返回调用进程的会话ID;失败:-1,设置errno
调用了setsid函数的进程,既是新的会长,也是新的组长。
如下图:
进程ID、进程组ID、会话ID一样(三线合一)

cpp
#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;
}

四十、守护进程
cpp
守护进程,是Linux中的后台服务进程,通常周期性地执行某种任务或等待处理某些发生的事件。
一般采用以d结尾的名字。
Linux后台的一些系统服务进程,没有控制终端,不能直接和用户交互。
如:预读入缓输出机制的实现;ftp服务器;nfs服务器等。
不受用户登录、注销的影响,一直在运行,除非kill掉
创建守护进程,最关键的一步是调用setsid 函数创建一个新的Session。
cpp
守护进程创建步骤:
1.fork子进程,让父进程终止。
2.子进程调用setsid()创建新会话
3.改变工作目录位置chdir()
4.重设umask文件权限掩码
5.关闭/重定向文件描述符
6.守护进程业务逻辑。while()
关闭文件描述符是因为0 1 2的fd默认是开启的,用不到,浪费资源
cpp
#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查看该服务