现在的位置: 首页 > 综合 > 正文

16、深入理解计算机系统笔记:信号

2011年04月13日 ⁄ 综合 ⁄ 共 4497字 ⁄ 字号 评论关闭

1、更高层软件形式的异常,称为unix信号,它允许进程中断其他进程。一个信号(signal)就是一条消息,它通知进程一个某种类型的事件已经在系统中发生了。Linux支持30种不同类型的信号。

wps_clip_image-8745

2、以前,主存储器是用一种称为磁芯存储器(core memory)的技术来实现的。“转储存储器(dumping core)”是一个历史术语,意为把代码和数据存储器段的映像写到磁盘上。

3、传送一个信号到目的进程是由两个不同步骤组成:

1)发送信号:内核通过更新目的进程上下文中的某个状态,发送(delivers/sends)一个信号给目的进程。发送信号可以有如下两种原因:内核检测到一个系统事件,比如除0或子进程终止;一个进程调用了kill函数,显式地要求内核发送一个信号给目的进程。一个进程可以给自己发信号。

2)接收信号:当目的进程被内核强迫以某种方式对信号的发送做出反应时,目的进程就接收了信号。进程可以忽略这个信号,终止,或通过一个称为信号处理程序(signal handler)的用户函数捕获这个信号。

一个只发出而没有被接收的信号叫做待处理信号(pending singal)。在任何时候,一种类型至多只会有一个待处理信号。一个待处理信号只能被接收一次。

4Unix系统提供了大量的机制,用来发送信号给进程;这些机制都是基于进程组(process group)这个概念。

1)进程组 每个进程都只属于一个进程组,由正整数ID来标识。

示例代码

#include <unistd.h>
pid t getpgrp(void);
returns: process group ID of calling process
#include <unistd.h>
pid t setpgid(pid t pid, pid t pgid);
returns: 0 on success, -1 on error.

默认地,一个子进程和它的父进程同属于一个进程组。

2/bin/kill 程序可以向另外的进程发送任意的信号。

kill -9 15213

kill -9 -15213 //一个负的PID会导致信号被发送到PID进程组中的每个进程。

3unix shell 作用作业(job)的抽象概念来表示求值一条命令行而产生的进程。在任何时刻,到多只有一个前台作业和0个或式个后台作业。shell为每个作业创建一个独立的进程组。

示例代码

#include <sys/types.h>
#include <signal.h>
int kill(pid t pid, int sig);
returns: 0 if OK, -1 on error
If pid is greater than zero, then the kill function sends signal number sig to process 
pid.If pid is less than zero, than kill sends signal sig to every process in process group 
abs(pid).
#include <unistd.h>
unsigned int alarm(unsigned int secs);
returns: remaining secs of previous alarm, or 0 if no previous alarm

进程可以通过调用alarm函数向它自己发送SIGALRM信号。

示例代码

#include "csapp.h"

void handler(int sig) 
{
    static int beeps = 0;

    printf("BEEP\n");
    if (++beeps < 5)  
	Alarm(1); /* next SIGALRM will be delivered in 1s */
    else {
	printf("BOOM!\n");
	exit(0);
    }
}

int main() 
{
    Signal(SIGALRM, handler); /* install SIGALRM handler */
    Alarm(1); /* next SIGALRM will be delivered in 1s */

    while (1) {
	;  /* signal handler returns control here each time */
    }
    exit(0);
}
/* $end alarm */

5、接收信号

当内核从一个异常处理程序返回,准备将控制传递给进程p时,它会检查未被阻塞的待处理信号((pending & ˜blocked)的集合。如果这个集合为空,则内核传递控制给p的逻辑控制流中的下一条指令。

如果集合非空,则选择集合中信号k(通常k是最小的),并且强制p接收信号k。收到信号会触发某种行为。进程完成了这个行为后,控制就传递回p的逻辑控制流中的下一条指令。

每个信号都有默认行为:

The process terminates.

The process terminates and dumps core.

The process stops until restarted by a SIGCONT signal.

The process ignores the signal.

进程可以通过signal函数修改和信号相关联的默认行为。惟一的例外是SIGSTOPSIGKILL,它的默认行为是不能修改的。

   The signal function can change the action associated with a signal signum in one

of three ways:

If handler is SIG_IGN, then signals of type signum are ignored.

If handler is SIG_DFL, then the action for signals of type signum reverts to the

default action.

Otherwise, handler is the address of a user-defined function, called a signal handler,

that will be called whenever the process receives a signal of type signum. Changing

the default action by passing the address of a handler to the signal function is

known as installing the handler. The invocation of the handler is called catching the

signal. The execution of the handler is referred to(is called) as handling the signal.

示例代码

#include <signal.h>
typedef void handler t(int)
handler t *signal(int signum, handler t *handler)
returns: ptr to previous handler if OK, SIG ERR on error (does not set errno)

信号不可以用来对其他进程中发生的事件计数。

6、由于“在任何时候,一种类型至多只会有一个待处理信号”,这样,当一个父进程有多个子进程时,如果这些子进程都死掉的话,父进程在回收中可能有回到不到的情况,原书中8.5.4Signal Handling Issues”节讲述了这个问题的处理。

示例代码

/* $begin signal3 */
#include "csapp.h"

void handler2(int sig) 
{
    pid_t pid;
  
    while ((pid = waitpid(-1, NULL, 0)) > 0)
	printf("Handler reaped child %d\n", (int)pid);
    if (errno != ECHILD)
	unix_error("waitpid error");
    Sleep(2);
    return;
}

int main() {
    int i, n;
    char buf[MAXBUF];
    pid_t pid;

    if (signal(SIGCHLD, handler2) == SIG_ERR)
	unix_error("signal error");

    /* parent creates children */
    for (i = 0; i < 3; i++) {
	pid = Fork();
	if (pid == 0) {
	    printf("Hello from child %d\n", (int)getpid());
	    Sleep(1);
	    exit(0);
	}
    }

    /* Manually restart the read call if it is interrupted */
    while ((n = read(STDIN_FILENO, buf, sizeof(buf))) < 0)
	if (errno != EINTR)
	    unix_error("read error");

    printf("Parent processing input\n");
    while (1)
	;

    exit(0);
}
/* $end signal3 */

7、不同系统间,信号处理语义的差异,是unix的一个缺陷,为此,Posix标准定了函数sigaction来统一这个情况。

应用程序可以使用sigpromask函数显式都阻塞和取消选择的信号。在网上的英文版中没有这要叙述;而中文版本有,可以参见中文版本中关于how的设定。sigpromask函数对于同步父子进程是很方便的。注意:子进程继承了它们父进程的被阻塞集合。

示例代码

/* $begin sigint1 */
#include "csapp.h"

void handler(int sig) /* SIGINT handler */
{
    printf("Caught SIGINT\n");
    exit(0);
}

int main() 
{
    /* Install the SIGINT handler */
    if (signal(SIGINT, handler) == SIG_ERR) 
	unix_error("signal error");
    
    pause(); /* wait for the receipt of a signal */
    
    exit(0);
}
/* $end sigint1 */

示例代码

#include <signal.h>
int sigaction(int signum, struct sigaction *act, struct sigaction *oldact);
returns: 0 if OK, -1 on error
/* Signal wrappers */
typedef void handler_t(int);
handler_t *Signal(int signum, handler_t *handler);
void Sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
void Sigemptyset(sigset_t *set);
void Sigfillset(sigset_t *set);
void Sigaddset(sigset_t *set, int signum);
void Sigdelset(sigset_t *set, int signum);
int Sigismember(const sigset_t *set, int signum);

<Computer Systems:A Programmer's Perspective>

【上篇】
【下篇】

抱歉!评论已关闭.