您的位置:首页 > 运维架构 > Linux

Linux系统学习笔记:信号

2013-01-29 21:55 543 查看
Linux系统学习笔记:信号
Unix/Linux Yeolar
2012-05-11 12:54

主题: Linux系统学习笔记

« Linux系统学习笔记:进程

» Linux系统学习笔记:线程

本篇总结信号。信号是软件中断,它提供了一种处理异步事件的方法。

Contents

信号

信号名字和映射
中断的系统调用
不可重入函数

信号集
发送信号
挂起进程
信号处理
进程同步


信号

前面已经介绍过信号。信号被定义为正整数。很多条件可以产生信号:

用户按某些终端键时引发终端产生信号。
硬件异常产生信号。
进程调用 kill 函数可将信号发送给另一个进程或进程组。发送信号和接收信号的进程的所有者必须相同,或发送信号进程的所有者必须是超级用户。
用户可用 kill 命令发送信号给其他进程。
检测到某种软件条件已经发生并应通知有关进程时也产生信号。

信号是异步事件的经典实例。有三种处理信号的方式:

忽略信号。 SIGKILL 和 SIGSTOP 信号是不能忽略的,它们提供了使进程终止或停止的可靠方法。
捕捉信号。这需通知内核在某种信号发生时调用一个用户函数,用户函数中包含希望对事件进行的处理。 SIGKILL 和 SIGSTOP 信号不能被捕捉。
执行系统默认动作。大多数信号的系统默认动作是终止进程。

执行程序时, exec 函数会将原先设为要捕捉的信号改为执行默认动作。
使用 fork 创建进程时,子进程继承父进程的信号处理方式。


信号名字和映射

有一些办法可以获得信号的名字。使用数组 sys_siglist 可以获得指向信号字符串名字的指针。 psignal类似于 perror ,将有关信号的说明输出到标准错误输出。 strsignal 则返回说明字符串。
#include <signal.h>

/* 输出s: sig info\n到标准错误输出 */
void psignal(int sig, const char *s);

#include <string.h>

/* 返回指向描述信号的字符串的指针 */
char *strsignal(int sig);



中断的系统调用

系统调用分为低速系统调用和其他系统调用。低速系统调用是可能会使进程永远阻塞的一类系统调用。包括:

读有些类型文件(管道、终端、网络),且数据不存在。
写这些类型文件,不能立即接受数据。
打开某些类型文件,在某种条件发生之前(终端等待)。
pause 和 wait 函数。
某些 ioctl 操作。
某些进程间通信函数。

磁盘I/O只会暂时阻塞,所以并非低速系统调用。
早期的UNIX系统中,进程在执行低速系统调用而阻塞期间捕捉到一个信号,则系统调用就被中断不再执行,系统调用返回出错, errno 设置为 EINTR 。现在引入了自动重启动功能,Linux系统默认重启动由信号中断的系统调用。


不可重入函数

进程捕捉到信号时,进程在执行的指令序列就被信号处理程序临时中断,执行完信号处理程序中的指令后,若从信号处理程序返回,则继续执行原来在执行的指令序列。但这可能对进程是具有破坏性的,如正在执行 malloc 函数或一些结果存放在静态变量中的函数时被中断,再返回继续执行时就可能得到错误的结果甚至对进程造成破坏。
大多数函数是不可重入的,有三种原因:

使用了静态数据结构。
调用 malloc 和 free 。
标准I/O函数,它们通常使用不可重入的全局数据结构。


信号集

信号集用来告诉内核不产生该信号集中的信号。下面函数用来处理信号集结构 sigset_t 。
#include <signal.h>

/* 初始化set为空集
* @return      成功返回0,出错返回-1 */
int sigemptyset(sigset_t *set);
/* 初始化set包含所有信号
* @return      成功返回0,出错返回-1 */
int sigfillset(sigset_t *set);
/* 将signum添加到set中
* @return      成功返回0,出错返回-1 */
int sigaddset(sigset_t *set, int signum);
/* 从set中删除signum
* @return      成功返回0,出错返回-1 */
int sigdelset(sigset_t *set, int signum);
/* signum在set中返回1,否则返回0,出错返回-1 */
int sigismember(const sigset_t *set, int signum);

前面提到进程的信号屏蔽字规定了对进程阻塞的信号集,用 sigprocmask 函数来检测和更改信号屏蔽字。
#include <signal.h>

/* 改变进程的信号屏蔽字
* @return      成功返回0,出错返回-1 */
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

参数说明:
how
指定如何修改屏蔽字。

SIG_BLOCK :添加 set 中的信号到屏蔽字中。
SIG_UNBLOCK :从信号屏蔽字中删除 set 中的信号。
SIG_SETMASK :将信号屏蔽字设为 set 。

set待设信号集,若为空则不改变信号屏蔽字。oldset若非空,将当前信号屏蔽字保存在 oldset 中。
sigpending 函数返回当前阻塞信号的信号集。
#include <signal.h>

/* 获取当前阻塞信号的信号集
* @return      成功返回0,出错返回-1 */
int sigpending(sigset_t *set);

例:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include "error.h"

static void sig_quit(int);

int main(void)
{
sigset_t    newmask, oldmask, pendmask;

if (signal(SIGQUIT, sig_quit) == SIG_ERR)
err_sys("can't catch SIGQUIT");

/* Block SIGQUIT and save current signal mask. */
sigemptyset(&newmask);
sigaddset(&newmask, SIGQUIT);
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
err_sys("SIG_BLOCK error");

sleep(5);   /* SIGQUIT here will remain pending */

if (sigpending(&pendmask) < 0)
err_sys("sigpending error");
if (sigismember(&pendmask, SIGQUIT))
printf("\nSIGQUIT pending\n");

/* Reset signal mask which unblocks SIGQUIT. */
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
printf("SIGQUIT unblocked\n");

sleep(5);   /* SIGQUIT here will terminate */
exit(0);
}

static void sig_quit(int signo)
{
printf("caught SIGQUIT\n");
if (signal(SIGQUIT, SIG_DFL) == SIG_ERR)
err_sys("can't reset SIGQUIT");
}



发送信号

kill 函数发送信号给进程或进程组, raise 函数发送信号给自己。
#include <sys/types.h>
#include <signal.h>

/* 发送信号给进程或进程组
* @return      成功返回0,出错返回-1 */
int kill(pid_t pid, int sig);

#include <signal.h>

/* 发送信号给进程或进程组
* @return      成功返回0,出错返回-1 */
int raise(int sig);

raise(sig) 相当于 kill(getpid(), sig) 。
pid 参数有四种情况:

> 0 时,将信号发送给进程ID等于 pid 的进程。
= 0 时,将信号发送给和发送进程在同一进程组的所有进程,且需要具有向这些进程发送信号的权限。
< 0 时,将信号发送给进程组ID等于 pid 绝对值的进程,且需要具有向这些进程发送信号的权限。
= -1 时,将信号发送给所有进程,且需要具有向这些进程发送信号的权限。

所有进程中不包括系统进程。
关于权限,超级用户可将信号发送给任一进程,非超级用户发送者的实际或有效用户ID须等于接收者的实际或有效用户ID。一个特例是发送信号 SIGCONT 时进程可将它发送给同一会话的所有进程。
0为空信号,做参数时不发送信号。 kill 向不存在的进程发送空信号时返回-1,设置 errno 为 ESRCH 。
alarm 函数可以设置一个闹钟,当闹钟超时时产生 SIGALRM 信号,默认终止调用 alarm 的进程。
#include <unistd.h>

/* 设置一个计时器
* @return      0或上次设置的闹钟时间的剩余秒数 */
unsigned int alarm(unsigned int seconds);

每个进程只能有一个闹钟。如果调用 alarm 时,有一个未超时的闹钟,则该闹钟被代替或取消(本次调用参数为0时),剩余时间作为本次调用的返回值。
abort 函数可以发送 SIGABRT 信号到调用进程,相当于 raise(SIGABRT) ,它使程序终止。


挂起进程

pause 函数使调用进程挂起直到捕捉到一个信号。
#include <unistd.h>

/* 挂起进程
* @return      -1,并设errno为EINTR */
int pause(void);

执行了信号处理程序并返回时, pause 才返回。
sigsuspend 函数可以将设置信号屏蔽字和挂起进程组成为一个原子操作,这样就可以正确地实现对某个信号解除阻塞,然后挂起等待以前被阻塞的信号的发生。
#include <signal.h>

/* 将进程的信号屏蔽字设为mask,挂起进程,捕捉到信号返回时恢复信号屏蔽字为之前的值
* @return      -1,并设errno为EINTR */
int sigsuspend(const sigset_t *mask);

sleep 函数也挂起进程,直到经过指定秒数或捕捉到一个信号并从信号处理程序返回。 usleep 提供微秒级的时间粒度。
#include <unistd.h>

/* 挂起进程
* @return      0或剩余秒数 */
unsigned int sleep(unsigned int seconds);
/* 挂起进程
* @return      成功返回0,出错返回-1 */
int usleep(useconds_t usec);

还有一个 nanosleep 函数,它可以提供纳秒级的时间粒度。
#include <time.h>

/* 挂起线程
* @return      成功返回0,中断或出错返回-1 */
int nanosleep(const struct timespec *req, struct timespec *rem);

req 指定休眠时间, rem 不为 NULL 时被设置为剩余时间。结构 timespec 的定义如下:
struct timespec {
time_t  tv_sec;     /* 秒数 */
long    tv_nsec;    /* 纳秒数 */
}



信号处理

sigaction 函数可以检查或修改指定信号的处理动作,它取代了 signal 函数。
#include <signal.h>

/* 检查或修改信号的处理动作
* @return      成功返回0,出错返回-1 */
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

若 act 非空,则修改信号的动作;若 oldact 非空,则获取信号的上一个动作。
结构 sigaction 的定义如下:
struct sigaction {
void        (*sa_handler)(int); /* 信号捕捉函数,或SIG_IGN, SIG_DFL */
void        (*sa_sigaction)(int, siginfo_t *, void *); /* 替代动作 */
sigset_t    sa_mask;            /* 额外要阻塞的信号 */
int         sa_flags;           /* 信号选项 */
void        (*sa_restorer)(void);
};

在 sa_handler 被调用之前, sa_mask 和 signum 信号被加到信号屏蔽字中,从信号捕捉函数返回时再将信号屏蔽字恢复为原值。
sa_flags 字段的可选项包括:

SA_INTERRUPT ,该信号中断的系统调用不会自动重启动。
SA_NOCLDSTOP ,对 SIGCHLD 信号,子进程停止或从停止继续时不产生该信号;子进程终止时仍产生该信号。
SA_NOCLDWAIT ,对 SIGCHLD 信号,子进程终止时不创建僵死进程。若进程调用 wait ,则进程阻塞,直到所有子进程终止,然后返回-1, errno 设为 ECHILD 。
SA_NODEFER ,执行信号捕捉函数时,不阻塞 signum ,除非在 sa_mask 中包含 signum 。
SA_ONSTACK ,若用 sigaltstack 函数声明了一替换栈,则将 signum 传递给替换栈上的进程。
SA_RESETHAND ,在信号捕捉函数入口处,将 signum 的处理方式复位为 SIG_DFL ,并清除SA_SIGINFO 标志,但不能复位 SIGILL 和 SIGTRAP 的配置。
SA_RESTART ,该信号中断的系统调用会自动重启动。
SA_SIGINFO ,对信号处理程序提供附加信息:指向 siginfo 结构的指针和指向进程上下文标识符的指针。

使用了 SA_SIGINFO 选项时,使用替代的 sa_sigaction 信号捕捉函数。 siginfo_t 结构的定义参考手册。
可以用 sigaction 函数实现 signal 函数:
sighandler_t signal(int signum, sighandler_t handler)
{
struct sigaction action, oldaction;
action.sa_handler = handler;
sigemptyset(&action.sa_mask);
action.sa_flags = SA_RESTART;
if (sigaction(signum, &action, &oldaction) < 0)
return SIG_ERR;
return (oldaction.sa_handler);
}



进程同步

可以用信号实现父子进程间的同步。
下面是用信号解决竞争条件的版本。 SIGUSR1 由父进程发送给子进程, SIGUSR2 由子进程发送给父进程。这个版本适合在等待信号时休眠,如果在等待信号时希望调用其他系统函数,一般需要使用多线程。

static volatile sig_atomic_t sigflag; /* set nonzero by sig handler */
static sigset_t newmask, oldmask, zeromask;

static void sig_usr(int signo)  /* one signal handler for SIGUSR1 and SIGUSR2 */
{
sigflag = 1;
}

void TELL_WAIT(void)
{
if (signal(SIGUSR1, sig_usr) == SIG_ERR)
err_sys("signal(SIGUSR1) error");
if (signal(SIGUSR2, sig_usr) == SIG_ERR)
err_sys("signal(SIGUSR2) error");
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGUSR1);
sigaddset(&newmask, SIGUSR2);

/* Block SIGUSR1 and SIGUSR2, and save current signal mask. */
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
err_sys("SIG_BLOCK error");
}

void TELL_PARENT(pid_t pid)
{
kill(pid, SIGUSR2);     /* tell parent we're done */
}

void WAIT_PARENT(void)
{
while (sigflag == 0)
sigsuspend(&zeromask);  /* and wait for parent */
sigflag = 0;

/* Reset signal mask to original value. */
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
}

void TELL_CHILD(pid_t pid)
{
kill(pid, SIGUSR1);         /* tell child we're done */
}

void WAIT_CHILD(void)
{
while (sigflag == 0)
sigsuspend(&zeromask);  /* and wait for child */
sigflag = 0;

/* Reset signal mask to original value. */
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: