深入探索Linux Signal.h信号处理机制 (linux signal.h)

在Linux系统中,信号(signal)是一种异步事件的机制,可以用来通知进程发生了某种事件。Linux系统提供了Signal.h头文件来支持信号处理机制,其中包含了各种信号相关的常量、结构体和函数。

本文将从以下几个方面,包括信号的基本概念、信号的分类、信号的发送和接收、信号的处理方式以及信号的应用实例。

一、信号的基本概念

信号是由操作系统发送给进程的一种通知机制,用于向进程发送各种事件的通知。当某个事件发生时,操作系统会向特定的进程发送相应的信号,进程可以注册信号处理函数来处理这些信号。

Linux系统中有几十种不同的信号,每一种信号都用一个整数常量来表示。这些常量定义在Signal.h头文件中,并以”SIG”开头。例如,SIGINT表示当用户按下Ctrl+C时发送给前台进程的信号,SIGTERM表示进程终止信号,SIGKILL表示无法处理或终止进程的强制终止信号。

二、信号的分类

Linux系统中的信号可以根据不同的分类方式进行分类,其中较为常见的分类方式包括以下几种:

1.默认信号和自定义信号

默认信号是由系统定义的且不能被修改的信号,如SIGTERM、SIGKILL等。自定义信号可以由用户根据自己的需求来定义,如SIGUSR1、SIGUSR2等。

2.同步信号和异步信号

同步信号是进程在执行某些操作时自己产生的信号,如除零错误、访问非法地址等。异步信号是由进程外部的事件(如按键、定时器、网络套接字等)触发引起的信号。

3.可靠信号和不可靠信号

可靠信号是指能保证信号不会被丢失或重复发送的信号。不可靠信号可能会在信号处理函数还未执行完毕时再次被发送,或者在信号处理函数中多次发送。

三、信号的发送和接收

Linux系统中,进程可以使用kill函数向其他进程或自己发送信号。kill函数的原型如下:

“`c++

#include

int kill(pid_t pid, int sig);

“`

其中pid参数表示接收信号的进程ID,sig参数表示发送的信号。

接收信号的进程需要通过signal函数来注册信号处理函数,如:

“`c++

#include

void signal_handler(int sig) {

// 处理信号

}

int mn() {

signal(SIGINT, signal_handler); // 注册键盘中断信号处理函数

while (1) {

// 循环等待信号

}

return 0;

}

“`

在本例中,调用signal函数来注册SIGINT信号,并指定了signal_handler作为处理函数。当接收到SIGINT信号时,操作系统会调用signal_handler处理函数来处理信号。

四、信号的处理方式

接收到信号之后,操作系统会调用之前注册的信号处理函数来处理信号。关于信号处理函数的基本要求如下:

1.信号处理函数必须是一个C语言函数,返回值为void。

2.信号处理函数不能有任何参数。

3.信号处理函数必须是可重入的,因为它可能在任何时间被调用。

信号处理函数有以下三种处理方式:

1.默认处理方式

默认处理方式就是不做任何处理,直接忽略信号或终止进程。可以使用signal函数进行设置,如:

“`c++

#include

signal(SIGINT, SIG_DFL); // 默认操作为终止进程

“`

2.忽略信号

可以使用signal函数将信号处理函数设置为SIG_IGN来忽略某个信号,如:

“`c++

#include

signal(SIGINT, SIG_IGN); // 忽略键盘中断信号

“`

3.自定义处理方式

可以编写自定义的信号处理函数来处理特定的信号,如:

“`c++

#include

void signal_handler(int sig) {

// 处理信号

}

signal(SIGINT, signal_handler); // 注册键盘中断信号处理函数

“`

五、信号的应用实例

Linux系统中,信号机制广泛应用于进程间的通信、进程控制、时间管理等领域。下面列举几个信号的应用实例。

1.进程间通信

可以使用kill函数向其他进程发送自定义信号,如:

“`c++

kill(pid, SIGUSR1); // 向进程pid发送SIGUSR1信号

“`

收到信号的进程可以根据信号类型和来源,决定如何响应。例如,可以使用自定义信号来实现双方进程的心跳检测、资源分配等功能。

2.进程控制

可以使用信号机制来控制进程的行为,例如通过SIGSTOP和SIGCONT信号来停止和继续进程的执行,如:

“`c++

kill(pid, SIGSTOP); // 停止进程pid的执行

kill(pid, SIGCONT); // 继续进程pid的执行

“`

3.时间管理

可以使用定时器信号来进行时间管理,例如使用SIGALRM信号来实现闹钟功能,如:

“`c++

#include

#include

void alarm_handler(int sig) {

// 播放音乐或执行其他操作

}

signal(SIGALRM, alarm_handler); // 注册定时器信号处理函数

alarm(10); // 10秒后发送SIGALRM信号

pause(); // 等待定时器信号

“`

在本例中,使用alarm函数设置10秒钟的定时器,当定时器到期时,将发送SIGALRM信号通知进程。进程在收到SIGALRM信号时,调用alarm_handler函数进行处理。

相关问题拓展阅读:

当linux应用程序中存在多个异步通知时怎样处理

驱动程序运行在内核空间中,应用程序运行在用户空间中,两者是不能直接通信的。但在实际应用中,在设备已经准备好的时候,我们希望通知用户程序搏告坦设备已经ok,用户程序可以读取了,这样应用程序就不需要一直查询该设备的状态,从而节约了资源,这就是异步通知。好,那下一个问题就来了,这个过程如何实现呢?简单,两方面的工作。

一 驱动方面:

1. 在设备抽象的数据结构中增加一个struct fasync_struct的指针

2. 实现设备操作中的fasync函数,这个函数很简单,其主体就是调用内核的fasync_helper函数。

3. 在需要向用户空间通知的地方(例如中断中)调用内核的kill_fasync函数。

4. 在驱动的release方法中调用前面定义的fasync函数

呵呵,简单吧,就三点。其中fasync_helper和kill_fasync都是内核函数,我们只需要调用就可以了。在

1中定义的指针是一个重要参数,fasync_helper和kill_fasync会使用这个参数。

二 应用层方面

1. 利用signal或者sigaction设置SIGIO信号的处理函数

2. fcntl的F_SETOWN指令设置当前进程为设备文件owner

3. fcntl的F_SETFL指令设置FASYNC标志

完成了以上的工作的话,当内核执行到kill_fasync函数,用户空间SIGIO函数的处理函数就会被调用了。

呵呵,看起来不是很复杂把,让我们结合具体代码看看就更明白了。

先从应用层代码开始吧:

#include

#include

#include

#include

#include

#include

#define MAX_LEN 100

//处理函数,没什么好讲的,用户自己定义

void input_handler(int num)

{

char data;

int len;

//读取并输出STDIN_FILENO上的输入

len = read(STDIN_FILENO, &data, MAX_LEN);

data = 0;

printf(“input available:%s\n”, data);

}

void main()

{

int oflags;

//启动信号驱动机制,将SIGIO信号同input_handler函数关联起来,一旦产生SIGIO信号,就会执行input_handler

signal(SIGIO, input_handler);

//STDIN_FILENO是打开的设备文件描述符,F_SETOWN用来决定操作是干什么的,getpid()是个系统调用,

//功能是返回当前进程的进程号,整个函数基桐的功能是STDIN_FILENO设置这个设备文件的拥有者为当前进程。

fcntl(STDIN_FILENO, F_SETOWN, getpid());

//得到打开文件描述符的状态

oflags = fcntl(STDIN_FILENO, F_GETFL);

//设置文件描述符的状态为oflags | FASYNC属性,一旦文件描述符被设置成具有FASYNC属性的状态,

//也就是将设备文件切换到异步操作模式。这时系统就会自动调用驱动程序的fasync方法。

fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC);

//最后进入一个死循环,程序什么都不干了,只有信号能激发input_handler的运行

/友凯/如果程序中没有这个死循环,会立即执行完毕

while (1);

}

再看驱动层代码,驱动层其他部分代码不变,就是增加了一个fasync方法的实现以及一些改动

//首先是定义一个结构体,其实这个结构体存放的是一个列表,这个

//列表保存的是一系列设备文件,SIGIO信号就发送到这些设备上

static struct fasync_struct *fasync_queue;

//fasync方法的实现

static int my_fasync(int fd, struct file * filp, int on)

{

int retval;

//将该设备登记到fasync_queue队列中去

retval=fasync_helper(fd,filp,on,&fasync_queue);

if(retval

{

return retval;

}

return 0;

}

在驱动的release方法中我们再调用my_fasync方法

int my_release(struct inode *inode, struct file *filp)

{

//..processing..

drm_fasync(-1, filp, 0);

//..processing..

}

这样后我们在需要的地方(比如中断)调用下面的代码,就会向fasync_queue队列里的设备发送SIGIO信号

,应用程序收到信号,执行处理程序

if (fasync_queue)

kill_fasync(&fasync_queue, SIGIO, POLL_IN);

好了,这下大家知道该怎么用异步通知机制了吧?

以下是几点说明:

1 两个函数的原型

int fasync_helper(struct inode *inode, struct file *filp, int mode, struct fasync_struct **fa);

一个”帮忙者”, 来实现 fasync 设备方法. mode 参数是传递给方法的相同的值, 而 fa 指针指向一个设

备特定的 fasync_struct *

void kill_fasync(struct fasync_struct *fa, int sig, int band);

如果这个驱动支持异步通知, 这个函数可用来发送一个信号到登记在 fa 中的进程.

2.

fasync_helper 用来向等待异步信号的设备链表中添加或者删除设备文件, kill_fasync被用来通知拥有相关设备的进程. 它的参数是被传递的信号(常常是 SIGIO)和 band, 这几乎都是 POLL_IN(但是这可用来发送”紧急”或者带外数据, 在网络代码里).

写一个linux下写个关于c语言的双守护进程,就是监视一个进程,当其死掉,马上将其重启

这判州宴跟execvp函数的实现方式有关:

int execvp(const char *file ,char * const argv );

execvp()会从PATH 环境变量所指的目录中查找符掘银合参数file的文件名,找到后便执行该文件,然后将第二个参数argv传给该欲执行的文件。如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中。

之所以显示“fail to exec”,是因为在PATH环境变量所指的目录中没有名为“hello”的程序。建议进行如下操作:

1、运行“echo $PATH”,查看一迹乱下PATH环境变量指向那些目录

2、编写一个输出“hello world”的程序,并命名为hello,即执行命令:

gcc -o hello hello.c

3、把名为”hello“的程序拷贝到PATH变量所指的其中一个目录中

可以分三步来做:

做两个简单的守护进程,并能正常运行

监控进程是否在运行

启动进程

综合起来就可以了,代码如下:

被监控进程thisisatest.c(来自

):

#include

#include

#include

#include

#include

#include

#include

#include

void init_daemon()

{

int pid;

int i;

pid=fork();

if(pid0) //父进程退出

    exit(0);

    

setsid(); //使子进程成为组长

pid=fork();

if(pid>0)

    exit(0); //再次退出,使进程不是组长,这样进程就不会打开控制终端

else if(pid=0)

{

time(&t);

竖族     fprintf(fp,”current time is:%s\n”,asctime(localtime(&t)));  //转换为本地时间输出

fclose(fp);

}

    }

    return;

}

监控进程monitor.c:

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#define BUFSZ 150

void init_daemon()

{

int pid;

int i;

pid=fork();

if(pid0) //父进程退出

    exit(0);

setsid(); //使子进程成为组长

pid=fork();

if(pid>0)

    exit(0); //再次退出,使进程不是组长,这样进程就不会打开控制终端

else if(pid=0)

{

count = does_service_work();

time(&t);

if(count>0)

  fprintf(fp,”current time is:%s and the process exists, the count is %d\n”,asctime(localtime(&t)), count);  //转换为本地时间输出

else

{

  fprintf(fp,”current time is:%s and the process does not exist, restart it!\n”,asctime(localtime(&t)));  //转换为本地时间输出

  system(“/home/user/daemon/thisisatest”); //启动服务

}

fclose(fp);

}

    }

    return;

}

具体CMD命令:

cc thisisatest.c -o thisisatest

./thisisatest

cc monitor.c -o monitor

./monitor

tail -f testfork3.log   — 查看日志

linux signal.h的介绍就聊到这里吧,感谢你花时间阅读本站内容,更多关于linux signal.h,深入探索Linux Signal.h信号处理机制,当linux应用程序中存在多个异步通知时怎样处理,写一个linux下写个关于c语言的双守护进程,就是监视一个进程,当其死掉,马上将其重启的信息别忘了在本站进行查找喔。


数据运维技术 » 深入探索Linux Signal.h信号处理机制 (linux signal.h)