Linux下的进程demo(二)---信号与信号集

3 篇文章 0 订阅
2 篇文章 0 订阅

三、信号

1、进程间通信
进程间通信(IPC)
	进程是一个独立的资源分配单元,不同进程没有关联,不能在一个进程中直接访问另一个进程的资源;
	但是为了满足某些业务需求,不同的进程需要进行信息的交互和状态的传递等,因此需要IPC
IPC功能包括:
数据传输:一个进程需要将它的数据发送给另一个进程
资源共享:多个进程之间共享同样的资源
通知事件:一个进程需要向另一个或一组进程发送消息,通知他们发生了某种事件。
进程控制:有些进程希望完全控制另一个进程的执行(如gdb),此时控制进程希望能够拦截另一个进程的所有操作,以便及时获取其状态的改变

Linux系统支持的IPC通信机制有哪些呢?
同一主机进程间通信:
	Unix进程间通信方式:无名管道、有名管道、信号
	System V 或 POSIX进程间通信方式:消息队列、共享内存、信号量
不同主机进程间通信:socket

进程间通信的实质:
	系统只要创建一个进程,就会给当前进程分配4G的虚拟内存(32位操作系统),虚拟内存不是日常生活所说的内存条,内存条的空间叫物理内存,虚拟内存和物理内存是通过映射关系进行对应的。
	4G的虚拟内存分为3G的用户空间和1G的内核空间,用户空间是进程所私有的,像栈区、堆区、数据区、代码区等都属于用户空间
	内核空间是所有进程所公有的,这说明绝大多数进程间的通信方式,本质上是对内核空间进行操作
2、信号通信
	信号是软件中断,它相当于硬件的中断机制在软件上的模拟实现
	信号可以让一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。
	信号是一种异步通信方式
		进程不必等待信号的到达,也不知道信号什么时候来,信号可以直接进行用户空间进程和内核空间进程的交互,
		内核进程可以利用它来通知用户空间进程发生了哪些系统事件
	每个信号的名字都以字符串SIG开头,在头文件signum.h中,可以通过kill -l命令查看 
	信号是由当前系统已经定义好的一些标识,每一个标识都会在特定的场合使用
	并且都会对进程有一定的影响,当信号产生时,会让当前信号做出相应的操作
	这些信号都是已经定义好的,我们不能自己再去创造,直接使用这些就可以

那么这些信号是如何产生的呢?
	(1)当用户按某些终端键的时候,将产生信号。
		例如,Ctrl+c的组合键,会产生中断信号SIGINT;按Ctrl+\会产生SIGQUIT;Ctrl+z会产生SIGSTOP
	(2)硬件异常将产生信号
		除数为0,无效的内存访问等。这些情况通常由硬件检测到,并通知内核,然后内核产生适当的信号发送给相应进程。
	(3)软件异常将产生信号
		当检测到某种软件条件已发生,并将其通知有关进程时,会产生信号。
	(4)调用kill函数将发送信号
		注意:接收信号进程和发送信号进程的所有者必须相同,或发送信号进程的所有者必须是超级用户
	(5)运行kill命令将发送信号
		此程序实际上是使用kill函数来发送信号,也常用此命令终止一个失控的后台进程

当进程中产生了一个信号,就会让当前进程做出一定的反应,默认处理信号的方式有以下几种:
	(1)终止进程:当信号产生后,当前进程就会立即结束
	(2)缺省处理:当信号产生后,当前进程不做任何处理
	(3)停止进程:当信号产生后,使得当前进程停止
	(4)让停止的进程恢复运行:当信号产生后,停止进程继续执行,转为后台进程

一个进程收到一个信号的时候,可以用如下方法进行处理:
	(1)执行系统默认动作
	(2)忽略此信号
	(3)执行自定义信号处理函数
	注意:SIGKILL和SIGSTOP不能更改,只能以默认方式运行,不能忽略也不能自定义
	
常见的信号有哪些?
	SIGKILL 				9				当产生这个信号后,当前进程会退出,不能被缺省和捕捉
	SIGSTOP			19			当产生这个信号后,当前进程会暂停,不能被缺省和捕捉
	SIGINT				2				Ctrl+c产生的信号
	SIGQUIT				3				Ctrl+\产生的信号
	SIGTSTP				20			Ctrl+z产生的信号
	SIGCONT			18			当产生这个信号后,当前进程会恢复运行
	SIGALRM			14			调用alarm函数设置的时间到达后,会产生该信号,默认退出进程
	SIGPIPE				13			当管道破裂时,会产生当前信号,默认退出进程
	SIGABRT			6				当调用abort函数时会产生当前信号,默认退出进程
	SIGCHLD			17			当使用fork创建一个子进程时,子进程退出时产生该信号,默认缺省
	SIGUSR1			10			用户自定义信号,不会自动产生,只能使用kill发送,默认缺省
	SIGUSR2			12			用户自定义信号,不会自动产生,只能使用kill发送,默认缺省



信号的基本操作
	kill命令
		kill -9 3053   在终端输入该命令表示发送SIGKILL给3053号进程
	kill函数
		#include <sys/types.h>
   		#include <signal.h>
   		int kill(pid_t pid, int sig);
   		功能:给指定进程发送信号
   		返回值:成功返回0,失败返回-1
   		参数:
   			pid>0:将信号传送给进程ID为pid的进程
   			pid=0:将信号传送给当前进程所在进程组中的所有进程
   			pid=-1:将信号传送给系统内所有的进程(不常用)
   			pid<-1:将信号传给指定进程组的所有进程,这个进程组号等于pid的绝对值
   			sig:信号的编号
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
int main(int argc, char const *argv[])
{
    pid_t pid;
    pid = fork();
    if (pid < 0)
    {
        perror("fail to fork\n");
        exit(1);
    }
    else if (pid > 0) //父进程
    {
        printf("父进程开始运行\n");
        while (1)
        {
            printf("父进程运行中\n");
            sleep(1);
        }
        printf("父进程运行结束\n");
    }
    else //子进程
    {
        printf("子进程开始运行\n");
        sleep(3);
        printf("子进程挂起3s后\n");
        kill(getppid(), SIGKILL);
        printf("子进程运行结束\n");
    }
    return 0;
}
alarm函数
   #include <unistd.h>
   unsigned int alarm(unsigned int seconds);
   功能:在seconds秒后,向调用进程发送一个SIGALRM信号,SIGALRM信号的默认动作是终止调用alarm函数的进程
   返回值:若以前没有设置过定时器,或设置的定时器已超时,返回0;否则返回定时器剩余的秒数,并重新设定定时器
//验证代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
    unsigned int sec, cnt;
    cnt = 0;
    sec = alarm(5);
    printf("sec = %d\n", sec);
    sleep(3);
    sec = alarm(6);
    printf("sec = %d\n", sec);
    while (1)
    {
        cnt++;
        printf("after %d seconds \n", cnt);
        sleep(1);
    }
    return 0;
}

raise函数
   #include <signal.h>
   int raise(int sig);
   功能:给调用进程本身发送一个信号
   参数:sig:信号的编号
   返回值:成功返回0,失败返回-1
   raise(sig)等价于 kill(getpid(),sig);
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
int main(int argc, char const *argv[])
{
    //5s后结束本进程
    unsigned int sec = 0;
    while (1)
    {
        sleep(1);
        sec++;
        printf("after %d seconds\n", sec);
        if (sec == 5)
        {
            printf("The process exit\n");
            raise(SIGINT);
            //kill(getpid(), SIGINT);
            //abort();
        }
    }
    return 0;
}
abort函数
   #include <stdlib.h>
   void abort(void);
   功能:向进程发送一个SIGABRT信号,默认情况下进程会退出
   参数、返回值:无
   即使SIGABRT信号被加入阻塞集,一旦进程调用了abort函数,进程也还是会被终止,且在终止前会刷新缓冲区,关闭文件描述符
   案例:把abort();函数在上个案例打开


pause函数
	#include<unistd.h>
	int pause(void);
	功能:阻塞等待一个信号的产生
	返回值:当有信号产生时,函数返回-1
//pause函数案例
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
int main(int argc, char const *argv[])
{
    pid_t pid;
    pid = fork();
    if (pid < 0)
    {
        printf("fail to fork\n");
    }
    else if (pid > 0) //父进程
    {
        printf("parent process execute\n");
        pause();
    }
    else //子进程
    {
        printf("child process execute and sleep for 3sec \n");
        sleep(3);
        kill(getppid(), SIGINT);
    }
    return 0;
}
signal函数
	#include <signal.h>
    typedef void (*sighandler_t)(int);
    sighandler_t signal(int signum, sighandler_t handler);
    功能:
    	注册信号处理函数(不可用于SIGKILL、SIGSTOP信号),即确定收到信号后处理函数的入口地址
    参数:
    	signum:信号编号
    	handler的取值:
    		忽略该信号:SIG_IGN
    		执行系统默认动作:SIG_DFL
    		自定义信号处理函数:信号函数处理名
    返回值:
    	成功:返回函数地址,该地址为此信号上一次注册的信号处理函数的地址
    	失败:返回SIG_ERR
	进程接收到信号后的处理方式
		1、执行系统默认动作
		2、忽略此信号
		3、执行自定义信号处理函数
	程序中可用函数signal()改变信号的处理方式
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void handler(int sig)
{
    if (sig == SIGQUIT)
    {
        printf("SIGQUIT 信号处理函数执行\n");
    }
    if (sig == SIGTSTP)
    {
        printf("SIGTSTP 信号处理函数执行\n");
    }
}
int main(int argc, char const *argv[])
{
    int cnt = 0;
#if 0
    //以默认方式处理信号
    if (signal(SIGQUIT, SIG_DFL) == SIG_ERR)
    {
        perror("fail to signal to SIGQUIT\n");
        exit(1);
    }
    if (signal(SIGTSTP, SIG_DFL) == SIG_ERR)
    {
        perror("fail to signal to SIGTSTP\n");
        exit(1);
    }
#endif
#if 0
    //以忽略方式处理信号
    if (signal(SIGQUIT, SIG_IGN) == SIG_ERR)
    {
        perror("fail to signal\n");
        exit(1);
    }
    if (signal(SIGTSTP, SIG_IGN) == SIG_ERR)
    {
            perror("fail to signal\n");
        exit(1);
    }
#endif
#if 1
    //以自定义方式处理信号,注意传参不要传SIGKILL、SIGSTOP
    if (signal(SIGQUIT, handler) == SIG_ERR)
    {
        perror("fail to signal\n");
        exit(1);
    }
    if (signal(SIGTSTP, handler) == SIG_ERR)
    {
        perror("fail to signal\n");
        exit(1);
    }
#endif
    while (1)
    {

        cnt++;
        printf("cnt = %d\n", cnt);
        sleep(1);
    }
    return 0;
}
//signal函数返回值的使用案例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void *ret_handler;
void handler(int sig)
{
    printf("SIGINT 自定义handler函数执行\n");
    if (signal(SIGINT, ret_handler) == SIG_ERR)
    {
        perror("fail to signal\n");
        exit(1);
    }
    else
    {
        printf("signal 函数 SIGINT 自定义函数指定成功\n");
    }
}
int main(int argc, char const *argv[])
{
    int cnt = 0;
    if ((ret_handler = signal(SIGINT, handler)) == SIG_ERR)
    {
        perror("fail to signal");
        exit(1);
    }
    else
    {
        printf("SIGINT自定义函数指定成功\n");
    }
    while (1)
    {
        cnt++;
        printf("cnt = %d\n", cnt);
        sleep(1);
    }
    return 0;
}
3、可重入函数
可重入函数是指函数可以由多个任务并发使用,而不必担心数据错误

可重入函数就是可以被中断的函数,当前函数可以在任何时刻中断它,并执行另一块代码,
当执行完毕后,回到原本的代码还可以正常继续运行

编写可重入函数要注意:
	1、不使用静态的数据、全局变量(除非用信号量互斥)
	2、不调用动态内存分配、释放的函数
	3、不调用任何不可重 入的函数(如标准I/O函数)
另外,还要注意:
	即使信号处理函数使用的都是可重入函数(常见的可重入函数),也要注意进入处理函数时,
	首先要保存errno的值,结束时,再恢复原值。因为,信号处理过程中,errno值随时可能被改变
常见的可重入函数有:
	sleep、alarm、read等函数,使用案例如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void handler(int sig)
{
    printf("SIGINT 自定义函数handler 开始执行\n");
}
int main(int argc, char const *argv[])
{

    unsigned int cnt;
    if (signal(SIGINT, handler) == SIG_ERR)
    {
        perror("fail to signal");
        exit(1);
    }
#if 0 
    //sleep是一个可重入函数,但是当执行信号处理函数之后,不会回到原本的位置继续睡眠
    //sleep(8);
    //alarm函数也是可重入函数,执行完信号处理函数后,会继续回到原本的位置继续运行(Ctrl+c触发信号处理函数)
    //alarm(5);
#endif
#if 1
//read 也是一个可重入函数,在等待终端输入时,如果产生信号并执行信号处理函数,
//信号处理函数执行完毕后,可以继续输入数据,read可以读取到信号处理函数之后的数据
    char buf[15] = "";
    if (read(STDIN_FILENO, buf, 20) == -1)
    {
        perror("fail to read\n");
        exit(1);
    }
    printf("buf = [ %s ] ", buf);
    #endif
    while (1)
    {
        cnt++;
        printf("cnt = %d\n", cnt);
        sleep(1);
    }
    return 0;
}
4、信号集
信号集:一个用户进程常常需要对多个信号做出处理。为了方便对多个信号进行处理,在Linux系统中引入了信号集。信号集是用来表示多个信号的数据类型。
信号集数据类型:sigset_t,在sigset.h中可以找到
信号集的操作主要包括以下函数:
sigemptyset	
sigfillset
sigismember
sigaddset
sigdelset		
	sigemptyset函数:
		初始化一个空的信号集
		#include <signal.h>
		int sigemptyset(sigset_t *set);
		功能:初始化由set指向的信号集,清除其中所有的信号即初始化一个空信号集
		参数:set 信号集标识的地址,以后操作此信号集,对set进行操作就可以了
		返回值:成功返回0,失败返回-1
	sigfillset函数
		初始化一个满的信号集
		#include <signal.h>
		int sigfillset(sigset_t *set);
		功能:初始化信号集合set,将信号集合设置为所有信号的集合
		参数:信号集标识的地址,以后操作此信号集,对set进行操作就可以了
		返回值:成功返回0,失败返回-1
	sigismember函数
		判断某个集合中是否有某个信号
		#include <signal.h>
		int sigismember(const sigset_t *set,int signum);
		功能:查询signum标识的信号是否在信号集合set之中
		参数:set--信号集标识符号的地址;signum--信号的编号
		返回值:在信号集中返回1,不在信号集中返回0,失败返回-1
	sigaddset函数
		向某个集合中添加一个信号
		#include <signal.h>
		int sigaddset(sigset_t *set,int signum);
		功能:
			将信号signum加入到信号集合set之中
		参数:
			set:信号集标识的地址
			signum:信号的编号
		返回值:
			成功返回0,失败返回-1
	sigdelset函数
		从某个信号集中删除一个信号
		#include <signal.h>
		int sigdelset(sigset_t *set,int signum);
		功能:
			将signum所标识的信号从信号集合set中删除
		参数:
			set:信号集标识的地址
			signum:信号的编号
		返回值:
			成功返回0,失败返回-1
#include <stdio.h>
#include <signal.h>
int main(int argc, char const *argv[])
{
    //__sigset_t set;
    sigset_t set;
    int ret = 0;
	//初始化一个空的信号集
    //__sigemptyset(&set);
    sigemptyset(&set);
    //判断SIGINT是否在信号集中
    //ret = __sigismember(&set, SIGINT);
    ret = sigismember(&set, SIGINT);
    if (ret == 0)
    {
        printf("SIGINT is not a member of set\n");
    }
    //将SIGINT/SIGQUIT添加到信号集中
    //__sigaddset(&set, SIGINT);
    //__sigaddset(&set, SIGQUIT);
    sigaddset(&set, SIGINT);
    sigaddset(&set, SIGQUIT);
    //再判断一次SIGINT是否在信号集中
    //ret = __sigismember(&set, SIGINT);
    ret = sigismember(&set, SIGINT);
    if (ret == 1)
    {
        printf("SIGINT is a member of set\n");
    }
    return 0;
}
5、信号阻塞集(掩码)(屏蔽集)
	每个进程都有一个阻塞集,它用来描述哪些信号递送到该进程的时候
被阻塞(信号发生时记住,直到进程准备好时再将信号通知进程)

	所谓阻塞并不是禁止传送信号,而是暂缓信号的传送,
若将被阻塞的信号从信号阻塞集中删除,且对应的信号在被阻塞时发生了,进程将会收到相应的信号

sigprocmask函数
	创建一个阻塞集合
	#include<signal.h>
	int sigprocmask(int how,const sigset_t *set,sigset_t *oldset);
	功能:
		检查或修改信号阻塞集,根据how指定的方法对进程的阻塞集合进行修改,新的
		信号阻塞集由set指定,而原先的信号阻塞集合由oldset保存
	参数:
		how:信号阻塞集合的修改方法
				SIG_BLOCK:向信号阻塞集合中添加set信号集
				SIG_UNBLOCK:从信号阻塞集合中删除set集合
				SIG_SETMASK:将信号阻塞集合设为set集合
		set:要操作的信号集地址
		oldset:保存原先信号集地址	
		注:若set为NULL,则不改变信号阻塞集合,函数只把当前信号阻塞集合保存到oldset中
	返回值:
		成功返回0,失败返回-1
#include <stdio.h>
#include <signal.h>
int main(int argc, char const *argv[])
{
    int i = 0;
    sigset_t sigset;
    sigemptyset(&sigset);
    sigaddset(&sigset, SIGINT);
    while (1)
    {
        //将set信号集添加到信号阻塞集中
        sigprocmask(SIG_BLOCK, &sigset, NULL);
        for (i = 0; i < 5; i++)

        {
            printf("SIGINT is blocked\n");
            sleep(1);
        }
        //将set信号集从信号阻塞集中删除
        sigprocmask(SIG_UNBLOCK, &sigset, NULL);
        for (i = 0; i < 5; i++)
        {
            printf("SIGINT is not blocked\n");
            sleep(1);
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值