当前位置:IT培训 > 华清远见教育科技集团 > 嵌入式学习 > 讲师博文 > 进程间通信之信号
进程间通信之信号
时间:2018-03-13作者:华清远见

一:信号的基本介绍

信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式(进程在运行过程中,随时可能被各种信号打断)。

信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了那些系统事件。

如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进

程恢复执行再传递个它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞取消时才被传递给进程。

二:信号的产生

A.用户在终端按下某些键时,终端驱动程序会发送信号给前台进程,例如ctr+c产生SIGINT, ctr + \产生SIGQUI信号,ctr + z产生SIGTSTP。

B.硬件异常产生信号,这些条件由硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给当前进程 。我们常见的段错误。

C.一个进程调用int kill(pid_t pid,int sig)函数可以给另一个进程发送信号。

D.可以用kill命令给某个进程发送信号,如果不明确指定信号则发送SIGTERM信号,该信号的默认处理动作是终止进程。

E.当内核检测到某种软件条件发生时也可以通过信号通知进程,例如闹钟超时产生

SIGALRM信号,向读端已关闭的管道写数据时产生SIGPIPE信号。

三:linux操作系统支持的信号

A. kill -l命令查看当前系统支持的所有的信号

进程间通信之信号

B:常用信号的含义

信号名 含义 默认操作
 
SIGHUP
该信号在用户终端连接(正常或非正常)结束时发出,通常是在终端的控制进程结束时,通知同一会话内的各个作业与控制终端不再关联。  
终止
 
SIGINT
该信号在用户键入INTR字符(通常是Ctrl-C)时发出,终端驱动程序发送此信号并送到前台进程中的每一个进程。 终止
 
SIGQUIT
该信号和SIGINT类似,但由QUIT字符(通常是Ctrl-\)来控制。 终止
 
SIGILL
该信号在一个进程企图执行一条非法指令时(可执行文件本身出现错误,或者试图执行数据段、堆栈溢出时)发
出。
 
终止
 
SIGFPE
该信号在发生致命的算术运算错误时发出。这里不仅包括浮点运算错误,还包括溢出及除数为0等其它所有的算术的错误。  
终止
信号名 含义 默认操作
 
SIGKILL
该信号用来立即结束程序的运行,并且不能被阻塞、处理和忽略。 终止
SIGALRM 该信号当一个定时器到时的时候发出。 终止
SIGSTOP 该信号用于暂停一个进程,且不能被阻塞、处理或忽略。 暂停进程
 
SIGTSTP
该信号用于暂停交互进程,用户可键入SUSP字符(通常是Ctrl-Z)发出这个信号。 暂停进程
SIGCHLD 子进程改变状态时,父进程会收到这个信号 忽略
SIGABORT 该信号用于结束进程 终止

四:linux中进程对信号处理

忽略信号,即对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及

SIGSTOP。

捕捉信号,定义并注册信号处理函数,当信号发生时,执行相应的处理函数。

【重点】。

执行缺省操作,Linux对每种信号都规定了默认操作

五:相关API

1:信号的发送(kill和raise)
#include <sys/types.h>
#include <signal.h>

函数原型:int kill(pid_t pid, int sig); 函数功能:给进程 id 为 pid 的进程发送信号

函数参数:@param pid : 发送信号的目标进程的 id

@param sig : 发送的信号编号,例如:9(SIGKILL) 返回值:成功调用返回 0 ,失败返回 -1 ,并设置 errno
#include <signal.h>

函数原型:int raise(int sig); 函数功能:给当前进程自己发送信号

函数参数:@param sig : 发送的信号编号

返回值:成功调用返回 0 ,失败返回 -1 ,并设置 errno

.*练习

我们通过终端kill -9 某个进程终止过一个进程,现在我们使用kill函数来终止一下。过程:

父进程创建一个子进程,父进程拿到子进程的进程ID;子进程中while循环打印hello,sleep(1);

父进程sleep(5)之后,给子进程发送9这个信号来终止子进程。

2:信号的捕捉(signal)

知识点回顾

void func(int);//函数的声明

void (*func)(int);//定义函数指针,指向void (int)类型的函数typedef int a;//给int类型的a起别名

typedef void (*funcp)(int);//给类型为void (*)(int);的函数指针起别名funcp 捕捉信号的处理过程:

进程间通信之信号

#include <signal.h>

typedef void (*sighandler_t)(int);//指向函数的指针,表示信号处理函数的形式sighandler_t signal(int signum, sighandler_t handler);

函数功能 : 将信号与信号处理函数进行关联函数参数:@param signum : 信号的编号

@param handler : 信号处理函数的指针SIG_DFL : 表示默认操作

SIG_IGN : 表示忽略信号(SIGKILL和SIGSTOP时不能被忽略的) 返回值:成功调用返回信号处理函数的指针,否则,返回SIG_ERR

进程间通信之信号

注意:sighandler_t handler中的int保存的是调用这个函数是因为哪个信号触发的,带过来对应的信号值。

.* 练习

(1)忽略ctrl+c对进程的终止信号。signal(SIGINT, SIG_IGN);

(2)在信号处理函数中将对应的信号的描述信息进行打印。

.* 练习:

fork前采用signal信号处理函数不阻塞,不轮询的方式回收僵尸态子进程[waitpid()函数]。 在信号处理函数signal_handler()中对信号进行收尸操作。然后利用fork函数创建一个子进程。休眠10s后退出。父进程是一个死循环,每秒输出"father do something…"的字符串。

提示:

子进程在终止时会给父进程发SIGCHLD,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理函数。我们这里调用waitpid非阻塞的回收僵尸态子进程。这样父进程只需要专心处理自己的工作,不必关心子进程了,子进程终止时会通知父进程,父进程在信号处理函数中调用waitpid函数清理子进程即可。

一般信号对僵尸态子进程的处理方法:

<1>父进程采用signal(SIGCHLD, hand_signal),采用信号处理函数,对接收到的SIGCHLD进行进行处理。在接收到SIGCHLD信号的时候,采用waitpid利用非阻塞的方式的释放它们的资源。若是使用wait()函数的话,父进程会阻塞。 [推荐使用]

<2>父进程采用signal(SIGCHLD, SIG_IGN),忽略SIGCHLD信号,这样子进程结束后,就不需要父进程来wait和释放资源。它会自动被过继给老祖宗init进程,int进程会负责释放他的资源,这样就不会产生僵尸态子进程。

3:定时闹钟函数(alarm)

unsigned int alarm(unsigned int seconds);

函数功能:给进程启动一个定时器,经过seconds秒后把SIGALRM信号发送给当前进程。函数参数:@seconds 秒

返回值:成功返回0,失败返回 -1

注意:一个进程只能有一个闹钟事件,若是多次使用alarm函数,则闹钟时间被刷新。

.*练习

(1)main函数中设置2s定时器,然后注册SIGALRM信号的处理函数,处理函数中打印当前时间到屏幕上

(2)我们现实中经常有这样的需求,需要每隔2s执行某个函数,这样怎么处理呢? 答案:在定时器处理函数里边,再次刷新闹钟alarm(2);
#include <stdio.h>
#include <time.h>
#include <signal.h>
 
void handler(int sig)
{
time_t tim = time(NULL);
struct tm *ptm = localtime(&tim);
printf("d-02d-02d 02d:02d:02d\n", ptm->tm_year+1900,
ptm->tm_mon+1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
alarm(1); 
return;
}
int main(int argc, const char *argv[])
{
signal(SIGALRM, handler); alarm(1);
int n = 0; while(1)
{
printf("n = d\n", ++n); usleep(200);
}
return 0;
}

4:信号的等待(pause)

int pause(void);

特点:挂起一个进程,直到进程收到一个信号,进程会继续执行

上边的练习,在while循环中pause()一下。现象:不加pause()之前printf("n = d\n",

++n);会200ms打印一次,然后1秒打印一次时间,加上pause()之后,现象则是1s打印一次printf("n = d\n", ++n),时间也是1s一次。因为pause()会将进程挂起,接收到信号之后会继续,进程1s接收一次ALARM信号,则进程会1s会被唤醒一次。

#include <stdio.h>
#include <time.h>
#include <signal.h>
 
void handler(int sig)
{

time_t tim = time(NULL);
struct tm *ptm = localtime(&tim);
printf("d-02d-02d 02d:02d:02d\n",          ptm->tm_year+1900, ptm->tm_mon+1, ptm->tm_mday, ptm->tm_hour,
ptm->tm_min, ptm->tm_sec); alarm(1);
 
return;
}
int main(int argc, const char *argv[])
{
signal(SIGALRM, handler); alarm(1);
int n = 0; while(1)
{
pause();
printf("n = d\n", ++n); usleep(200);
}
return 0;
}


发表评论

全国咨询电话:400-611-6270,双休日及节假日请致电值班手机:15010390966

在线咨询: 曹老师QQ(3337544669), 徐老师QQ(1462495461), 刘老师 QQ(3108687497)

企业培训洽谈专线:010-82600901,院校合作洽谈专线:010-82600350,在线咨询:QQ(248856300)

Copyright 2004-2018 华清远见教育科技集团 版权所有 ,京ICP备16055225号,京公海网安备11010802025203号

有位老师想和您聊一聊