Linux多进程编程(下)

二十一、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查看该服务
相关推荐
_可乐无糖2 小时前
活到老学到老之Jenkins build triggers中的定时schedule规则细讲
linux·jenkins·pipe
博睿谷IT99_4 小时前
Linux 备份与恢复常用命令
java·linux·服务器
阳懿4 小时前
windows系统电脑远程登录ubuntu系统电脑
linux·运维·ubuntu
wuk9984 小时前
在Ubuntu 18.04/20.04 LTS设置静态DNS服务器
linux·服务器·ubuntu
“愿你如星辰如月”4 小时前
Linux C缓冲区机制全解析
linux·运维·服务器·c语言·vscode
探云抛雾؁ۣۖ5 小时前
SSH安全 白名单配置限制实战:AllowUsers 限制指定 IP 登录
linux·运维·服务器
BUTCHER56 小时前
Go语言环境安装
linux·开发语言·golang
云上小朱6 小时前
软件部署-domino
linux·apache
深盾科技6 小时前
了解一下Ubuntu上搭建的ROS环境
linux·运维·ubuntu