进程间通信(二)

本文深入介绍了Linux信号机制,包括信号的发送、响应、安装等关键概念,并详细解析了kill、raise、alarm等信号处理函数的功能与用法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

三、信号

信号时linux中进程间的一种重要的通信方式。信号是在软件层次上对中断的一种模拟,也叫做软中断。信号是linux进程间通信机制中唯一的异步通信机制。信号通信机制包括信号的发送,信号的响应,信号的安装,信号集与信号集操作函数,以及信号的阻塞和未决。Linux中总共包括了64中信号,前32种信号是不可靠的信号(非实时信号:不支持排队);后32种是可靠地信号(即有效的解决了信号丢失的问题,且是实时信号:支持信号的排队)。下面列举出ubuntu 中64中信号: 终端中输入命令: kill -l


下面依次地介绍信号通信机制中的几种操作:

1、 信号的响应

进程可以通过三种方式对信号进行响应:1)忽略信号,对信号不做任何处理;但SIGKILL 和SIGSTOP是不能被忽略的。2)捕捉信号,定义信号处理函数,当信号发生时执行信号处理函数。3)执行缺省操作,linux对每种信号都定义了缺省操作。


2、信号的发送

发送信号的函数主要包括:kill(); raise(); sigqueue(); alarm(); setitimer(); 以及abort();

1).kill()

#include<sys/types.h>
#include<signal.h>
int kill(pid_t pid, int signum);
其中pid是进程的ID;signum是信号值,当为0时(即空信号),实际不发送任何信号,但照常进行错误检查,因此,可用于检查目标进程是否存在,以及当前进程是否具有向目标发送信号的权限(root权限的进程可以向任何进程发送信号,非root权限的进程只能向属于同一个session或者同一个用户的进程发送信号)。

参数pid

意义

=0

同一个进程组的进程

>0

ID为pid的进程

<0且不等于-1

ID为-pid的进程组

=-1

除发送进程自身外,所有进程ID大于1的进程


2).raise()

#include<signal.h>
int raise(int signum)
向进程本身发送信号,信号值为函数的参数signum。

3).alarm()

#inlcude<unistd.h>
unsigned int alarm(unsigned int seconds)
alarm()又称作闹钟函数,可以在进程中设置一个定时器,当定时时间到了,内核就会向进程发送一个SIGALRM信号。seconds为定时的秒数,如果该参数为0,则取消进程中所有的alarm()定时作用。如果alarm()函数之前有alarm()存在,则返回之前alarm()定时的剩余时间,否则返回0。且后来定义的alarm()函数会清除前面定义的alarm()函数的作用。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include <sys/types.h>

void Sigalarm_func(int signum)
{
	if(SIGALRM == signum)
		printf("enter alarm!\n");
	exit(EXIT_SUCCESS);
}

int main()
{
	int n =0;
	int ncount = 0;
	signal(SIGALRM, Sigalarm_func);
	n = alarm(10);
	printf("now n is :%d\n", n);
	sleep(6);
	n = alarm(5);
	printf("now n is :%d\n", n);
	int i;
	for(i = 0; i<10; i++)
	{
		printf("now ncount is: %d\n",ncount);
		ncount++;
		sleep(1);
	}
	return 0;

}
输出结果如下:

从上面可以看出后面定义的alarm(5)取消了alarm(10)的定时作用。

4). setitimer()

#include<sys/time.h>
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue )
setitimer也是用作定时作用,但相比于alarm()有很大的灵活度。函数的第一个参数有三种可选的值:

  1. ITIMER_REL :设定绝对的时间;经过设定的时间后,内核将发送SIGALRM给本进程
  2. ITIMER_VIRTUAL :设定程序执行时间;经过设定的时间后,内核将发送SIGALRM给本进程。
  3. ITIMER_PROF: 设定进程执行时间以及内核因该进程执行所消耗的时间和,经过设定的时间后,内核将发送SIGALRM给本进程。
第二个参数struct itimerval是一个结构体;

struct itimerval {
struct timeval it_interval;
struct timeval it_value;
};
struct timeval {
long tv_sec; // second
long tv_usec; // usecond 微秒
};

it_interval指定间隔时间,it_value指定初始定时时间。如果只是指定it_value,就是实现一次定时;如果同时指定it_interval,则超时后,系统会重新初始化it_value为it_interval,实现重复定时;两者都清零,则会清除定时器。

第三个参数 ovalue是保存先前的值,通常会置为NULL;

5). absort()

#include <stdlib.h>
void absort(void)
向进程发送SIGABSORT信号,缺省情况下使进程异常退出。也可以为SIGABSORT信号自定义处理函数(handler)


6). sigqueue()

这个是最新的信号发送函数,但也是最复杂的一个,其函数原型如下:

#include<sys/types.h>
#include<signal.h>
int sigqueue(pid_t pid, int signum, const union sigval val)
其中第一个参数是指定的进程ID号(该函数只能给一个进程发送信号,而kill可以给一个进程组发送信号);第二个参数是信号值;第三个参数是sigval联合体的一个实例,

	typedef union sigval {
 		int  sival_int;
 		void *sival_ptr;
 	}sigval_t;
该联合体提供了信号传递的参数,即sigqueue相比于kill可以支持传递带参数的信号。同样如果signum=0,将会执行错误检查,但实际上不发送任何信号,0值信号可用于检查pid的有效性以及当前进程是否有权限向目标进程发送信号。



3. 信号的安装

信号的安装的意思是:实现对特定信号进行自定义函数的处理。

1). signal()

#include<signal>
void(* signal(int signum, void(*handler)(int signum)))(int)
或者可以理解为:

typedef void (*sighandler_t)(int); 
sighandler_t signal(int signum, sighandler_t handler)); 
即signal函数有两个参数组成包括:一个int型的信号值和一个参数为int型返回为void的函数指针handler。在提前定义handler的时候,调用signal可以实现对信号特性处理的安装。

2). signalaction()

这是一个比较复杂的函数,不过它提供了相当强大的功能。与sigqueue配合使用可以传递具有参数的信号。

#include<sys/types.h>
#include<signal.h>
int sigaction(int signum, const struct sigaction *act, const struct sigaciton *oldact)
其中第一个比较好理解,它就是信号值。第二和第三个参数都是sigaction的结构体,其中第三个参数是对之前的信息的存储,可以设置为NULL;

下面着重介绍下sigaction结构体

 struct sigaction {
          union{
            __sighandler_t _sa_handler;
            void (*_sa_sigaction)(int,struct siginfo *, void *);
            }_u
                     sigset_t sa_mask;
                    unsigned long sa_flags; 
                  void (*sa_restorer)(void);
                  }

结构体中第一个数据成员是一个联合体,它也就是对信号的处理方式,可以选择:SIGDEF(缺省模式),SIGIGN(忽略信号),或者一个处理函数的函数指针。

但是这里的处理函数相比于signal中的处理函数多出了两个参数,第二和第三个参数。第三参数现在没有用处。第二参数是一个结构体,其中定义如下:

typedef struct {
		int si_signo; //信号值
		int si_errno; //错误值		
		int si_code; // 信号产生的原因		
		union sigval si_value;	
		} siginfo_t;
union sigval {     //也就是在sigqueue中的第三个参数
		int sival_int;		
		void *sival_ptr;	
		}
在信号的处理函数中包括了 sigval的联合体的目的是给处理函数提供由信号携带的参数。具体怎么操作可以在编写信号处理函数时进行设计。


继续上面有关结构体sigaction的介绍:

它的第二个参数是sa_mask,sa_mask指定在信号处理程序执行过程中,哪些信号应当被阻塞。缺省情况下当前信号本身被阻塞,防止信号的嵌套发送,除非指定SA_NODEFER或者SA_NOMASK标志位。

它的第三个参数:sa_flags中包含了许多标志位,包括刚刚提到的SA_NODEFER及SA_NOMASK标志位。另一个比较重要的标志位是SA_SIGINFO,当设定了该标志位时,表示信号附带的参数可以被传递到信号处理函数中,因此,应该为sigaction结构中的sa_sigaction指定处理函数,而不应该为sa_handler指定信号处理函数,否则,设置该标志变得毫无意义。即使为sa_sigaction指定了信号处理函数,如果不设置SA_SIGINFO,信号处理函数同样不能得到信号传递过来的数据,在信号处理函数中对这些信息的访问都将导致段错误(Segmentation fault)。



信号是进程通信中的一种重要的通信机制,一定要熟悉该通信机制的发送信号,安装信号的函数。当然还有信号集,以及信号阻塞的部分的函数。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值