Linux系统编程Day07

1.匿名映射实现父子进程通信

	通过使用我们发现,使用映射区来完成文件读写操作十分方便,父子进程间通信也
较容易。但缺陷是,每次创建映射区一定要依赖一个文件才能实现。

	通常为了建立映射区要open一个temp文件,创建好了再unlink、close掉,比较
麻烦。 可以直接使用匿名映射来代替。

	其实Linux系统给我们提供了创建匿名映射区的方法,无需依赖一个文件即可创建
映射区。同样需要借助标志位参数flags来指定。

	使用MAP_ANONYMOUS (或MAP_ANON)。
	int *p = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
		1.4"随意举例,该位置表示映射区大小,可依实际需要填写。
		2.MAP_ANONYMOUS和MAP_ANON这两个宏是Linux操作系统特有的宏。在类
		Unix系统中如无该宏定义,可使用如下两步来完成匿名映射区的建立。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<errno.h>


//父子进程使用匿名映射进行进程间通信
int main()
{
        pid_t pid = -1;
        void* addr = NULL;
        //1.创建匿名映射
        addr = mmap(NULL,4096,PROT_READ |PROT_WRITE, MAP_SHARED| MAP_ANONYMOUS,-1, 0);
        if(addr == MAP_FAILED)
        {
                perror("mmap");
                return 1;
        }

        //2.创建子进程
        pid = fork();
        if(-1 == pid)
        {
                perror("fork");
                munmap(addr,4096);
                return 1;
        }
        //3.父子进程通信
        //子进程
        if(0 == pid)
        {
                memcpy(addr, "123456789", 9);
        }
        else
        {
                //父进程
                wait(NULL);
                printf("父进程读取:%s\n",(char* )addr);
        }

        //4. 断开映射
        munmap(addr,4096);

        return 0;
}

2.信号的概述

2.1 信号的概念
	信号是 Linux 进程间通信的最古老的方式。信号是软件中断,它是在软件层次上对
中断机制的一种模拟,是一种异步通信的方式 。信号可以导致一个正在运行的进程被另
一个正在运行的异步进程中断,转而处理某一个突发事件。

2.2 信号的特点
	1.简单
	2.不能携带大量信息
	3.满足某个特设条件才发送

	信号可以直接进行用户空间进程和内核空间进程的交互,内核进程可以利用它来通知
用户空间进程发生了哪些系统事件。	
	一个完整的信号周期包括三个部分:信号的产生,信号在进程中的注册,信号在进程
中的注销,执行信号处理函数。
	注意:这里信号的产生,注册,注销时信号的内部机制,而不是信号的函数实现。

2.3 信号四要素
	每个信号必备4要素,分别是:
		编号、名称、事件、默认处理动作

	可通过man 7 signal查看帮助文档获取
	其中,在标准信号中,有一些信号是有三个“Value”,第一个值通常对alpha和sparc架构有效,中间值针对x86、arm和其他架构,最后一个应用于mips架构。一个‘-’表示在对应架构上尚未定义该信号。
	
	Action为默认动作:
		Term:终止进程
		Ign: 忽略信号 (默认即时对该种信号忽略操作)
		Core:终止进程,生成Core文件。(查验死亡原因,用于gdb调试)
		Stop:停止(暂停)进程
		Cont:继续运行进程

	注: SIGKILL 和SIGSTOP信号,不允许忽略和捕捉,只能执行默认动作。甚至不能
将其设置为阻塞。
	另外需清楚,只有每个信号所对应的事件发生了,该信号才会被递送(但不一定递达)

2.4 信号的状态
	1) 产生
		a) 当用户按某些终端键时,将产生信号。
			终端上按“Ctrl+c”组合键通常产生中断信号 SIGINT
			终端上按“Ctrl+\”键通常产生中断信号 SIGQUIT		
			终端上按“Ctrl+z”键通常产生中断信号 SIGSTOP 等。
			
		b) 硬件异常将产生信号。		
			除数为 0,无效的内存访问等。这些情况通常由硬件检测到,并通知内核,然后
		内核产生适当的信号发送给相应的进程。
		
		c) 软件异常将产生信号。		
		当检测到某种软件条件已发生(如:定时器alarm),并将其通知有关进程时,产生信号。
		
		d) 调用系统函数(如:kill、raise、abort)将发送信号。		
		注意:接收信号进程和发送信号进程的所有者必须相同,或发送信号进程的所有者
		必须是超级用户。
		
		e) 运行 kill /killall命令将发送信号。		
		此程序实际上是使用 kill 函数来发送信号。也常用此命令终止一个失控的后台进程。
					
	2) 未决状态:没有被处理
	
	3) 递达状态:信号被处理了

2.5 阻塞信号集和未决信号集
	信号的实现手段导致信号有很强的延时性,但对于用户来说,时间非常短,不易察觉。
	Linux内核的进程控制块PCB是一个结构体,task_struct, 除了包含进程id,状态,
工作目录,用户id,组id,文件描述符表,还包含了信号相关的信息,主要指阻塞信号
集和未决信号集。

1 阻塞信号集(信号屏蔽字)	
	将某些信号加入集合,对他们设置屏蔽,当屏蔽x信号后,再收到该信号,该信号的
处理将推后(处理发生在解除屏蔽后)。

2 未决信号集	
	信号产生,未决信号集中描述该信号的位立刻翻转为1,表示信号处于未决状态。当
信号被处理对应位翻转回为0。这一时刻往往非常短暂。	
	信号产生后由于某些原因(主要是阻塞)不能抵达。这类信号的集合称之为未决信号
集。在屏蔽解除前,信号一直处于未决状态。

3.信号产生函数

3.1 kill函数
#include <sys/types.h>
#include <signal.h>
​
int kill(pid_t pid, int sig);
功能:给指定进程发送指定信号(不一定杀死)
​
参数:
    pid : 取值有 4 种情况 :
        pid > 0:  将信号传送给进程 ID 为pid的进程。
        pid = 0 :  将信号传送给当前进程所在进程组中的所有进程。
        pid = -1 : 将信号传送给系统内所有的进程。
        pid < -1 : 将信号传给指定进程组的所有进程。这个进程组号等于 pid 的绝对值。
    sig : 信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill - l("l" 为字母)进行相应查看。不推荐直接使用数字,应使用宏名,因为不同操作系统信号编号可能不同,但名称一致。
​
返回值:
    成功:0
    失败:-1
    
    注:
	1.super用户(root)可以发送信号给任意用户,普通用户是不能向系统用户发送信号的。	
	2.kill -9 (root用户的pid) 是不可以的。同样,普通用户也不能向其他普通用户
	发送信号,终止其进程。 只能向自己创建的进程发送信号。	
	3.普通用户基本规则是:发送者实际或有效用户ID == 接收者实际或有效用户ID
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
#include<sys/types.h>
#include<signal.h>
#include<unistd.h>


int main()
{
	pid_t pid = -1;

	//1.创建一个子进程
	pid = fork();
	if(-1 == pid)
	{
		perror("fork");
		return 1;
	}

	//2.进入进程
	if(0 == pid)
	{
		//子进程
		while(1)
		{
			printf("子进程一直在工作。。\n");
			sleep(1);
		}

		exit(0);
	}
	else
	{
		//父进程
		sleep(3);

		printf("子进程工作完毕!\n");
		kill(pid,15);
		printf("子进程已经退出!\n");
	}

	return 0;
}

3.2 raise函数
	#include <signal.h>
	​
	int raise(int sig);
	功能:给当前进程发送指定信号(自己给自己发),等价于 kill(getpid(), sig)
	参数:
	    sig:信号编号
	返回值:
	    成功:0
	    失败:非0值
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<signal.h>

int main()
{
	int i = 0;
	while(1)
	{
		printf("%d 该进程正在工作!\n", i);
		if(i == 4)
		{
			//等价于以下信号
			//kill(getpid(),SIGTERM);
			raise(SIGTERM);
		}

		++i;
		sleep(1);
	}


	return 0;
}
3.3 abort函数
	#include <stdlib.h>
	​
	void abort(void);
	功能:给自己发送异常终止信号 6) SIGABRT,并产生core文件,等价于kill(getpid(), SIGABRT);		​
	参数:无
	返回值:无
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
#include<unistd.h>


int main()
{
	int i = 1;

	while(1)
	{
		printf("%d 正在工作\n",i);
		if(i == 4)
		{
			//给自己发生一个编号为6的信号,默认的行为就是终止一个进程
			abort();
		}

		i++;

		sleep(1);
	}
	return 0;
}
3.4 alarm函数(闹钟)
#include <unistd.h>
​
unsigned int alarm(unsigned int seconds);
功能:
    设置定时器(闹钟)。在指定seconds后,内核会给当前进程发送14)SIGALRM信号。进程收到该信号,默认动作终止。每个进程都有且只有唯一的一个定时器。
    取消定时器alarm(0),返回旧闹钟余下秒数。
参数:
    seconds:指定的时间,以秒为单位
返回值:
    返回0或剩余的秒数
​
	定时,与进程状态无关(自然定时法)!就绪、运行、挂起(阻塞、暂停)、终止、僵
尸等,无论进程处于何种状态,alarm都计时。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>

int main()
{
	unsigned int ret = 0;

	//第一次设置闹钟
	ret = alarm(5);
	printf("上一次闹钟剩余的时间:%u\n", ret);
	sleep(3);

	ret = alarm(3);
	//之前没有超时的闹钟被新的设备给覆盖掉
	printf("上一次闹钟剩下的时间是:%u\n", ret);


	printf("按下任意键继续。。\n");
	getchar();

	return 0;
}

3.5 setitimer函数(定时器)
#include <sys/time.h>
​
int setitimer(int which,  const struct itimerval *new_value, struct itimerval *old_value);
功能:
    设置定时器(闹钟)。 可代替alarm函数。精度微秒us,可以实现周期定时。
参数:
    which:指定定时方式
        a) 自然定时:ITIMER_REAL → 14)SIGALRM计算自然时间
        b) 虚拟空间计时(用户空间):ITIMER_VIRTUAL → 26)SIGVTALRM  只计算进程占用cpu的时间
        c) 运行时计时(用户 + 内核):ITIMER_PROF → 27)SIGPROF计算占用cpu及执行系统调用的时间
    new_value:struct itimerval, 负责设定timeout时间
        struct itimerval {
            struct timerval it_interval; // 闹钟触发周期
            struct timerval it_value;    // 闹钟触发时间
        };
        struct timeval {
            long tv_sec;            // 秒
            long tv_usec;           // 微秒
        }
        itimerval.it_value: 设定第一次执行function所延迟的秒数 
        itimerval.it_interval:  设定以后每几秒执行function
​
    old_value: 存放旧的timeout值,一般指定为NULL
返回值:
    成功:0
    失败:-1
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/time.h>
#include<unistd.h>
#include<signal.h>


//信号处理函数
void fun(int signo)
{
	printf("捕捉到信号:%d\n",signo);
}


int main()
{
	int ret = -1;
	struct itimerval tmo;

	//第一次触发时间
	tmo.it_value.tv_sec = 3;
	tmo.it_value.tv_usec = 0;

	//触发周期
	tmo.it_interval.tv_sec = 2;
	tmo.it_interval.tv_usec = 0;

	//捕捉信号 SIGALRM
	signal(SIGALRM, fun);


	//设置定时器
	ret = setitimer(ITIMER_REAL, &tmo, NULL);
	if(-1 == ret)
	{
		perror("setitimer");
		return 1;
	}


	//进程收到闹钟超时信号之后就会终止该进程
	printf("按下任意键继续\n");
	getchar();

	return 0;
}

4.信号集

4.1 信号集概述
	在PCB中有两个非常重要的信号集。一个称之为“阻塞信号集”,另一个称之为“未决
信号集”。
	这两个信号集都是内核使用位图机制来实现的。但操作系统不允许我们直接对其进
行位操作。而需自定义另外一个集合,借助信号集操作函数来对PCB中的这两个信号
集进行修改

4.2 自定义信号集函数
	为了方便对多个信号进行处理,一个用户进程常常需要对多个信号做出处理,在 Linux
 系统中引入了信号集(信号的集合)。	
	信号集是一个能表示多个信号的数据类型,sigset_t set,set即一个信号集。
既然是一个集合,就需要对集合进行添加/删除等操作。

相关函数说明如下:
	#include <signal.h>  
	​
	int sigemptyset(sigset_t *set);       //将set集合置空
	int sigfillset(sigset_t *set);          //将所有信号加入set集合
	int sigaddset(sigset_t *set, int signo);  //将signo信号加入到set集合
	int sigdelset(sigset_t *set, int signo);   //从set集合中移除signo信号
	int sigismember(const sigset_t *set, int signo); //判断信号是否存在
	除sigismember外,其余操作函数中的set均为传出参数。sigset_t类型的本质
是位图。但不应该直接使用位操作,而应该使用上述函数,保证跨系统操作有效。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>
#include<unistd.h>

void show_set(sigset_t *s)
{

	for(int i = 1; i < 32; i++)
	{
		if(sigismember(s, i))
		{
			printf("1");
		}
		else
		{
			printf("0");
		}
	}
	putchar('\n');
}

//信号集处理函数
int main()
{
	int i = 0;

	//信号集集合
	sigset_t set;

	//清空集合
	sigemptyset(&set);

	show_set(&set);

	//将所有信号加入到set集合中
	sigfillset(&set);
	show_set(&set);

	//将信号2和3从信号集中移除
	sigdelset(&set, SIGINT);
	sigdelset(&set, SIGQUIT);

	show_set(&set);

	//将信号2添加到集合中
	sigaddset(&set, SIGINT);
	show_set(&set);

	return 0;
}

4.3 sigprocmask函数
	信号阻塞集也称信号屏蔽集、信号掩码。每个进程都有一个阻塞集,创建子进程时
子进程将继承父进程的阻塞集。信号阻塞集用来描述哪些信号递送到该进程的时候被阻塞
(在信号发生时记住它,直到进程准备好时再将信号通知进程)。	
	所谓阻塞并不是禁止传送信号, 而是暂缓信号的传送。若将被阻塞的信号从信号阻
塞集中删除,且对应的信号在被阻塞时发生了,进程将会收到相应的信号。	
	我们可以通过 sigprocmask() 修改当前的信号掩码来改变信号的阻塞情况。

	#include <signal.h>
	​
	int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
	功能:
	    检查或修改信号阻塞集,根据 how 指定的方法对进程的阻塞集合进行修改,新的信号阻塞集由 set 指定,而原先的信号阻塞集合由 oldset 保存。
	​
	参数:
	    how : 信号阻塞集合的修改方法,有 3 种情况:
	        SIG_BLOCK:向信号阻塞集合中添加 set 信号集,新的信号掩码是set和旧信号掩码的并集。相当于 mask = mask|set。
	        SIG_UNBLOCK:从信号阻塞集合中删除 set 信号集,从当前信号掩码中去除 set 中的信号。相当于 mask = mask & ~ set。
	        SIG_SETMASK:将信号阻塞集合设为 set 信号集,相当于原来信号阻塞集的内容清空,然后按照 set 中的信号重新设置信号阻塞集。相当于mask = set。
	    set : 要操作的信号集地址。
	        若 set 为 NULL,则不改变信号阻塞集合,函数只把当前信号阻塞集合保存到 oldset 中。
	    oldset : 保存原先信号阻塞集地址
	​
	返回值:
	    成功:0,
	    失败:-1,失败时错误代码只可能是 EINVAL,表示参数 how 不合法。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>
#include<unistd.h>

//信号处理函数1
void fun1(int signum)
{
	printf("捕捉到信号:%d\n",signum);
}

//信号处理函数2
void fun2(int signum)
{
	printf("捕捉到信号:%d\n",signum);
}

//信号注册函数
int main()
{
	int ret = -1;

	//信号集
	sigset_t set;
	sigset_t old;


	//Ctrl + C
	signal(SIGINT, fun1);

	//Ctrl + '\'
	signal(SIGQUIT, fun2);

	printf("按下任意键,阻塞信号2\n");
	getchar();

	sigemptyset(&old);
	sigemptyset(&set);
	sigaddset(&set, SIGINT);

	//设置屏蔽编号为2的信号
	ret = sigprocmask(SIG_BLOCK, &set, &old);
	if(-1 == ret)
	{
		perror("sigprocmask");
		return 1;
	}
	printf("设置屏蔽编号为2的信号成功\n");
	printf("按下任意键接触编号为2的信号的阻塞\n");
	getchar();

	//将信号屏蔽集设置为原来的集合
	ret = sigprocmask(SIG_SETMASK, &old, &set);
	if(-1 == ret)
	{
		perror("sigprocmask");
		return 1;
	}
	printf("按下任意键退出\n");
	getchar();

	while(1)
	{
		sleep(1);
	}


	return 0;
}

4.4 sigpending函数
	#include <signal.h>
	​
	int sigpending(sigset_t *set);
	功能:读取当前进程的未决信号集
	参数:
	    set:未决信号集
	返回值:
	    成功:0
	    失败:-1
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>
#include<unistd.h>

//信号处理函数1
void fun1(int signum)
{
	printf("捕捉到信号:%d\n",signum);
}

//信号处理函数2
void fun2(int signum)
{
	printf("捕捉到信号:%d\n",signum);
}

//信号注册函数
int main()
{
	int ret = -1;
	int i = 0;

	//信号集
	sigset_t set;
	sigset_t old;


	//Ctrl + C
	signal(SIGINT, fun1);

	//Ctrl + '\'
	signal(SIGQUIT, fun2);

	printf("按下任意键,阻塞信号2\n");
	getchar();

	sigemptyset(&old);
	sigemptyset(&set);
	sigaddset(&set, SIGINT);

	//设置屏蔽编号为2的信号
	ret = sigprocmask(SIG_BLOCK, &set, &old);
	if(-1 == ret)
	{
		perror("sigprocmask");
		return 1;
	}
	printf("设置屏蔽编号为2的信号成功\n");

	printf("按下回车键来获取未决的信号\n");
	getchar();
	//获取未决的信号
	sigemptyset(&set);
	ret = sigpending(&set);
	if(-1 == ret)
	{
		perror("sigpending");
		return 1;
	}
	printf("处于未决的信号有:");
	//输出未决的信号
	for(i = 1; i < 32; i++)
	{
		if(sigismember(&set,i))
		{
			printf("1");
		}
	}
	putchar('\n');

	printf("按下任意键接触编号为2的信号的阻塞\n");
	getchar();

	//将信号屏蔽集设置为原来的集合
	ret = sigprocmask(SIG_SETMASK, &old, &set);
	if(-1 == ret)
	{
		perror("sigprocmask");
		return 1;
	}
	printf("按下任意键退出\n");
	getchar();

	while(1)
	{
		sleep(1);
	}


	return 0;
}

5 信号捕捉

5.1信号处理方式
	一个进程收到一个信号的时候,可以用如下方法进行处理:

	1)执行系统默认动作		
		对大多数信号来说,系统默认动作是用来终止该进程。		
	2)忽略此信号(丢弃)		
		接收到此信号后没有任何动作。		
	3)执行自定义信号处理函数(捕获)
		用用户定义的信号处理函数处理该信号。
	
	注:SIGKILL 和 SIGSTOP 不能更改信号的处理方式,因为它们向用户提供了
一种使进程终止的可靠方法。

5.2 signal函数
	#include <signal.h>
​
	typedef void(*sighandler_t)(int);
	sighandler_t signal(int signum, sighandler_t handler);
	功能:
	    注册信号处理函数(不可用于 SIGKILL、SIGSTOP 信号),即确定收到信
	   号后处理函数的入口地址。此函数不会阻塞。
	​
	参数:
	    signum:信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以
	    通过命令 kill - l("l" 为字母)进行相应查看。
	    handler : 取值有 3 种情况:
	          SIG_IGN:忽略该信号
	          SIG_DFL:执行系统默认动作
	          信号处理函数名:自定义信号处理函数,如:func
	          回调函数的定义如下:
	            void func(int signo)
	            {
	                // signo 为触发的信号,为 signal() 第一个参数的值
	            }
	​
	返回值:
	    成功:第一次返回 NULL,下一次返回此信号上一次注册的信号处理函数的地址。
	    如果需要使用此返回值,必须在前面先声明此函数指针的类型。
	    失败:返回 SIG_ERR
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>
#include<unistd.h>

//信号处理函数1
void fun1(int signum)
{
	printf("捕捉到信号:%d\n",signum);
}

//信号处理函数2
void fun2(int signum)
{
	printf("捕捉到信号:%d\n",signum);
}

//信号注册函数
int main()
{

	//Ctrl + C
	signal(SIGINT, fun1);

	//Ctrl + '\'
	signal(SIGQUIT, fun2);
	
	while(1)
	{
		sleep(1);
	}


	return 0;
}

5.3 sigaction函数
	#include <signal.h>
	​
	int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
	功能:
	    检查或修改指定信号的设置(或同时执行这两种操作)。
	​
	参数:
	    signum:要操作的信号。
	    act:   要设置的对信号的新处理方式(传入参数)。
	    oldact:原来对信号的处理方式(传出参数)。
	​
	    如果 act 指针非空,则要改变指定信号的处理方式(设置),如果 oldact 指针非空,则系统将此前指定信号的处理方式存入 oldact。
	​
	返回值:
	    成功:0
	    失败:-1

	struct sigaction结构体:

struct sigaction {
    void(*sa_handler)(int); //旧的信号处理函数指针
    void(*sa_sigaction)(int, siginfo_t *, void *); //新的信号处理函数指针
    sigset_t   sa_mask;      //信号阻塞集
    int        sa_flags;     //信号处理的方式
    void(*sa_restorer)(void); //已弃用
};
1) sa_handler、sa_sigaction:信号处理函数指针,和 signal() 里的函数指针
用法一样,应根据情况给sa_sigaction、sa_handler 两者之一赋值,其取值如下:	
	a) SIG_IGN:忽略该信号		
	b) SIG_DFL:执行系统默认动作		
	c) 处理函数名:自定义信号处理函数

2) sa_mask:信号阻塞集,在信号处理函数执行过程中,临时屏蔽指定的信号。

3) sa_flags:用于指定信号处理的行为,通常设置为0,表使用默认属性。它可以是一下值的“按位或”组合:	
	SA_RESTART:使被信号打断的系统调用自动重新发起(已经废弃)	
	SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 
	信号。	
	SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时
	子进程如果退出也不会成为僵尸进程。	
	SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个
	信号。	
	SA_RESETHAND:信号处理之后重新设置为默认的处理方式。	
	SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数。

信号处理函数:
	void(*sa_sigaction)(int signum, siginfo_t *info, void *context);
	参数说明:
	    signum:信号的编号。
	    info:记录信号发送进程信息的结构体。
	    context:可以赋给指向 ucontext_t 类型的一个对象的指针,以引用在
	    传递信号时被中断的接收进程或线程的上下文。		
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>

//信号处理函数
void fun(int signo)
{
	printf("捕捉到信号:%d\n", signo);
}


//演示sigaction函数使用
int main()
{
	int ret = -1;
	struct sigaction act;

	//使用旧的信号处理函数指针
	act.sa_handler = fun;
	//标志为默认 默认使用旧的信号处理函数指针
	act.sa_flags = 0;

	//信号注册
	ret = sigaction(SIGINT, &act, NULL);
	if(-1 == ret)
	{
		perror("sigaction");
		return 1;
	}

	printf("按下任意键退出。。\n");
	while(1)
	{
		getchar();
	}

	return 0;
}

5.4 sigqueue 函数		
	#include <signal.h>
	​
	int sigqueue(pid_t pid, int sig, const union sigval value);
	功能:
	    给指定进程发送信号。
	参数:
	    pid : 进程号。
	    sig : 信号的编号。
	    value : 通过信号传递的参数。
	        union sigval 类型如下:
	            union sigval
	            {
	                int   sival_int;
	                void *sival_ptr;
	            };
	返回值:
	    成功:0
	    失败:-1
	​向指定进程发送指定信号的同时,携带数据。但如传地址,需注意,不同进程之间
虚拟地址空间各自独立,将当前进程地址传递给另一进程没有实际意义。
	
5.5 不可重入、可重入函数
	满足下列条件的函数多数是不可重入(不安全)的:
		函数体内使用了静态的数据结构;
		函数体内调用了malloc() 或者 free() 函数(谨慎使用堆);
		函数体内调用了标准 I/O 函数。

	相反,肯定有一个安全的函数,这个安全的函数又叫可重入函数。所谓可重入是指
一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会出错。

	保证函数的可重入性的方法:
		1.在写函数时候尽量使用局部变量(例如寄存器、栈中的变量);
		2.对于要使用的全局变量要加以保护(如采取关中断、信号量等互斥方法),
		这样构成的函数就一定是一个可重入的函数。

6. SIGCHLD信号

6.1 SIGCHLD信号产生的条件
	1) 子进程终止时		
	2) 子进程接收到SIGSTOP信号停止时		
	3) 子进程处在停止态,接受到SIGCONT后唤醒时

6.2 如何避免僵尸进程
	1) 最简单的方法,父进程通过 wait() 和 waitpid() 等函数等待子进程结束,
但是,这会导致父进程挂起。	
	2) 如果父进程要处理的事情很多,不能够挂起,通过 signal() 函数人为处理信号
SIGCHLD ,只要有子进程退出自动调用指定好的回调函数,因为子进程结束后,父进程
会收到该信号 SIGCHLD ,可以在其回调函数里调用 wait() 或 waitpid() 回收。
void sig_child(int signo)
{
    pid_t  pid;
​
    //处理僵尸进程, -1 代表等待任意一个子进程, WNOHANG代表不阻塞
    while ((pid = waitpid(-1, NULL, WNOHANG)) > 0)
    {
        printf("child %d terminated.\n", pid);
    }
}
​
int main()
{
    pid_t pid;
​
    // 创建捕捉子进程退出信号
    // 只要子进程退出,触发SIGCHLD,自动调用sig_child()
    signal(SIGCHLD, sig_child);
​
    pid = fork();   // 创建进程
    if (pid < 0)
    { // 出错
        perror("fork error:");
        exit(1);
    }
    else if (pid == 0)
    { // 子进程
        printf("I am child process,pid id %d.I am exiting.\n", getpid());
        exit(0);
    }
    else if (pid > 0)
    { // 父进程
        sleep(2);   // 保证子进程先运行
        printf("I am father, i am exited\n\n");
        system("ps -ef | grep defunct"); // 查看有没有僵尸进程
    }
​
    return 0;
}
​
	3)如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCHLD, SIG_IGN)
通知内核,自己对子进程的结束不感兴趣,父进程忽略此信号,那么子进程结束后,内
核会回收, 并不再给父进程发送信号。
int main()
{
    pid_t pid;
​
    // 忽略子进程退出信号的信号
    // 那么子进程结束后,内核会回收, 并不再给父进程发送信号
    signal(SIGCHLD, SIG_IGN);
​
    pid = fork();   // 创建进程
​
    if (pid < 0)
    { // 出错
        perror("fork error:");
        exit(1);
    }
    else if (pid == 0)
    { // 子进程
        printf("I am child process,pid id %d.I am exiting.\n", getpid());
        exit(0);
​
    }
    else if (pid > 0)
    { // 父进程
        sleep(2);   // 保证子进程先运行
        printf("I am father, i am exited\n\n");
        system("ps -ef | grep defunct"); // 查看有没有僵尸进程
    }
​
    return 0;
}
上一篇:Day07_会话技术


下一篇:WEB后端_Day07(订单模块、Filter、ThreadLocal、Ajax)