【Linux】进程间通信(1)

信号

    什么是信号?信号是给程序提供一种可以处理异步事件的方法,它利用软件中断来实现。不能自定义信号,所有信号都是系统预定义的。

信号由谁产生?

  1. 由shell终端根据当前发生的错误(段错误、非法指令等)Ctrl+c而产生相应的信号

            比如:

            socket通信或者管道通信,如果读端都已经关闭,执行写操作(或者发送数据),

            将导致执行写操作的进程收到SIGPIPE信号(表示管道破裂)

             该信号的默认行为:终止该进程。

                       

     2. 在shell终端,使用kill或killall命令产生信号

常见的信号

       -------------------------------------------

       信号名称                    说明

       -------------------------------------------

       SIGABORT            进程异常终止

       SIGALRM              超时告警

       SIGFPE                 浮点运算异常

       SIGHUP                连接挂断

       SIGILL                   非法指令

       SIGINT                  终端中断  (Ctrl+C将产生该信号)

       SIGKILL                *终止进程                            

       SIGPIPE                向没有读进程的管道写数据

       SIGQUIT                终端退出(Ctrl+\将产生该信号)

       SIGSEGV              无效内存段访问

       SIGTERM              终止

       SIGUSR1              *用户自定义信号1

       SIGUSR2              *用户自定义信号2

       -------------------------------------->以上信号如果不被捕获,则进程接受到后都会终止!

       SIGCHLD             子进程已停止或退出

       SIGCONT            *让暂停的进程继续执行

       SIGSTOP             *停止执行(即“暂停")

       SIGTSTP              中断挂起

       SIGTTIN              后台进程尝试读操作

       SIGTTOU             后台进程尝试写

       -------------------------------------------

信号的处理

1. 忽略此信号

2. 捕捉信号,指定信号处理函数进行处理

3. 执行系统默认动作,大多数都是终止进程

 信号的捕获

 信号的捕获,是指,指定接受到某种信号后,去执行指定的函数。

 注意:SIGKILL和SIGSTOP不能被捕获,即,这两种信号的响应动作不能被改变。

 Example1:

main函数中,signal函数中存放指定的信号编号和函数指针,当捕获到该信号时,将执行函数指针所指向的函数。
由于该函数改变了收到SIGINT信号(即ctrl + c)的行为(即原来的结束进程),故当程序启动时,再次按下ctrl + c,进程不会结束,而是执行myhandle函数。

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>


void myhandle(int sig) 
{
	printf("Catch a signal : %d\n", sig);
}

int main(void) 
{

	signal(SIGINT, myhandle);
	while (1) {
            sleep(1);
	}
	return 0;
}

 signal函数的具体含义如下图所示。

 Example2:

在Example1的基础上,在myhandle()函数中添加了一句
signal(SIGINT,SIG_DFL);
该段程序在首次捕捉到SIGINT信号后,不会结束进程,而是进入myhandle(),执行相关操作,在myhandle()中,又重新将捕获到SIGINT信号后的操作恢复默认,即结束进程。

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void myhandle(int sig) 
{
	static int cnt = 0;
	printf("Catch a signal : %d\n", sig);

	signal(SIGINT, SIG_DFL); //等同于signal(sig, SIG_DFL);
}

int main(void) 
{
	signal(SIGINT, myhandle);

	while (1) {
        sleep(1);
	}

	return 0;
}

使用sigaction  (项目实战强烈推荐使用

       sigaction与signal的区别: sigaction比signal更“健壮”,建议使用sigaction

           

       用法:man 2 sigaction

      

       结构:struct sigaction

        struct sigaction {

            void (*sa_handler)(int);     /* 信号的响应函数 */

            sigset_t   sa_mask;          /* 屏蔽信号集 */                        

            int sa_flags;                     /* 当sa_flags中包含 SA_RESETHAND时,接受到该信号并调用指定的信号处理函数执行之后,把该信号的响应行为重置为默认行为SIG_DFL */

            ...

        }

       

        补充:

        当sa_mask包含某个信号A时,则在信号处理函数执行期间,如果发生了该信号A,

         则阻塞该信号A(即暂时不响应该信号),直到信号处理函数执行结束。

         即,信号处理函数执行完之后,再响应该信号A

Example3:

其中sigemptyset()函数是用于清空屏蔽信号集的掩码,相当于初始化的作用。
若在该函数中,将sa_flags设为SA_RESETHAND则与Example2的功能类似,捕捉相应信号对应的函数只执行一次,往后遍恢复默认设置。

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void myhandle(int sig) 
{
	printf("Catch a signal : %d\n", sig);
}

int main(void) 
{
	struct sigaction act;

	act.sa_handler = myhandle;
	sigemptyset(&act.sa_mask);
    act.sa_flags = 0;

	sigaction(SIGINT, &act, 0);

	while (1) {
        
	}

	return 0;
}

信号的发送

       信号的发送方式:

             在shell终端用快捷键产生信号

             使用kill,killall命令。

             使用kill函数和alarm函数

使用kill函数

           给指定的进程发送指定信号

           用法:man 2 kill

           注意:

                        给指定的进程发送信号需要“权限”:

                        普通用户的进程只能给该用户的其他进程发送信号

                        root用户可以给所有用户的进程发送信号

           kill失败

               失败时返回-1

               失败原因:

                     权限不够

                     信号不存在

                     指定的进程不存在

 Example4:

创建一个子进程,子进程每秒中输出字符串“child process work!",父进程等待用户输入,如果用户按下字符A, 则向子进程发信号SIGUSR1, 子进程的输出字符串改为大写; 如果用户按下字符a, 则向子进程发信号SIGUSR2, 子进程的输出字符串改为小写 

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

int workflag = 0;

void work_up_handle(int sig) 
{
	workflag = 1;
}

void work_down_handle(int sig) 
{
	workflag = 0;
}



int main(void) 
{
	pid_t pd;
	char c;


	pd = fork();
	if (pd == -1) {                  //子线程创建失败
		printf("fork error!\n");
		exit(1); 
	} else if (pd == 0) {            //子线程创建成功,子线程执行如下程序
		char *msg;
		struct sigaction act; 
		act.sa_flags = 0;
		act.sa_handler = work_up_handle;
		sigemptyset(&act.sa_mask);		
		sigaction(SIGUSR1, &act, 0);    //SIGUSR1信号与work_up_handle函数挂钩
		
		act.sa_handler = work_down_handle;
		sigaction(SIGUSR2, &act, 0);    //SIGUSR2信号与work_down_handle函数挂钩
		
		while (1) {
			if (!workflag) {
				msg = "child process work!";
			} else {
				msg = "CHILD PROCESS WORK!";
			}
			printf("%s\n", msg);
			sleep(1);
		}
		
	} else {                          //父进程执行如下程序
		while(1) { 
			c = getchar();            
			if (c == 'A') {          //若输入的是‘A’,则产生SIGUSR1信号
				kill(pd, SIGUSR1);
			} else if (c == 'a') {   //若输入的是‘a’,则产生SIGUSR2信号
				kill(pd, SIGUSR2);
			}
		}
	}
	

	return 0;
}

Example5:

创建一个子进程,子进程在5秒钟之后给父进程发送一个SIGALR,父进程收到SIGALRM信号之后,“闹铃”(用打印模拟)

getppid():获取父进程编号。
pause():把该进程挂起,并阻塞,直到收到任意一个信号,才继续往下执行。 

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

int wakeflag = 0;

void wake_handle(int sig) 
{
	wakeflag = 1;
}

int main(void) 
{
	pid_t pd;
	char c;


	pd = fork();
	if (pd == -1) {
		printf("fork error!\n");
		exit(1);
	} else if (pd == 0) {
		sleep(5);
		kill(getppid(), SIGALRM);  //getppid() 获得父进程编号
                                             //向父进程发送SIGALRM信号
	} else {
		struct sigaction act; 
		act.sa_handler = wake_handle;  
		act.sa_flags = 0;
		sigemptyset(&act.sa_mask);  //清空掩码

		sigaction(SIGALRM,  &act, 0); //接收到SIGALRM信号后,调用act指定的处理函数  

		pause(); //把该进程挂起,直到收到任意一个信号

		if (wakeflag) {
			printf("Alarm clock work!!!\n");
		}
	}

	return 0;
}

alarm函数

        作用:在指定时间之内给该进程本身发送一个SIGALRM信号。

        用法:man 2 alarm

        注意:时间的单位是“秒”

                 实际闹钟时间比指定的时间要大一点。 

                 如果参数为0,则取消已设置的闹钟。

                 如果闹钟时间还没有到,再次调用alarm,则闹钟将重新定时

                 每个进程最多只能使用一个闹钟。

        

        返回值:

                 失败:返回-1

                 成功:返回上次闹钟的剩余时间(秒)

Example6:给自己的进程发信号 

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>

int wakeflag = 0;

void wake_handle(int sig) 
{
	wakeflag = 1;
}

int main(void) 
{
	int ret;
	
	struct sigaction act;
	act.sa_flags = 0;
	act.sa_handler = wake_handle;
	sigemptyset(&act.sa_mask);
	sigaction(SIGALRM, &act, 0);
	
	printf("time =%ld\n", time((time_t*)0));

	ret = alarm(5);          //过5s,发送一次alarm信号
	if (ret == -1) {
		printf("alarm error!\n");
		exit(1);
	}

	//挂起当前进程,直到收到任意一个信号
	pause();

	if (wakeflag) {
		printf("wake up, time =%ld\n", time((time_t*)0));
	}

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值