2017-05-11 18:33:09 a1414345 阅读数 960

信号产生方式

  1. 通过终端按键产生。 如ctrl+c 终止进程
  2. 通过系统函数向进程发送信号。如kill()函数,给指定进程发送信号。
  3. 有软件条件产生信号。如alarm()函数,设定一个闹钟信号。
  4. 硬件异常。如内存越界,除0异常。

利用kill()函数实现自己的kill 命令

// 发送信号给进程
#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid,  int sig);

先写一个死循环程序test并在后台跑起来

//test.c
#include <stdio.h>
int main()
{
    while(1);
    return 0;
}

这是利用kill 函数实现的mykill:

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

int main( int argc, char** argv)
{
    if(argc != 1)
    {
        printf("parameter error.\n");
        exit(1);
    }

    // 获取pid
    pid_t pid = atoi(argv[1]);

    kill(pid, SIGKILL);

    return 0;
}

testrun

然后执行jobs ,可以看到后台作业test已经存在。

jobs

此时执行mykill 并 输入test程序的pid

jobs

第一次执行jobs发现test状态已经从Running 变为 Killed,再次执行就会看到已经被干掉。

认识alarm() 函数

#include <unistd.h>

unsigned int alarm( unsigned int seconds);

alarm函数可以设定一个闹钟,告诉内核在seconds秒之后给当前进程发送SIGALRM信号,该信号的默认处理动作是终止当前进程。 函数返回值是0,或者是上次闹钟剩余时间。

比如先设置闹钟10s, 然后调用alarm(0) 表示取消闹钟, 如果返回0, 说明闹钟是在10后响的,如果大于0,则说明该闹钟提前响了。

下面是一个使用alarm函数的小栗子, 测试一秒对可以执行多少次++,并在每次++后打印值:

#include <stdio.h>
#include <unistd.h>


int main()
{
    int count = 0;

    alarm(1);
    while(1)
    {
        count++;
        printf("count is  %d .\n", count);
    }
    return 0;
}

信号的处理方式

一个进程在收到一个信号后,有三种方式处理方式:
1. 忽略此信号
2. 执行默认动作
3. 提供一个信号处理函数,要求内核在处理该信号时切换到用户态,这种方式也叫捕捉信号。

一个捕捉信号的小例子:

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


void sigact(int  num)
{
    printf("\n %d号 信号 被我吃了. \n", num);
}


int main()
{
    printf("catch start ... \n");
    signal(SIGINT, sigact); // 捕捉 SIGINT 信号,提供自定义动作
    while(1)
    {
        sleep(1);
        printf("你杀不掉我 hhh \n");
    }
    return 0;
}

catch_2

在代码中,我们捕捉了SIGINT 信号,也就是2号信号(kill -l 可以看到全部信号列表), 操作系统收到ctrl + c ,发送该信号给当前前台作业。发现执行后它每个一秒打印一条“你杀不掉我 hhh”, 当我们按ctrl+c 也无济于事,因为我们为该进程提供了2号信号的自定义函数“信号被我吃掉了”。最后我们只能以ctrl + \干掉它。

信号在内存中的表示

以上讨论了信号产生的各种原因,而实际执行信号的动作称为递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending)。进程可以设置阻塞(Block)某个信号。

如果一个进程阻塞了某个信号,那么在它收到被阻塞的信号时,该信号会处于未决状态。直到对该信号解除阻塞,才会执行抵达的动作。

需要注意的是,信号阻塞和信号忽略是不同的。信号忽略是在该信号被递达后执行的动作,而阻塞说明该该信号在解除阻塞之前不可能递达。

我们知道在系统中运行的每一个进程都有一个 PCB, 而一个进程对应的信号信息也会被操作系统记录在该进程的 PCB 上。在task_struct 结构体会有对应的字段来记录进程当前是否有待处理的信号, 还有记录当前要阻塞的信号,以及信号对应的处理函数。下面用一张图来说明一下关系:

SIGNAL表示

我们可以这样理解:以看做在PCB中有三张表格,分别是block表,pending表,handler表。

block表中1 表示该信号被阻塞,当有信号产生时不会被抵达,会处于未决状态。
而pending表记录未被处理的信号。handler 对应每个信号的处理方式,有默认和忽略,以及一个函数指针,指向我们提供的处理函数。

一大波信号集处理函数袭来

通过上面的表格可以发现,阻塞和未决状态,每个信号只需要对应一个bit位即可解决1表示有效,0表示无效,所以系统为我们提供了信号量集 sigset_t 来保存阻塞和未决的状态。

#include <signal.h>

int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);

以上两个函数用来将信号机全部置0或全部置1,在使用之前,务必调用对应初始化函数,让信号机处于确定状态。


int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);

上面两个函数用来给,指定信号集,添加或删除signum信号。


int sigismember(sigset_t *set, int );

用来判断信号集中是否有该信号,有则返回1,无返回0,执行失败返回-1。

sigprocmask函数

用来读取或者更改进程的信号屏蔽集。

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

如果 set 为空 oldset 不为空, 则会将进程原来的信号屏蔽集传出;
如果 set 不为空,oldset 为空, 则会根据 how 参数的指示修改进程的信号屏蔽集;
如果两个指针都不为空, 则先备份原来的信号屏蔽字到 oldset, 然后根据 how参数修改。

how参数有如下函数

含义
SIG_BLOCK 将当前进程屏蔽字mask 增加我们希望通过参数set的屏蔽信号,相当于mask与set执行按位或
SIG_UNBLOCK 将当前进程屏蔽字mask 删除我们希望通过参数set解除屏蔽的信号
SIG_SETMASK 设置当前信号屏蔽字为set,相当于 mask = set

sigpending

用来读取当前进程的未决信号集。

#include <signal.h>
int sigpending(sigset_t *set);

下面运用上面介绍的信号集函数写一个小实验。

程序运行时,每秒打印一此未决状态信号集,初始全为0,当输出ctrl-c时, 由于我们阻塞了SIGINT信号, 会使该信号处于未决状态。

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


// 打印信号集
void printsigset(const sigset_t *set)
{
    int i = 0;
    for(; i<32; ++i)
    {
        if(sigismember(set, i) == 1)
            printf("1");
        else
            printf("0");

    }
    printf("\n");
}

int main()
{

    sigset_t s;
    sigemptyset(&s); // 初始化
    sigaddset(&s, SIGINT);
    sigprocmask(SIG_BLOCK, &s, NULL );

    while(1)
    {
        sigpending(&s);
        printsigset(&s);
        sleep(1);
    }


    return 0;
}

printpendingset

2019-08-05 20:23:17 m0_37622246 阅读数 29

信号的基本概念

中断

中止(不是终止)当前正在执行的任务,转而执行其他任务(可能返回也可能不返回),中断分为硬件中断(硬件设备产生的中断)和软件中断(其他程序产生的中断)。

信号

是一种软件中断,提供了一种异步执行任务的机制。

常见的信号

SIGINT(2)  Ctrl+C  产生的信号
SIGQUIT(3) Ctrl+\ 产生的信号
SIGABRT(6) 调用abort函数产生此信号
SIGFPE(8)  例如除以0、浮点溢出等
SIGKILL(9) 不能被捕获或忽略。常用于杀死进程
SIGSEGV(11)段错误信号,非法访问内存产生的信号
SIGCHLD(17)子进程状态改变信号
SIGTSTP(20) Ctrl+z 产生的信号,强制退出
注意:在终端中执行 kill -l 可以显示出所有信号

不可靠信号

建立在早期机制上的信号被称为不可靠信号,SIGHUP(1)~SIGSYS(31),不支持排队,可能会丢失,同一个信号产生多次,进程可能只接收到一次。

可靠信号

采用新的机制产生的信号,34~64之间,支持排队,不会丢失。

信号的来源

硬件产生:除0、非法内存访问。
这些异常时硬件的驱动检测到,并通知内核,然后内核在向引发这些异常的进程发送相应信号。
硬件产生:通过kill/raise/alarm/setitmer/sigqueue函数产生。

信号的处理

1、忽略
2、终止进程
3、终止进程并产生core文件
4、捕获信号并处理

信号的捕获

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:信号处理注册函数
signum:信号的编号,1~31也可以是宏
handler:
	SIG_IGN 忽略该信号。
	SIG_DEL 默认处理
	函数指针:当signum信号编号来了会执行handler函数

注意:在UNIX系统上,signal注册的函数只执行一次,执行完后就恢复成默认处理方式,如果想长期使用该函数处理信号,可以在函数结束前再注册一次。
SIGKILL/SIGSTOP 既不能被捕获也不能被处理,SIGSTOP会让进程暂停,当再次受到SIGCONT信号时会继续执行。
普通用户只能给自己的进程发送信号,而root用户可以给任何进程发送信号

发送信号

键盘

Ctrl+c SIGINT
Ctrl+\ SIGQUIT
Ctrl+z SIGTSTP

错误

除零 SIGFPE
非法访问内存 SIGSEGV

命令

kill -signum pid
ps -aux 查看所有进程编号

函数

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
功能:向指定的进程发送信号
pid:进程id
	pid>0 向进程号为pid的进程发送信号
	pid=0 向同一进程组的进程发送信号
	pid=-1 向所有有权利发送信号的进程发送信号
	pid<-1 向进程号为abs(pid)的进程组发送信号
sig:信号的编号
	sig值为0时,kill不会发送信号,但会进行错误检查(检查进程号或进程组id号是否存在)。
	
#include <signal.h>
int raise(int sig);
功能:向当前进程发送信号

暂停和休眠

#include <unistd.h>
int pause(void);
功能:一旦执行进程就会进入无限休眠(暂停),直到遇到信号。
先执行信号处理函数才会从休眠中醒来。

#include <unistd.h>
unsigned int sleep(unsigned int seconds);
功能:休眠指定的秒数,当有信号来临时会提前醒来,提前醒来会返回剩余的秒数,或者睡够了返回0。

时钟

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
功能:告诉内核在seconds秒之后,向当前进程发送SIGALRM信号。
注意:如果之前设定的时间还没有到,则会重新设置(覆盖),并返回之前设置的剩余秒数,返回0表示之前未设置过或之前的还剩下0

信号集与信号屏蔽

信号集

信号的集合,由128位二进制组成,每一位代表一个信号。

#include <signal.h>
int sigemptyset(sigset_t *set);
功能:清空信号集,把所有位设置为0.

int sigfillset(sigset_t *set);
功能:填满信号集,把所有位设置为1.

int sigaddset(sigset_t *set, int signum);
功能:向信号集中添加一个信号

int sigdelset(sigset_t *set, int signum);
功能:从信号集中删除一个信号

int sigismember(const sigset_t *set, int signum);
功能:判断信号集中是否有signum信号

信号屏蔽

当做一些特殊操作时会希望有些信号来,有些信号不要来,而与设置信号忽略不同的是,信号屏蔽只是暂时不来,而可以获取到这一段时间发生了哪些信号。
每个进程都有一个信号掩码(信号集),其中包括了需要屏蔽的信号,可以通过sigprocmask函数,检查、修改进程的信号掩码。

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能:检查、修改进程的信号掩码
how:SIG_BLOCK	设置当前信号集与set的并集为新的信号掩码,往旧的信号集添加新的信号集
	SIG_UNBLOCK	新的信号掩码是当前掩码与set补集的交集,(从信号掩码中删除)
	SIG_SETMASK	把set当作新的信号掩码,重新设置。
set:可以为空,则获取信号掩码。
oldset:旧的信号屏蔽掩码

int sigpending(sigset_t *set);
功能:获取信号屏蔽期间发生的信号,当信号屏蔽解除后就没了。

注意:在信号屏蔽期间发生的信号,无论多少次,只能捕获一次(不可靠信号),可靠信号发生多少次捕获多少次。

带附加信息的信号

#include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
功能:向内核注册信号处理函数
signum:信号编码
act:信号的处理方式
oldact:获取到此信号旧的处理方式,可以为NULL
struct sigaction {
    void     (*sa_handler)(int);	//简单的信号处理函数指针
    void     (*sa_sigaction)(int, siginfo_t *, void *);	//可以带附加信息的信号处理函数指针
    sigset_t   sa_mask;	//当执行信号处理函数时需要屏蔽的信号
    int        sa_flags;
			SA_RESETHAND:信号只处理一次,然后就恢复默认处理方式。
			SA_RESTART:系统调用如果被signum信号中断,自行重启
			SA_NOCLDSTOP: 当子进程暂停时,不用通知父进程。
			SA_NODEFER:当执行信号处理函数时不屏蔽正在处理的信号
			SA_ONSTACK
			SA_SIGINFO:使用第二个函数指针处理信号

    void     (*sa_restorer)(void);	//保留暂不使用
};
	siginfo_t {
           int      si_signo;    /* Signal number */
           int      si_errno;    /* An errno value */
           int      si_code;     /* Signal code */
           int      si_trapno;   /* Trap number that caused
                                    hardware-generated signal
                                    (unused on most architectures) */
           pid_t    si_pid;      /* Sending process ID */
           uid_t    si_uid;      /* Real user ID of sending process */
           int      si_status;   /* Exit value or signal */
           clock_t  si_utime;    /* User time consumed */
           clock_t  si_stime;    /* System time consumed */
           sigval_t si_value;    /* Signal value */
           int      si_int;      /* POSIX.1b signal */
           void    *si_ptr;      /* POSIX.1b signal */
           int      si_overrun;  /* Timer overrun count; POSIX.1b timers */
           int      si_timerid;  /* Timer ID; POSIX.1b timers */
           void    *si_addr;     /* Memory location which caused fault */
           long     si_band;     /* Band event (was int in
                                    glibc 2.3.2 and earlier) */
           int      si_fd;       /* File descriptor */
           short    si_addr_lsb; /* Least significant bit of address
                                    (since kernel 2.6.32) */
	}

#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
功能:信号发送函数,与kill不同的是可以附加一些额外数据
pid:目标进程号
sig:要发送的信号
value:联合,成员可以是整数或指针

计时器

系统为每个进程维护三个计时器

ITIMER_REAL 真实计时器,程序运行实际所用时间
ITIMER_VIRTUAL 虚拟计时器,程序运行在用户态所消耗的时间
ITIMER_PROF 实用计时器,程序在用户态和内核态所消耗的时间
实际时间(真实计时器)=用户时间(虚拟)+内核时间+睡眠时间

#include <sys/time.h>
int getitimer(int which, struct itimerval *curr_value);
功能:获取当前进程的定时器
which:选择使用哪一种定时器
curr_value:
           struct timeval it_value;    /* 第一次触发时钟信号所需要的时间 */
           struct timeval it_interval; /* 每次触发时钟信号所需要的时间 */
struct timeval {
           long tv_sec;                /* seconds */
           long tv_usec;               /* microseconds */
       };
int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);
功能:给当前进程设置定时器,与alarm的区别更精确,
which:可以选择哪个时间段计算(选择使用哪个计时器)。
2010-04-30 16:41:00 liguangyi 阅读数 1118

PHP在运行的时候,直接kill掉,有肯能造成数据的丢失。幸好php模块,有针对signal的处理。


处理方式,首先检查有没有安装 PCNTL 模块


然后可以在一个包含文件中,添加以下代码


global $exitFlag;


$exitFlag = false;

// 增加linux信号量处理
if (DIRECTORY_SEPARATOR != '//') {
    pcntl_signal(SIGTERM, "sig_handler");
    pcntl_signal(SIGHUP,  "sig_handler");
    pcntl_signal(SIGINT,  "sig_handler");
}

// 信号处理函数
function sig_handler($signo)
{
    global $exitFlag;
    echo "signal handler.../n";
    switch ($signo) {
        case SIGTERM :
        case SIGHUP :
        case SIGINT :
            $exitFlag = true;
            break;
        default :
        // handle all other signals
    }
}


最后一个注意点: 在你所运行的php主文件里,添加一行

declare(ticks = 1);


否则这个信号处理是不起作用的。




2013-09-11 16:58:30 shawhe 阅读数 529
信号是Linux编程中非常重要的部分,本文将详细介绍信号机制的基本概念、Linux对信号机制的大致实现方法、如何使用信号,以及有关信号的几个系统调用。

信号机制是进程之间相互传递消息的一种方法,信号全称为软中断信号,也有人称作软中断。从它的命名可以看出,它的实质和使用很象中断。所以,信号可以说是进程控制的一部分。

一、信号的基本概念

本节先介绍信号的一些基本概念,然后给出一些基本的信号类型和信号对应的事件。基本概念对于理解和使用信号,对于理解信号机制都特别重要。下面就来看看什么是信号。

1、基本概念

软中断信号(signal,又简称为信号)用来通知进程发生了异步事件。进程之间可以互相通过系统调用kill发送软中断信号。内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。注意,信号只是用来通知某进程发生了什么事件,并不给该进程传递任何数据。

收 到信号的进程对各种信号有不同的处理方法。处理方法可以分为三类:第一种是类似中断的处理程序,对于需要处理的信号,进程可以指定处理函数,由该函数来处 理。第二种方法是,忽略某个信号,对该信号不做任何处理,就象未发生过一样。第三种方法是,对该信号的处理保留系统的默认值,这种缺省操作,对大部分的信 号的缺省操作是使得进程终止。进程通过系统调用signal来指定进程对某个信号的处理行为。

在进程表的表项中有一个软中断信号域,该域中每一位对应一个信号,当有信号发送给进程时,对应位置位。由此可以看出,进程对不同的信号可以同时保留,但对于同一个信号,进程并不知道在处理之前来过多少个。

2、信号的类型

发出信号的原因很多,这里按发出信号的原因简单分类,以了解各种信号:

(1) 与进程终止相关的信号。当进程退出,或者子进程终止时,发出这类信号。
(2) 与进程例外事件相关的信号。如进程越界,或企图写一个只读的内存区域(如程序正文区),或执行一个特权指令及其他各种硬件错误。
(3) 与在系统调用期间遇到不可恢复条件相关的信号。如执行系统调用exec时,原有资源已经释放,而目前系统资源又已经耗尽。
(4) 与执行系统调用时遇到非预测错误条件相关的信号。如执行一个并不存在的系统调用。
(5) 在用户态下的进程发出的信号。如进程调用系统调用kill向其他进程发送信号。
(6) 与终端交互相关的信号。如用户关闭一个终端,或按下break键等情况。
(7) 跟踪进程执行的信号。

Linux支持的信号列表如下。很多信号是与机器的体系结构相关的,首先列出的是POSIX.1中列出的信号:

信号 值 处理动作 发出信号的原因
----------------------------------------------------------------------
SIGHUP 1 A 终端挂起或者控制进程终止
SIGINT 2 A 键盘中断(如break键被按下)
SIGQUIT 3 C 键盘的退出键被按下
SIGILL 4 C 非法指令
SIGABRT 6 C 由abort(3)发出的退出指令
SIGFPE 8 C 浮点异常
SIGKILL 9 AEF Kill信号
SIGSEGV 11 C 无效的内存引用
SIGPIPE 13 A 管道破裂: 写一个没有读端口的管道
SIGALRM 14 A 由alarm(2)发出的信号
SIGTERM 15 A 终止信号
SIGUSR1 30,10,16 A 用户自定义信号1
SIGUSR2 31,12,17 A 用户自定义信号2
SIGCHLD 20,17,18 B 子进程结束信号
SIGCONT 19,18,25 进程继续(曾被停止的进程)
SIGSTOP 17,19,23 DEF 终止进程
SIGTSTP 18,20,24 D 控制终端(tty)上按下停止键
SIGTTIN 21,21,26 D 后台进程企图从控制终端读
SIGTTOU 22,22,27 D 后台进程企图从控制终端写

下面的信号没在POSIX.1中列出,而在SUSv2列出

信号 值 处理动作 发出信号的原因
--------------------------------------------------------------------
SIGBUS 10,7,10 C 总线错误(错误的内存访问)
SIGPOLL A Sys V定义的Pollable事件,与SIGIO同义
SIGPROF 27,27,29 A Profiling定时器到
SIGSYS 12,-,12 C 无效的系统调用 (SVID)
SIGTRAP 5 C 跟踪/断点捕获
SIGURG 16,23,21 B Socket出现紧急条件(4.2 BSD)
SIGVTALRM 26,26,28 A 实际时间报警时钟信号(4.2 BSD)
SIGXCPU 24,24,30 C 超出设定的CPU时间限制(4.2 BSD)
SIGXFSZ 25,25,31 C 超出设定的文件大小限制(4.2 BSD)

(对于SIGSYS,SIGXCPU,SIGXFSZ,以及某些机器体系结构下的SIGBUS,Linux缺省的动作是A (terminate),SUSv2 是C (terminate and dump core))。

下面是其它的一些信号

信号 值 处理动作 发出信号的原因
----------------------------------------------------------------------
SIGIOT 6 C IO捕获指令,与SIGABRT同义
SIGEMT 7,-,7
SIGSTKFLT -,16,- A 协处理器堆栈错误
SIGIO 23,29,22 A 某I/O操作现在可以进行了(4.2 BSD)
SIGCLD -,-,18 A 与SIGCHLD同义
SIGPWR 29,30,19 A 电源故障(System V)
SIGINFO 29,-,- A 与SIGPWR同义
SIGLOST -,-,- A 文件锁丢失
SIGWINCH 28,28,20 B 窗口大小改变(4.3 BSD, Sun)
SIGUNUSED -,31,- A 未使用的信号(will be SIGSYS)

(在这里,- 表示信号没有实现;有三个值给出的含义为,第一个值通常在Alpha和Sparc上有效,中间的值对应i386和ppc以及sh,最后一个值对应mips。信号29在Alpha上为SIGINFO / SIGPWR ,在Sparc上为SIGLOST。)

处理动作一项中的字母含义如下
A 缺省的动作是终止进程
B 缺省的动作是忽略此信号
C 缺省的动作是终止进程并进行内核映像转储(dump core)
D 缺省的动作是停止进程
E 信号不能被捕获
F 信号不能被忽略

上 面介绍的信号是常见系统所支持的。以表格的形式介绍了各种信号的名称、作用及其在默认情况下的处理动作。各种默认处理动作的含义是:终止程序是指进程退 出;忽略该信号是将该信号丢弃,不做处理;停止程序是指程序挂起,进入停止状况以后还能重新进行下去,一般是在调试的过程中(例如ptrace系统调 用);内核映像转储是指将进程数据在内存的映像和进程在内核结构中存储的部分内容以一定格式转储到文件系统,并且进程退出执行,这样做的好处是为程序员提 供了方便,使得他们可以得到进程当时执行时的数据值,允许他们确定转储的原因,并且可以调试他们的程序。

注意 信号SIGKILL和SIGSTOP既不能被捕捉,也不能被忽略。信号SIGIOT与SIGABRT是一个信号。可以看出,同一个信号在不同的系统中值可能不一样,所以建议最好使用为信号定义的名字,而不要直接使用信号的值。

二、信 号 机 制

上 一节中介绍了信号的基本概念,在这一节中,我们将介绍内核如何实现信号机制。即内核如何向一个进程发送信号、进程如何接收一个信号、进程怎样控制自己对信 号的反应、内核在什么时机处理和怎样处理进程收到的信号。还要介绍一下setjmp和longjmp在信号中起到的作用。

1、内核对信号的基本处理方法

内 核给一个进程发送软中断信号的方法,是在进程所在的进程表项的信号域设置对应于该信号的位。这里要补充的是,如果信号发送给一个正在睡眠的进程,那么要看 该进程进入睡眠的优先级,如果进程睡眠在可被中断的优先级上,则唤醒进程;否则仅设置进程表中信号域相应的位,而不唤醒进程。这一点比较重要,因为进程检 查是否收到信号的时机是:一个进程在即将从内核态返回到用户态时;或者,在一个进程要进入或离开一个适当的低调度优先级睡眠状态时。

内核处理一个进程收到的信号的时机是在一个进程从内核态返回用户态时。所以,当一个进程在内核态下运行时,软中断信号并不立即起作用,要等到将返回用户态时才处理。进程只有处理完信号才会返回用户态,进程在用户态下不会有未处理完的信号。

内 核处理一个进程收到的软中断信号是在该进程的上下文中,因此,进程必须处于运行状态。前面介绍概念的时候讲过,处理信号有三种类型:进程接收到信号后退 出;进程忽略该信号;进程收到信号后执行用户设定用系统调用signal的函数。当进程接收到一个它忽略的信号时,进程丢弃该信号,就象没有收到该信号似 的继续运行。如果进程收到一个要捕捉的信号,那么进程从内核态返回用户态时执行用户定义的函数。而且执行用户定义的函数的方法很巧妙,内核是在用户栈上创 建一个新的层,该层中将返回地址的值设置成用户定义的处理函数的地址,这样进程从内核返回弹出栈顶时就返回到用户定义的函数处,从函数返回再弹出栈顶时, 才返回原先进入内核的地方。这样做的原因是用户定义的处理函数不能且不允许在内核态下执行(如果用户定义的函数在内核态下运行的话,用户就可以获得任何权 限)。

在信号的处理方法中有几点特别要引起注意。第一,在一些系统中,当一个进程处理完中断信号返回用户态之前,内核清除用户区中设 定的对该信号的处理例程的地址,即下一次进程对该信号的处理方法又改为默认值,除非在下一次信号到来之前再次使用signal系统调用。这可能会使得进程 在调用signal之前又得到该信号而导致退出。在BSD中,内核不再清除该地址。但不清除该地址可能使得进程因为过多过快的得到某个信号而导致堆栈溢 出。为了避免出现上述情况。在BSD系统中,内核模拟了对硬件中断的处理方法,即在处理某个中断时,阻止接收新的该类中断。

第二个要 引起注意的是,如果要捕捉的信号发生于进程正在一个系统调用中时,并且该进程睡眠在可中断的优先级上,这时该信号引起进程作一次longjmp,跳出睡眠 状态,返回用户态并执行信号处理例程。当从信号处理例程返回时,进程就象从系统调用返回一样,但返回了一个错误代码,指出该次系统调用曾经被中断。这要注 意的是,BSD系统中内核可以自动地重新开始系统调用。

第三个要注意的地方:若进程睡眠在可中断的优先级上,则当它收到一个要忽略的信号时,该进程被唤醒,但不做longjmp,一般是继续睡眠。但用户感觉不到进程曾经被唤醒,而是象没有发生过该信号一样。

第 四个要注意的地方:内核对子进程终止(SIGCLD)信号的处理方法与其他信号有所区别。当进程检查出收到了一个子进程终止的信号时,缺省情况下,该进程 就象没有收到该信号似的,如果父进程执行了系统调用wait,进程将从系统调用wait中醒来并返回wait调用,执行一系列wait调用的后续操作(找 出僵死的子进程,释放子进程的进程表项),然后从wait中返回。SIGCLD信号的作用是唤醒一个睡眠在可被中断优先级上的进程。如果该进程捕捉了这个 信号,就象普通信号处理一样转到处理例程。如果进程忽略该信号,那么系统调用wait的动作就有所不同,因为SIGCLD的作用仅仅是唤醒一个睡眠在可被 中断优先级上的进程,那么执行wait调用的父进程被唤醒继续执行wait调用的后续操作,然后等待其他的子进程。

如果一个进程调用signal系统调用,并设置了SIGCLD的处理方法,并且该进程有子进程处于僵死状态,则内核将向该进程发一个SIGCLD信号。

2、setjmp和longjmp的作用

前面在介绍信号处理机制时,多次提到了setjmp和longjmp,但没有仔细说明它们的作用和实现方法。这里就此作一个简单的介绍。

在 介绍信号的时候,我们看到多个地方要求进程在检查收到信号后,从原来的系统调用中直接返回,而不是等到该调用完成。这种进程突然改变其上下文的情况,就是 使用setjmp和longjmp的结果。setjmp将保存的上下文存入用户区,并继续在旧的上下文中执行。这就是说,进程执行一个系统调用,当因为资 源或其他原因要去睡眠时,内核为进程作了一次setjmp,如果在睡眠中被信号唤醒,进程不能再进入睡眠时,内核为进程调用longjmp,该操作是内核 为进程将原先setjmp调用保存在进程用户区的上下文恢复成现在的上下文,这样就使得进程可以恢复等待资源前的状态,而且内核为setjmp返回1,使 得进程知道该次系统调用失败。这就是它们的作用。

三、有关信号的系统调用

前面两节已经介绍了有关信号的大部分知 识。这一节我们来了解一下这些系统调用。其中,系统调用signal是进程用来设定某个信号的处理方法,系统调用kill是用来发送信号给指定进程的。这 两个调用可以形成信号的基本操作。后两个调用pause和alarm是通过信号实现的进程暂停和定时器,调用alarm是通过信号通知进程定时器到时。所 以在这里,我们还要介绍这两个调用。

1、signal 系统调用

系统调用signal用来设定某个信号的处理方法。该调用声明的格式如下:
void (*signal(int signum, void (*handler)(int)))(int);
在使用该调用的进程中加入以下头文件:
#include <signal.h>

上述声明格式比较复杂,如果不清楚如何使用,也可以通过下面这种类型定义的格式来使用(POSIX的定义):
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
但这种格式在不同的系统中有不同的类型定义,所以要使用这种格式,最好还是参考一下联机手册。

在调用中,参数signum指出要设置处理方法的信号。第二个参数handler是一个处理函数,或者是
SIG_IGN:忽略参数signum所指的信号。
SIG_DFL:恢复参数signum所指信号的处理方法为默认值。

传递给信号处理例程的整数参数是信号值,这样可以使得一个信号处理例程处理多个信号。系统调用signal返回值是指定信号signum前一次的处理例程或者错误时返回错误代码SIG_ERR。下面来看一个简单的例子:

#include <signal.h>
#include <unistd.h>
#include <stdio.h>
void sigroutine(int dunno) { /* 信号处理例程,其中dunno将会得到信号的值 */
switch (dunno) {
case 1:
printf("Get a signal -- SIGHUP ");
break;
case 2:
printf("Get a signal -- SIGINT ");
break;
case 3:
printf("Get a signal -- SIGQUIT ");
break;
}
return;
}

int main() {
printf("process id is %d ",getpid());
signal(SIGHUP, sigroutine); //* 下面设置三个信号的处理方法
signal(SIGINT, sigroutine);
signal(SIGQUIT, sigroutine);
for (;;) ;
}

其中信号SIGINT由按下Ctrl-C发出,信号SIGQUIT由按下Ctrl-发出。该程序执行的结果如下:

localhost:~$ ./sig_test
process id is 463
Get a signal -SIGINT //按下Ctrl-C得到的结果
Get a signal -SIGQUIT //按下Ctrl-得到的结果
//按下Ctrl-z将进程置于后台
[1]+ Stopped ./sig_test
localhost:~$ bg
[1]+ ./sig_test &
localhost:~$ kill -HUP 463 //向进程发送SIGHUP信号
localhost:~$ Get a signal – SIGHUP
kill -9 463 //向进程发送SIGKILL信号,终止进程
localhost:~$

2、kill 系统调用

系统调用kill用来向进程发送一个信号。该调用声明的格式如下:
int kill(pid_t pid, int sig);
在使用该调用的进程中加入以下头文件:
#include <sys/types.h>
#include <signal.h>

该 系统调用可以用来向任何进程或进程组发送任何信号。如果参数pid是正数,那么该调用将信号sig发送到进程号为pid的进程。如果pid等于0,那么信 号sig将发送给当前进程所属进程组里的所有进程。如果参数pid等于-1,信号sig将发送给除了进程1和自身以外的所有进程。如果参数pid小于- 1,信号sig将发送给属于进程组-pid的所有进程。如果参数sig为0,将不发送信号。该调用执行成功时,返回值为0;错误时,返回-1,并设置相应 的错误代码errno。下面是一些可能返回的错误代码:
EINVAL:指定的信号sig无效。
ESRCH:参数pid指定的进程或进程组不存在。注意,在进程表项中存在的进程,可能是一个还没有被wait收回,但已经终止执行的僵死进程。
EPERM: 进程没有权力将这个信号发送到指定接收信号的进程。因为,一个进程被允许将信号发送到进程pid时,必须拥有root权力,或者是发出调用的进程的UID 或EUID与指定接收的进程的UID或保存用户ID(savedset-user-ID)相同。如果参数pid小于-1,即该信号发送给一个组,则该错误 表示组中有成员进程不能接收该信号。

3、pause系统调用

系统调用pause的作用是等待一个信号。该调用的声明格式如下:
int pause(void);
在使用该调用的进程中加入以下头文件:
#include <unistd.h>

该调用使得发出调用的进程进入睡眠,直到接收到一个信号为止。该调用总是返回-1,并设置错误代码为EINTR(接收到一个信号)。下面是一个简单的范例:

#include <unistd.h>
#include <stdio.h>
#include <signal.h>
void sigroutine(int unused) {
printf("Catch a signal SIGINT ");
}

int main() {
signal(SIGINT, sigroutine);
pause();
printf("receive a signal ");
}

在这个例子中,程序开始执行,就象进入了死循环一样,这是因为进程正在等待信号,当我们按下Ctrl-C时,信号被捕捉,并且使得pause退出等待状态。

4、alarm和 setitimer系统调用

系统调用alarm的功能是设置一个定时器,当定时器计时到达时,将发出一个信号给进程。该调用的声明格式如下:
unsigned int alarm(unsigned int seconds);
在使用该调用的进程中加入以下头文件:
#include <unistd.h>

系 统调用alarm安排内核为调用进程在指定的seconds秒后发出一个SIGALRM的信号。如果指定的参数seconds为0,则不再发送 SIGALRM信号。后一次设定将取消前一次的设定。该调用返回值为上次定时调用到发送之间剩余的时间,或者因为没有前一次定时调用而返回0。

注意,在使用时,alarm只设定为发送一次信号,如果要多次发送,就要多次使用alarm调用。

对于alarm,这里不再举例。现在的系统中很多程序不再使用alarm调用,而是使用setitimer调用来设置定时器,用getitimer来得到定时器的状态,这两个调用的声明格式如下:
int getitimer(int which, struct itimerval *value);
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);
在使用这两个调用的进程中加入以下头文件:
#include <sys/time.h>

该系统调用给进程提供了三个定时器,它们各自有其独有的计时域,当其中任何一个到达,就发送一个相应的信号给进程,并使得计时器重新开始。三个计时器由参数which指定,如下所示:
TIMER_REAL:按实际时间计时,计时到达将给进程发送SIGALRM信号。
ITIMER_VIRTUAL:仅当进程执行时才进行计时。计时到达将发送SIGVTALRM信号给进程。
ITIMER_PROF:当进程执行时和系统为该进程执行动作时都计时。与ITIMER_VIR-TUAL是一对,该定时器经常用来统计进程在用户态和内核态花费的时间。计时到达将发送SIGPROF信号给进程。

定时器中的参数value用来指明定时器的时间,其结构如下:
struct itimerval {
struct timeval it_interval; /* 下一次的取值 */
struct timeval it_value; /* 本次的设定值 */
};

该结构中timeval结构定义如下:
struct timeval {
long tv_sec; /* 秒 */
long tv_usec; /* 微秒,1秒 = 1000000 微秒*/
};

在setitimer 调用中,参数ovalue如果不为空,则其中保留的是上次调用设定的值。定时器将it_value递减到0时,产生一个信号,并将it_value的值设 定为it_interval的值,然后重新开始计时,如此往复。当it_value设定为0时,计时器停止,或者当它计时到期,而it_interval 为0时停止。调用成功时,返回0;错误时,返回-1,并设置相应的错误代码errno:
EFAULT:参数value或ovalue是无效的指针。
EINVAL:参数which不是ITIMER_REAL、ITIMER_VIRT或ITIMER_PROF中的一个。

下面是关于setitimer调用的一个简单示范,在该例子中,每隔一秒发出一个SIGALRM,每隔0.5秒发出一个SIGVTALRM信号:

#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/time.h>
int sec;

void sigroutine(int signo) {
switch (signo) {
case SIGALRM:
printf("Catch a signal -- SIGALRM ");
break;
case SIGVTALRM:
printf("Catch a signal -- SIGVTALRM ");
break;
}
return;
}

int main() {
struct itimerval value,ovalue,value2;
sec = 5;

printf("process id is %d ",getpid());
signal(SIGALRM, sigroutine);
signal(SIGVTALRM, sigroutine);

value.it_value.tv_sec = 1;
value.it_value.tv_usec = 0;
value.it_interval.tv_sec = 1;
value.it_interval.tv_usec = 0;
setitimer(ITIMER_REAL, &value, &ovalue);

value2.it_value.tv_sec = 0;
value2.it_value.tv_usec = 500000;
value2.it_interval.tv_sec = 0;
value2.it_interval.tv_usec = 500000;
setitimer(ITIMER_VIRTUAL, &value2, &ovalue);

for (;;) ;
}

该例子的屏幕拷贝如下:

localhost:~$ ./timer_test
process id is 579
Catch a signal – SIGVTALRM
Catch a signal – SIGALRM
Catch a signal – SIGVTALRM
Catch a signal – SIGVTALRM
Catch a signal – SIGALRM
Catch a signal –GVTALRM

本文简单介绍了Linux下的信号,如果希望了解其他调用,请参考联机手册或其他文档。
2009-07-22 14:25:00 yuzhoudiwang 阅读数 615
信号是进程在运行过程中,由自身产生或由进程外部发过来的消息(事件)。信号是 硬件中断的软件模拟(软中断)。每个信号用一个整型常量宏表示,以SIG开头,比如SIGCHLD、SIGINT等,它们在系统头文 件<signal.h>中定义,也可以通过在shell下键入kill –l查看信号列表,或者键入man 7 signal查看更详细的说明。
信号的生成来自内核,让内核生成信号的请求来自3个地方:
l         用户:用户能够通过输入CTRL+c、Ctrl+/,或者是终端驱动程序分配给信号控制字符的其他任何键来请求内核产生信号;
l         内核:当进程执行出错时,内核会给进程发送一个信号,例如非法段存取(内存访问违规)、浮点数溢出等;
l         进程:一个进程可以通过系统调用kill给另一个进程发送信号,一个进程可以通过信号和另外一个进程进行通信。
由进程的某个操作产生的信号称为同步信号(synchronous signals),例如除0;由象用户击键这样的进程外部事件产生的信号叫做异步信号。(asynchronous signals)。
       进程接收到信号以后,可以有如下3种选择进行处理:
l         接收默认处理:接收默认处理的进程通常会导致进程本身消亡。例如连接到终端的进程,用户按下CTRL+c,将导致内核向进程发送一个SIGINT的信号,进程如果不对该信号做特殊的处理,系统将采用默认的方式处理该信号,即终止进程的执行;
l         忽略信号:进程可以通过代码,显示地忽略某个信号的处理,例如:signal(SIGINT,SIGDEF);但是某些信号是不能被忽略的,
l         捕捉信号并处理:进程可以事先注册信号处理函数,当接收到信号时,由信号处理函数自动捕捉并且处理信号。
 
有两个信号既不能被忽略也不能被捕捉,它们是SIGKILL和SIGSTOP。即进程接收到这两个信号后,只能接受系统的默认处理,即终止线程。
可以用函数signal注册一个信号捕捉函数。原型为:
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
 
signal的第1个参数signum表示要捕捉的信号,第2个参数是个函数指 针,表示要对该信号进行捕捉的函数,该参数也可以是SIG_DEF(表示交由系统缺省处理,相当于白注册了)或SIG_IGN(表示忽略掉该信号而不做任 何处理)。signal如果调用成功,返回以前该信号的处理函数的地址,否则返回SIG_ERR。
sighandler_t是信号捕捉函数,由signal函数注册,注册以后,在整个进程运行过程中均有效,并且对不同的信号可以注册同一个信号捕捉函数。该函数只有一个参数,表示信号值。
示例:
1、  捕捉终端CTRL+c产生的SIGINT信号:
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
 
void SignHandler(int iSignNo)
{
    printf("Capture sign no:%d/n",iSignNo);
}
 
int main()
{
    signal(SIGINT,SignHandler);
    while(true)
        sleep(1);
    return 0;
}
该程序运行起来以后,通过按 CTRL+c将不再终止程序的运行。应为CTRL+c产生的SIGINT信号已经由进程中注册的SignHandler函数捕捉了。该程序可以通过 Ctrl+/终止,因为组合键Ctrl+/能够产生SIGQUIT信号,而该信号的捕捉函数尚未在程序中注册。
2、  忽略掉终端CTRL+c产生的SIGINT信号:
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
 
int main()
{
    signal(SIGINT,SIG_IGN);
    while(true)
        sleep(1);
    return 0;
}
该程序运行起来以后,将CTRL+C产生的SIGINT信号忽略掉了,所以CTRL+C将不再能是该进程终止,要终止该进程,可以向进程发送SIGQUIT信号,即组合键CTRL+/
 
3、  接受信号的默认处理,接受默认处理就相当于没有写信号处理程序:
 
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
 
int main()
{
    signal(SIGINT,DEF);
    while(true)
        sleep(1);
    return 0;
}
在signal处理机制下,还有许多特殊情况需要考虑:
1、  册一个信号处理函数,并且处理完毕一个信号之后,是否需要重新注册,才能够捕捉下一个信号;
2、  如果信号处理函数正在处理信号,并且还没有处理完毕时,又发生了一个同类型的信号,这时该怎么处理;
3、  如果信号处理函数正在处理信号,并且还没有处理完毕时,又发生了一个不同类型的信号,这时该怎么处理;
4、  如果程序阻塞在一个系统调用(如read(...))时,发生了一个信号,这时是让系统调用返回错误再接着进入信号处理函数,还是先跳转到信号处理函数,等信号处理完毕后,系统调用再返回。
 
示例:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
 
int g_iSeq=0;
 
void SignHandler(int iSignNo)
{
    int iSeq=g_iSeq++;
    printf("%d Enter SignHandler,signo:%d./n",iSeq,iSignNo);
    sleep(3);
    printf("%d Leave SignHandler,signo:%d/n",iSeq,iSignNo);
}
 
int main()
{
    char szBuf[8];
    int iRet;
    signal(SIGINT,SignHandler);
    signal(SIGQUIT,SignHandler);
    do{
        iRet=read(STDIN_FILENO,szBuf,sizeof(szBuf)-1);
        if(iRet<0){
            perror("read fail.");
            break;
        }
      szBuf[iRet]=0;
        printf("Get: %s",szBuf);
    }while(strcmp(szBuf,"quit/n")!=0);
    return 0;
}
程序运行时,针对于如下几种输入情况(要输入得快),看输出结果:
1、  CTRL+c] [CTRL+c] [CTRL+c]
2、  [CTRL+c] [CTRL+/]
3、  hello [CTRL+/] [Enter]
4、  [CTRL+/] hello [Enter]
5、  hel [CTRL+/] lo[Enter]
 
针对于上面各种情况,不同版本OS可能有不同的响应结果。
如果要想用程序控制上述各种情况的响应结果,就必须采用新的信号捕获机制,即使用sigaction信号处理机制。
函数原型:
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
sigaction也用于注册一个信号处理函数。
参数signum为需要捕捉的信号;
参数 act是一个结构体,里面包含信号处理函数地址、处理方式等信息。
参数oldact是一个传出参数,sigaction函数调用成功后,oldact里面包含以前对signum的处理方式的信息。
如果函数调用成功,将返回0,否则返回-1
结构体 struct sigaction(注意名称与函数sigaction相同)的原型为:
struct sigaction {
    void (*sa_handler)(int);         // 老类型的信号处理函数指针
void (*sa_sigaction)(int, siginfo_t *, void *);//新类型的信号处理函数指针
sigset_t sa_mask;                 // 将要被阻塞的信号集合
int sa_flags;                         // 信号处理方式掩码
void (*sa_restorer)(void);     // 保留,不要使用。
}
       该结构体的各字段含义及使用方式:
1、字段sa_handler是一个函数指针,用于指向原型为void handler(int)的信号处理函数地址,       即老类型       的信号处理函数;
2、字段sa_sigaction也是一个函数指针,用于指向原型为:
void handler(int iSignNum,siginfo_t *pSignInfo,void *pReserved);
的信号处理函数,即新类型的信号处理函数。
该函数的三个参数含义为:
              iSignNum :传入的信号
              pSignInfo :与该信号相关的一些信息,它是个结构体
              pReserved :保留,现没用
3、字段sa_handler和sa_sigaction只应该有一个生效,如果 想采用老的信号处理机制,就应该让sa_handler指向正确的信号处理函数;否则应该让sa_sigaction指向正确的信号处理函数,并且让字段 sa_flags包含SA_SIGINFO选项。
4、字段sa_mask是一个包含信号集合的结构体,该结构体内的信号表示在进行信号处理时,将要被阻塞的信号。针对sigset_t结构体,有一组专门的函数对它进行处理,它们是:
              #include <signal.h>
        int sigemptyset(sigset_t *set);                                   // 清空信号集合set
        int sigfillset(sigset_t *set);                                 // 将所有信号填充进set中
        int sigaddset(sigset_t *set, int signum);               // 往set中添加信号signum
        int sigdelset(sigset_t *set, int signum);                // 从set中移除信号signum
        int sigismember(const sigset_t *set, int signum); // 判断signnum是不是包含在set中
       例如,如果打算在处理信号SIGINT时,只阻塞对SIGQUIT信号的处理,可以用如下种方法:
              struct sigaction act;
              sigemptyset(&act.sa_mask);
              sigaddset(&act_sa_mask,SIGQUIT);
              sigaction(SIGINT,&act,NULL);
5、  字段sa_flags是一组掩码的合成值,指示信号处理时所应该采取的一些行为,各掩码的含义为:
 
掩码
描述
SA_RESETHAND
处理完毕要捕捉的信号后,将自动撤消信号处理函数的注册,即必须再重新注册信号处理函数,才能继续处理接下来产生的信号。该选项不符合一般的信号处理流程,现已经被废弃。
SA_NODEFER
在处理信号时,如果又发生了其它的信号,则立即进入其它信号的处理,等其它信号处理完毕后,再继续处理当前的信号,即递规地处理。如果sa_flags包含了该掩码,则结构体sigaction的sa_mask将无效!
SA_RESTART
如果在发生信号时,程序正阻塞在某个系统调用,例如调用read()函数,则在处理完毕信号后,接着从阻塞的系统返回。该掩码符合普通的程序处理流程,所以一般来说,应该设置该掩码,否则信号处理完后,阻塞的系统调用将会返回失败!
SA_SIGINFO
指示结构体的信号处理函数指针是哪个有效,如果sa_flags包含该掩码,则sa_sigactiion指针有效,否则是sa_handler指针有效。
 
 
       练习与验证:
针对于先前的5种输入情况,给下面代码再添加一些代码,使之能够进行如下各种形式的响应:
       1 、[CTRL+c] [CTRL+c]时,第1个信号处理阻塞第2个信号处理;
       2 、[CTRL+c] [CTRL+c]时,第1个信号处理时,允许递规地第2个信号处理;
       3 、[CTRL+c] [CTRL+/]时,第1个信号阻塞第2个信号处理;
       4 、read不要因为信号处理而返回失败结果。
 
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
 
int g_iSeq=0;
 
void SignHandlerNew(int iSignNo,siginfo_t *pInfo,void *pReserved)
{
    int iSeq=g_iSeq++;
    printf("%d Enter SignHandlerNew,signo:%d./n",iSeq,iSignNo);
    sleep(3);
    printf("%d Leave SignHandlerNew,signo:%d/n",iSeq,iSignNo);
}
 
int main()
{
    char szBuf[8];
    int iRet;
    struct sigaction act;
    act.sa_sigaction=SignHandlerNew;
    act.sa_flags=SA_SIGINFO;
              //
    sigemptyset(&act.sa_mask);
   sigaction(SIGINT,&act,NULL);
    sigaction(SIGQUIT,&act,NULL);
    do{
        iRet=read(STDIN_FILENO,szBuf,sizeof(szBuf)-1);
        if(iRet<0){
            perror("read fail.");
            break;
        }
        szBuf[iRet]=0;
        printf("Get: %s",szBuf);
    }while(strcmp(szBuf,"quit/n")!=0);
    return 0;
}
 
函数sigaction中设置的被阻塞信号集合只是针对于要处理的信号,例如
struct sigaction act;
                  sigemptyset(&act.sa_mask);
              sigaddset(&act.sa_mask,SIGQUIT);
                  sigaction(SIGINT,&act,NULL);
       表示只有在处理信号SIGINT时,才阻塞信号SIGQUIT;
       函数sigprocmask是全程阻塞,在sigprocmask中设置了阻塞集合后,被阻塞的信号将不能再被信号处理函数捕捉,直到重新设置阻塞信号集合。
       原型为:
       #include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数how的值为如下3者之一:
       a :SIG_BLOCK ,将参数2的信号集合添加到进程原有的阻塞信号集合中
       b :SIG_UNBLOCK ,从进程原有的阻塞信号集合移除参数2中包含的信号
       c :SIG_SET,重新设置进程的阻塞信号集为参数2的信号集
参数set为阻塞信号集
参数oldset是传出参数,存放进程原有的信号集。
示例:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
 
int g_iSeq=0;
 
void SignHandlerNew(int iSignNo,siginfo_t *pInfo,void *pReserved)
{
    int iSeq=g_iSeq++;
    printf("%d Enter SignHandlerNew,signo:%d./n",iSeq,iSignNo);
    sleep(3);
    printf("%d Leave SignHandlerNew,signo:%d/n",iSeq,iSignNo);
}
 
int main()
{
    char szBuf[8];
    int iRet;
    struct sigaction act;
    act.sa_sigaction=SignHandlerNew;
    act.sa_flags=SA_SIGINFO;
    // 屏蔽掉SIGINT 信号,SigHandlerNew 将不能再捕捉SIGINT
sigset_t sigSet;
    sigemptyset(&sigSet);
    sigaddset(&sigSet,SIGINT);
    sigprocmask(SIG_BLOCK,&sigSet,NULL);
              //
    sigemptyset(&act.sa_mask);
    sigaction(SIGINT,&act,NULL);
    sigaction(SIGQUIT,&act,NULL);
    do{
        iRet=read(STDIN_FILENO,szBuf,sizeof(szBuf)-1);
        if(iRet<0){
            perror("read fail.");
            break;
        }
        szBuf[iRet]=0;
        printf("Get: %s",szBuf);
    }while(strcmp(szBuf,"quit/n")!=0);
    return 0;
}
 
原型为:
#include <sys/types.h>
    #include <signal.h>
int kill(pid_t pid, int sig);
       参数pid为将要接受信号的进程的pid
       参数sig为要发送的信号
       如果成功,返回0,否则为-1。
       示例,输入结束后,将通过发送信号SIGQUIT把自己杀掉:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
                  while(true){
               if(getchar()==EOF)
            kill(getpid(),SIGQUIT);
                  }
    return 0;
}
sigqueue也可以发送信号,并且能传递附加的信息。
原型为:
#include <signal.h>
    int sigqueue(pid_t pid, int sig, const union sigval value);
参数pid为接收信号的进程;
参数sig为要发送的信号;
参数value为一整型与指针类型的联合体:
       union sigval {
int   sival_int;
void *sival_ptr;
    };
由sigqueue函数发送的信号的第3个参数value的值,可以被进程的信号处理函数的第2个参数info->si_ptr接收到。
示例1,进程给自己发信号,并且带上附加信息:
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
 
void SignHandlerNew(int signum,siginfo_t *info,void *myact)
{
                  char *pszInfo=(char *)(info->si_ptr);
    printf("Get:%d info:%s/n",signum,pszInfo);
}
 
int main(int argc,char**argv)
{
                  struct sigaction act;  
    union sigval mysigval;
                  int sig;
    char data[]="other info";
                  //
                  if(argc<2){
                      printf("usage: SIGNNUM/n");
        return -1;
                  }
    mysigval.sival_ptr=data;
                  sig=atoi(argv[1]);
    sigemptyset(&act.sa_mask);
          act.sa_sigaction=SignHandlerNew;
                  act.sa_flags=SA_SIGINFO;
                  sigaction(sig,&act,NULL);
                  while(true){
                      printf("wait for the signal/n");
                      sigqueue(getpid(),sig,mysigval);
                      sleep(2);
                  }
}
 
示例2:一个进程向另外一个进程发送信号。注意:发送进程不要将自己进程空间的地址发送给接收进程,因为接收进程接收到地址也访问不到发送进程的地址空间的。
 
示例2信号接收程序:
       #include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
 
void SignHandlerNew(int signum,siginfo_t *info,void *myact)
{
                  printf("Get:%d info:%d/n",signum,info->si_int);
}
 
int main(int argc,char**argv)
{
                  struct sigaction act;
    //
                  if(argc<2){
                      printf("usage: signnum/n");
        return -1;
                  }
    sigemptyset(&act.sa_mask);
    act.sa_sigaction=SignHandlerNew;
                  act.sa_flags=SA_SIGINFO;
    sigaction(atoi(argv[1]),&act,NULL);
                  while(1)
    {
                      printf("wait for the signal/n");
               sleep(2);
    }
                  return 0;
}
      
示例2信号发送程序:
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
 
int main(int argc,char**argv)
{
                  union sigval mysigval;
    int iPid,iSignNo,iData;
                  //
    if(argc<4){
                      printf("usage: pid signnum data/n");
                      return -1;
}
    iPid=atoi(argv[1]);
                  iSignNo=atoi(argv[2]);
    iData=atoi(argv[3]);
    mysigval.sival_int=iData;
                  if(sigqueue(iPid,iSignNo,mysigval)<0)
                      perror("Send signal fail.");
    return 0;
}    
      
Linux下有两个睡眠函数,原型为:
       #include <unistd.h>
        unsigned int sleep(unsigned int seconds);
              void usleep(unsigned long usec);
       函数sleep让进程睡眠seconds秒,函数usleep让进程睡眠usec毫秒。
       sleep 睡眠函数内部是用信号机制进行处理的,用到的函数有:
              #include <unistd.h>
unsigned int alarm(unsigned int seconds);     // 告知自身进程,要进程在seconds秒后自动产生一个//SIGALRM的信号,
int pause(void);                       // 将自身进程挂起,直到有信号发生时才从pause返回
      
       示例:模拟睡眠3秒:
              #include <signal.h>
#include <stdio.h>
#include <unistd.h>
 
void SignHandler(int iSignNo)
{
    printf("signal:%d/n",iSignNo);
}
 
int main()
{
    signal(SIGALRM,SignHandler);
    alarm(3);
    printf("Before pause()./n");
    pause();
    printf("After pause()./n");
    return 0;
}
注意:因为sleep在内部是用alarm实现的,所以在程序中最好不要sleep与alarm混用,以免造成混乱。
Linux为每个进程维护3个计时器,分别是真实计时器、虚拟计时器和实用计时器。
l         真实计时器计算的是程序运行的实际时间;
l         虚拟计时器计算的是程序运行在用户态时所消耗的时间(可认为是实际时间减掉(系统调用和程序睡眠所消耗)的时间);
l         实用计时器计算的是程序处于用户态和处于内核态所消耗的时间之和。
例如:有一程序运行,在用户态运行了5秒,在内核态运行了6秒,还睡眠了7秒,则真实计算器计算的结果是18秒,虚拟计时器计算的是5秒,实用计时器计算的是11秒。
用指定的初始间隔和重复间隔时间为进程设定好一个计时器后,该计时器就会定时地向进程发送时钟信号。3个计时器发送的时钟信号分别为:SIGALRM,SIGVTALRM和SIGPROF。
用到的函数与数据结构:
#include <sys/time.h>
 
//获取计时器的设置
//which指定哪个计时器,可选项为ITIMER_REAL(真实计时器)、ITIMER_VITUAL(虚拟计时器、ITIMER_PROF(实用计时器))
//value为一结构体的传出参数,用于传出该计时器的初始间隔时间和重复间隔时间
//如果成功,返回0,否则-1
int getitimer(int which, struct itimerval *value);
 
//设置计时器
//which指定哪个计时器,可选项为ITIMER_REAL(真实计时器)、ITIMER_VITUAL(虚拟计时器、ITIMER_PROF(实用计时器))
//value为一结构体的传入参数,指定该计时器的初始间隔时间和重复间隔时间
//ovalue为一结构体传出参数,用于传出以前的计时器时间设置。
//如果成功,返回0,否则-1
int setitimer(int which, const struct itimerval *value, struct itimer val *ovalue);
   
struct itimerval {
struct timeval it_interval; /* next value */            // 重复间隔
struct timeval it_value;    /* current value */     // 初始间隔    
};
struct timeval {
long tv_sec;                /* seconds */                    // 时间的秒数部分
long tv_usec;               /* microseconds */        // 时间的微秒部分
};
 
示例:启用真实计时器的进行时钟处理
            #include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>
 
void TimeInt2Obj(int imSecond,timeval *ptVal)
{
ptVal->tv_sec=imSecond/1000;
        ptVal->tv_usec=(imSecond%1000)*1000;
}
 
void SignHandler(int SignNo)
{
printf("Clock/n");
}
 
int main()
{
signal(SIGALRM,SignHandler);
    itimerval tval;
    TimeInt2Obj(1,&tval.it_value);            // 设初始间隔为1毫秒,注意不要为0
              TimeInt2Obj(1500,&tval.it_interval);    // 设置以后的重复间隔为1500毫秒
    setitimer(ITIMER_REAL,&tval,NULL);
               while(getchar()!=EOF);
    return 0;
}

Linux信号总结

阅读数 24

linux的信号

阅读数 357

没有更多推荐了,返回首页