Linux系统编程二(FIFO管道、进程间通信、mmap建立映射区、信号、信号集操作函数、信号捕捉、回收子进程、守护进程)

FIFO管道进程间通信

fifo管道可以进行非血缘关系的两个进程间通信。两个进程内核空间共享,因为两个进程的虚拟地址内核空间映射一片物理内存的同一个区域,该区域有能力存放着两个进程的PCB进程控制块。

创建管道类型的且命名为myfifo的文件。

mkfifo myfifo

从写端写入数据到buf区域,写端fifo_w.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

void sys_err(char* str)
{
	perror(str);
	exit(-1);
}

int main(int argc, char* argv[])
{
	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");

	i = 0;
	while (1) {
		sprintf(buf, "hello itcast %d\n", i++);			//将参数2输出到buf指定的内存区域

		write(fd, buf, strlen(buf));
		sleep(1);
	}
	close(fd);

	return 0;
}

读端从buf中读取数据,读端fifo_r.c

//需加入相同上述头文件
void sys_err(char* str)
{
	perror(str);
	exit(-1);
}

int main(int argc, char* argv[])
{
	int fd, len;
	char buf[4096];		//共享内核空间

	if (argc < 2) {
		printf("Enter like this: ./a.out fifoname\n");
		return -1;
	}

	fd = open(argv[1], O_RDONLY);
	if (fd < 0)
		sys_err("open");

	while (1) {
		len = read(fd, buf, sizeof(buf));
		write(STDOUT_FILENO, buf, len);
		sleep(1);
	}
	close(fd);

	return 0;
}

输出结果
在这里插入图片描述

文件用于进程间通信

子进程写入数据,父进程再读取。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/wait.h>

int main(int argc, char* argv[])
{
	int fd1, fd2;
	pid_t pid;
	char buf[1024];
	char* str = "------test for shared fd in parent child process------\n";

	pid = fork();
	if (pid < 0) {
		perror("fork error");
		exit(1);
	}
	else if (pid == 0) {
		fd1 = open("test.txt", O_RDWR);		//子进程打开文件
		if (fd1 < 0) {
			perror("open error");
			exit(1);
		}
		write(fd1, str, strlen(str));		//strlen不包含\0
		printf("child wrote over..\n");
	}
	else {
		fd2 = open("test.txt", O_RDWR);
		if (fd2 < 0) {
			perror("open error");
			exit(1);
		}

		sleep(1);					//保证子进程写入数据
		int len = read(fd2, buf, sizeof(buf));		//sizeof包含\0
		write(STDOUT_FILENO, buf, len);

		wait(NULL);
	}

	return 0;
}

在这里插入图片描述

mmap建立映射区

存储映射 I/O:使一个磁盘文件映射到内存中,此时可以利用指针进行地址访问,利用库函数(strcpy、printf)进行操作,借助文件创建共享映射内存
创建共享内存映射:
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,可以返回任意类型的指针

释放映射区:
int munmap(void *addr, size_t length);
参数:addr:mmap的返回值;length:大小

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>

void sys_err(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, 0664);
	if (fd == -1)
		sys_err("open error");
	文件大小为0不可操作
	//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");
	}

	return 0;
}

在这里插入图片描述

mmap进程间通信

1、父进程先创建映射区。open(O_RDWR) mmap(MAP_SHARE)
2、指定 MAP_SHARED权限
3、fork()创建子进程
4、一个进程读,另一个进程写。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/mman.h>

int var = 100;		//全局变量

int main(int argc, char* argv[])
{
	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);
	}
	unlink("temp");				//删除临时文件目录项,使之具备被释放条件
	ftruncate(fd, 4);

	p = (int*)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED, 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;
}

在这里插入图片描述

信号

信号共性:简单、不能携带大量信息、满足条件才发送。
信号的特质:
1、信号是软件层面上的“中断”。 一旦信号产生,无论程序执行到什么位置,必须立即停止运行,处理信号结束后,再继续执行后续指令。
2、每个进程收到的所有信号,都是由内核负责发送的,内核处理。
3、所有信号的产生及处理全部都是由内核完成的。
信号相关的概念:
未决:产生与递达之间状态。
递达:产生并且送达到进程。 直接被内核处理掉。
信号处理方式:1、执行默认处理动作。2、忽略。3、捕捉(自定义)。
阻塞信号集(信号屏蔽字):本质:位图,用来记录信号的屏蔽状态,默认为0。一旦被屏蔽的信号,信号无法递达,不能被处理,在解除屏蔽前,一直处于未决态,通过操作阻塞信号集来影响未决信号集。
未决信号集:本质:位图,用来记录信号的处理状态,默认为0。该信号集中的信号,表示已经产生,但尚未被处理。
信号的产生:
1、按键产生 Ctrl+c
2、系统调用产生 Kill、raise、abort
3、软件条件产生 定时器alarm
4、硬件异常产生 非法访问内存(段错误)、除0(浮点数除外)、内存对齐出错(总线错误)。
5、命令产生 kill命令
信号的四要素:信号使用前,应先确定其四要素,而后再使用。编号、名称、信号对应事件、信号默认处理动作
查看信号及对应编号。

kill -l

信号集操作函数

设置信号屏蔽字和解除屏蔽函数:
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
how:1、SIG_BLOCK 设置阻塞;2、SIG_UNBLOCK 取消阻塞;3、SIG_SETMASK 用自定义set替换mask,一般不推荐。
set:自定义set集合
oldset:旧有的mask

sigpending(sigset_t *set):只能读取未决信号集,set传出参数。
利用Ctrl+c产生信号,一开始设置信号集操作函数。当未触发信号时,未决信号集全为0,触发信号按下Ctrl+c以后,未决信号集的2号虽然置为1,但在信号屏蔽集中设置阻塞屏蔽状态,导致其无法执行,所呈现的结果是信号无法被执行,即无法利用Ctrl+c中断程序。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <signal.h>

void sys_err(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);				//清空信号集置为0
	sigaddset(&set, SIGINT);		//Ctrl + C 信号添加到集合中
	
	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;
}

结果如下:在这里插入图片描述

sigaction实现函数捕捉

内核实现信号捕捉过程
1、在执行主控制流程的某条指令时因为中断、异常或系统调用进入内核
int main() {}
2、内核处理完异常准备回用户模式之前先处理当前进程中可以递送的信号
3、如果信号的处理动作自定义的信号处理函数则回到用户模式执行信号处理函数(而不是回到主控制流程)
4、信号处理函数返回时执行特殊的系统调用sigreturn再次进内核
5、sys_sigreturn()返回用户模式从主控制流程中上次被中断的地方继续向下执行
void sighandler(int){]
signal():注册一个信号捕捉函数,真正抓信号的是内核。
sigaction():注册一个信号捕捉函数
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <signal.h>

void sys_err(char* str)
{
	perror(str);
	exit(-1);
}

void sig_catch(int signo)			//回调函数 内核是调用者
{
	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));		//设置屏蔽字,只在sig_catch工作时有效
	act.sa_flags = 0;					//设置默认属性

	int ret = sigaction(SIGINT, &act, &oldact);		//注册信号捕捉函数
	if (ret == -1) {
		sys_err("sigaction error");
	}

	while (1);

	return 0;
}

借助信号捕捉回收子进程

sigchld信号:只要子进程的状态发生变化,就会马上产生此信号
产生的条件:
1、子进程终止时
2、子进程接收到SIGSTOP 信号停止时
3、子进程处在停止态,接受到SIGCONT后唤醒时

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>

void sys_err(char* str)
{
	perror(str);
	exit(-1);
}

void catch_child(int signo)		//有子进程终止,发生SIGCHLD信号时,该函数会被内核回调
{
	int status;
	pid_t wpid;
	//while (wpid = wait(NULL) != -1) {		//while一次回调回收多个死亡的子进程
	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 < 5; i++)
		if ((pid = fork()) == 0)				//创建多个子进程
			break;

	if (5 == i) {
		struct sigaction act;

		act.sa_handler = catch_child;			//设置捕捉回调函数	
		sigemptyset(&act.sa_mask);				//设置屏蔽字,只在sig_catch工作时有效
		act.sa_flags = 0;						//设置默认属性
		
		sigaction(SIGCHLD, &act, NULL);			//注册信号捕捉函数

		//解除阻塞
		sigprocmask(SIG_UNBLOCK, &set, NULL);

		printf("I'm parent, pid = %d\n", getpid());

		while (1);								//模拟父进程后续逻辑
	}
	else {
		//sleep(1);								让其先睡会,父进程要注册捕捉函数
		printf("I'm child, pid = %d\n", getpid());
		return i;
	}

	return 0;
}

守护进程创建

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>

void sys_err(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/dengzj/28_Linux");		//改变工作位置
	if (ret == -1)
		sys_err("chdir error");

	umask(0022);			//改变文件访问权限掩码

	close(STDIN_FILENO);	//关闭文件描述符 0 
	fd = open("/dev/null", O_RDWR);
	if(fd == -1)
		sys_err("open error");

	dup2(fd, STDOUT_FILENO);
	dup2(fd, STDERR_FILENO);

	while (1);				//模拟 守护进程

	return 0;
}

在这里插入图片描述
运行命令ps aux 查看进程,其进程ID号为32950
在这里插入图片描述
运行ps ajx 查看会话,结果是进程ID=进程组ID=会话ID
在这里插入图片描述
退出登陆后,重新启动后,守护进程仍然存在
在这里插入图片描述
最后想要退出此程序,需要kill命令杀死守护进程。

kill -9 32896

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值