apue学习笔记(第十章 信号)

本章先对信号机制进行综述,并说明每种信号的一般用法。

信号概念

每个信号都有一个名字,这些名字都以3个字符SIG开头。在头文件<signal.h>中,信号名都被定义为正整形常量。

在某个信号出现时,可以按下列3种方式之一进行处理:

1 忽略该信号。大多数信号都可以使用这种方式进行处理,但有两种信号却决不能被忽略:SIGKILL和SIGSTOP(只能执行系统默认动作)。

2 捕获信号。通知内核在某信号发生时,调用一个用户函数对这种时间进行处理。

3 执行系统默认动作。对于大多数信号的系统默认动作是终止该进程。

函数signal

UNIX系统信号机制最简单的接口是signal函数

#include <signal.h>
void (*signal(int signo,void (*func)(int)))(int);

可以使用typedef使其变得简单一点

typedef void Sigfunc(int);Sigfunc *signal(int,Sigfunc *);

第一个int参数是要捕获的信号(整形常量),第二个参数是一个函数指针(处理函数),该函数指针指向的函数返回值是void,参数是int。

下面给出一个简单得信号处理程序:

#include "apue.h"

static void    sig_usr(int);    /* one handler for both signals */

int
main(void)
{
    if (signal(SIGUSR1, sig_usr) == SIG_ERR)
        err_sys("can‘t catch SIGUSR1");
    if (signal(SIGUSR2, sig_usr) == SIG_ERR)
        err_sys("can‘t catch SIGUSR2");
    for ( ; ; )
        pause();
}

static void
sig_usr(int signo)        /* argument is signal number */
{
    if (signo == SIGUSR1)
        printf("received SIGUSR1\n");
    else if (signo == SIGUSR2)
        printf("received SIGUSR2\n");
    else
        err_dump("received signal %d\n", signo);
}

我们使该程序在后台运行,而且用kill命令将信号发送给它:

中断的系统调用

早期UNIX系统的一个特性是:如果进程在执行一个低速系统调用而阻塞期间捕获到一个信号,则该系统调用就被中断不再继续执行。

该系统调用返回出错,其errno设置为EINTR。后面的章节会更多的涉及到被中断的系统调用。

可重入函数

进程捕获到信号并对其进行处理时,进程正在执行的正常指令序列就被信号处理程序临时中断,它首先执行该信号处理程序中的指令。

如果从信号处理程序返回,则继续执行在捕获到信号时进程正在执行的正常指令序列。

在信号处理函数中调用某些函数可能对导致安全问题(其结果是不可预知的),下面列出了这些异步信号安全的函数,没有列入图中的大多数函数是不可重入的。

函数kill和raise

kill函数将信号发送给进程或进程组,raise函数则允许进程向自身发送信号。

#include <signal.h>
int kill(pid_t pid,int signo);
int raise(int signo);

函数alarm和pause

函数alarm设置一个定时器,当定时器超时时,产生SIGALRM信号。

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

pause函数使调用进程挂起直至捕捉到一个信号

#include <signal.h>
int pause(void);

只有执行了一个信号处理程序并从其返回时,pause才返回。此时,pause返回-1,errno设置为EINTR。

信号集

信号集是能表示多个信号的数据结构(sigset_t),下面列出5个处理信号集的函数

#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set,int signo);
int sigdelset(sigset_t *set,int signo);
int siggismember(const sigset_t *set,int signo);

在使用信号集之前,要对该信号集进行初始化(调用sigemptyset或者sigfillset)。

函数sigprocmask

进程的信号屏蔽字规定了当前阻塞而不能递送给该进程的信号集。调用函数sigprocmask可以检测或更改进程的信号屏蔽字。

#include <signal.h>
int sigprocmask(int how,const sigset_t *restrict set,sigset_t *restrict oset);

若oset是非空指针,那么进程的当前信号屏蔽字通过oset返回。

若set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。SIG_BLOCK是或操作,SIG_SETMASK则是赋值操作

函数sigpending

sigpending函数返回一信号集,对于调用进程而言,其中的各信号是阻塞不能递送的,因而也一定是当前未决的。

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

下面展示信号设置和sigprocmask实例

#include "apue.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");

    /*
     * Restore 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 with core file */
    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");
}

进程开始阻塞SIGQUIT信号,保存了当前信号屏蔽字(以便以后恢复),然后休眠5秒。在此期间所产生的退出信号SIGQUIT都被阻塞,不递送至该进程。

5秒休眠后,检查该信号是否是未决的,然后将SIGQUIT设置为不再阻塞。

运行程序,在5s之内键入退出字符Ctril+\(产生SIGQUIT信号),然后在第二个5s之内再次键入退出字符。

函数sigaction

sigaction函数的功能是检查或修改与制定信号相关联的处理动作。此函数取代了UNIX早期版本使用的signal函数。

#include <signal.h>
int sigaction(int signo,const struct sigction *restrict act,struct sigaction *restrict oact);

参数signo是要检测或修改其具体动作的信号编号。若act指针非空,则根据参数act修改其动作。若oact指针非空,则由oact指针返回该信号的上一个动作。

此函数使用下列结构:

struct sigaction {
        void     (*sa_handler)(int);
        void     (*sa_sigaction)(int, siginfo_t *, void *);
        sigset_t   sa_mask;
        int        sa_flags;
};

sa_handler字段包含一个信号捕捉函数的地址。

sa_mask字段说明了一个信号集,在调用该信号捕捉函数之前,这一信号集要加到进程的信号屏蔽字中。仅当从信号捕捉函数返回时将进程的信号屏蔽字恢复为原先值。

sa_flags字段指定对信号进行处理的各个选项。

sa_sigaction字段是一个替代的信号处理程序,当sa_flags设置为SA_SIGINFO时,使用该信号处理程序。

通常按下列方式调用信号处理程序:

void handler(int signo);

在设置了SA_SIGINFO标志,那么按下列凡是调用信号处理程序:

void handler(int signo,siginfo_t *info,void *context);

下面使用sigaction实现signal函数,它力图阻止被中断的系统调用重启动

typedef void Sigfunc(int);Sigfunc* mysignal(int signo,Sigfunc *func)
{

     struct sigaction    act,oact;
     act.sa_handler = func;
     sigemptyset(&act.sa_mask);
     act.sa_flags = 0;
     if(signo == SIGALRM)
     {
 #ifdef SA_INTERRUPT
         act.sa_flags |= SA_INTERRUPT;
 #endif
     }
     else
     {
 #ifdef  SA_RESTART
         act.sa_flags |= SA_RESTART;
 #endif
     }
     if(sigaction(signo,&act,&oact)<0)
         return (SIG_ERR);
     return (oact.sa_handler);
 }

函数sigsetjmp和siglongjmp

之前说明了setjmp和longjmp函数可以用户非局部转移,sigsetjmp跟siglongjmp指定了对信号屏蔽字的作用。

在信号处理程序中进行非局部转移时应当使用这两个函数。

#include <setjmp.h>
int sigsetjmp(sigjmp_buf env,int savemask);
void siglongjmp(sigjmp_buf env,int val);

与setjmp和longjmp函数唯一的区别是sigsetjmp增加了一个参数savemask。

如果savemask非0,则sigsetjmp在env中保存在env中保存进程的当前信号屏蔽字。调用siglongjmp时,从已经保存的env中恢复保存的信号屏蔽字。

函数sigsuspend

sigsuspend用于在接收到某个信号之前,临时用sigmask替换进程的信号屏蔽字,并暂停进程执行,直到捕捉到一个信号而且从该信号处理程序返回,并且进程的信号屏蔽字设置为调用sigsuspend之前的值。

#include <signal.h>
int sigsuspend(const sigset_t *sigmask);

下面显示了保护代码临界区,使其不被特定信号中断的正确方法

#include "apue.h"

static void    sig_int(int);

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

    pr_mask("program start: ");

    if (signal(SIGINT, sig_int) == SIG_ERR)
        err_sys("signal(SIGINT) error");
    sigemptyset(&waitmask);
    sigaddset(&waitmask, SIGUSR1);
    sigemptyset(&newmask);
    sigaddset(&newmask, SIGINT);

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

    /*
     * 代码临界区
     */
    pr_mask("in critical region: ");

    /*
     * Pause, allowing all signals except SIGUSR1.
     */
    if (sigsuspend(&waitmask) != -1)
        err_sys("sigsuspend error");

    pr_mask("after return from sigsuspend: ");

    /*
     * Reset signal mask which unblocks SIGINT.
     */
    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        err_sys("SIG_SETMASK error");

    /*
     * And continue processing ...
     */
    pr_mask("program exit: ");

    exit(0);
}

static void
sig_int(int signo)
{
    pr_mask("\nin sig_int: ");
}

下面是程序运行结果:

函数abort

abort函数的功能是使程序异常终止

#include <stdlib.h>
void abort(void);

此函数将SIGABRT信号发送给调用进程。让进程捕捉SIGABRT信号目的是在进程终止之前由其执行所需的清理操作。默认情况是终止调用进程。

函数system

POSIX.1要求system函数忽略SIGINT和SITQUIT信号,阻塞SIGCHLD。

函数sleep

此函数使调用进程被挂起,直到满足下列条件之一:

(1)已经经过seconds所指定的墙上时钟时间。

(2)调用进程捕捉到一个信号并从信号处理程序返回。

时间: 2024-10-11 18:14:36

apue学习笔记(第十章 信号)的相关文章

APUE学习笔记——10.9 信号发送函数kill、 raise、alarm、pause

转载注明出处:Windeal学习笔记 kil和raise kill()用来向进程或进程组发送信号 raise()用来向自身进程发送信号. #include <signal.h> int kill(pid_t pid,int signo); int raise(int signo); Both return: 0 if OK,?1 on error kill向进程号为pid的进程发送signo信号 能够看出 以下两行代码是等价的: kill(getpid(), signo); raise(sig

APUE学习笔记——10.可靠信号与不可靠信号

首先说明:现在大部分Unix系系统如Linux都已经实现可靠信号. 1~31信号与SIGRTMIN-SIGRTMAX之间并不是可靠信号与不可靠信号的区别,在大多数系统下他们都是可靠信号. 只不过: 1~31信号                              --不支持排队,为普通信号. SIGRTMIN-SIGRTMAX信号 --支持排队,实时信号 不可靠信号 什么是不可靠信号: 不可靠的意思是信号可能丢失或者被错误处理. 在早起系统中,信号存在两大缺陷,导致了信号不可靠. 缺陷一:

APUE学习笔记:第九章 进程关系

9.1 引言 本章将更详尽地说明进程组以及POSIX.1引入的会话的概念.还将介绍登陆shell(登录时所调用的)和所有从登陆shell启动的进程之间的关系. 9.1 终端登陆 系统管理员创建通常名为/etc/ttys的文件,其中每个终端设备都有一行,每一行说明设备名传递给getty程序的参数.当系统自举时,内核创建进程ID为1的进程,依旧是init进程.init进程使系统进入多用户状态.init进程读文件/etc/ttys,对每一个允许登陆的终端设备,init调用一次fork,所生成的子进程则

APUE学习笔记:第七章 进程环境

7.1 引言 本章将学习:当执行程序时,其main函数是如何被调用的:命令行参数是如何传送给执行程序的:典型的存储器布局是什么样式:如何分配另外的存储空间:进程如何使用环境变量:各种不同的进程终止方式等:另外还将说明longjmp和setjmp函数以及它们与栈的交互作用:还将介绍研究进程的资源限制 7.2 main函数 C程序总是从main函数开始执行.当内核执行C程序时,在调用main前先调用一个特殊的启动例程.可执行程序文件将此启动例程指定为程序的起始地址——这是由连接编辑器设置的,而连接编

APUE 学习笔记(二) 文件I/O

1. 文件I/O 对于内核而言,所有打开的文件都通过文件描述符引用,内核不区分文本文件和二进制文件 open函数:O_RDONLY  O_WRONLY  O_RDWR create函数: close函数:关闭一个文件时还会释放该进程加在该文件上的所有记录锁 lseek函数:显式地为一个打开的文件设置其偏移量 每个打开的文件都有一个与其相关联的 "当前文件偏移量",用以度量从文件开始处计算的字节数,通常,读.写操作都从当前文件偏移量处开始,并使偏移量增加所读写的字节数 文件偏移量可以大于

APUE 学习笔记(一) Unix基础知识

1. Unix 体系结构 内核的接口被称为系统调用 公用函数库构建在系统调用接口之上 应用软件既可以调用公用函数库,也可以直接进行系统调用 2. 文件和目录 目录操作函数:opendir---> readdir---> closedir struct dirent 结构体 stat 系统调用 3.程序.进程.线程 程序:存放在磁盘上.并处于某个目录中的一个可执行文件.使用exec系列函数将程序从磁盘读入存储器,并使其执行 进程:程序的执行实体.进程控制的3个函数:fork.exec.waitp

APUE学习笔记:第一章 UNUX基础知识

1.2 UNIX体系结构 从严格意义上,可将操作系统定义为一种软件(内核),它控制计算机硬件资源,提供程序运行环境.内核的接口被称为系统调用.公用函数库构建在系统调用接口之上,应用软件即可使用公用函数库,也可使用系统调用.shell是一种特殊的应用程序,它为运行其他应用程序提供了一个接口 从广义上,操作系统包括了内核和一些其他软件,这些软件使得计算机能够发挥作用,并给予计算机以独有的特性(软件包括系统实用程序,应用软件,shell以及公用函数库等) 1.3  shell shell是一个命令行解

APUE学习笔记:第三章 文件I/O

3.1 引言 术语不带缓冲指的是每个read和write都调用内核中的一个系统调用.这些不带缓冲的I/O函数不是ISO C的组成部分,但是,它们是POSIX.1和Single UNIX Specification的组成部分 3.2 文件描述符 UNIX系统shell使用文件描述符0与进程的标准输入相关联.文件描述符1与标准输出相关联.文件描述符2与标准出错输出相关联. 在依从POSIX的应用程序中,幻数0.1.2应当替换成符号常量STDIN_FILENO,STDOUT_FILENO和STDERR

APUE学习笔记:第二章 UNIX标准化及实现

2.2UNIX标准化 2.2.1 ISO C 国际标准化组织(International Organization for Standardization,ISO) 国际电子技术委员会(International Electrotechnical Commission,IEC) ISO C标准的意图是提供C程序的可移植性,使其能适合于大量不同的操作系统,而不只是UNIX系统.此标准不仅定义了C程序设计语言的语法和语义,还定义了其标准库.因为所有现今的UNIX系统都提供C标准中定义的库例程,所以该

APUE学习笔记:第八章 进程控制

8.1 引言 本章介绍UNIX的进程控制,包括创建新进程.执行程序和进程终止.还将说明进程属性的各种ID-----实际.有效和保存的用户和组ID,以及他们如何受到进程控制原语的影响.本章还包括了解释器文件和system函数.本章最后讲述大多数UNIX系统所提供的进程会计机制.这种机制使我们能够从另一个角度了解进程的控制功能. 8.2 进程标识符 每个进程都有一个非负整型表示的惟一进程ID.因为进程标识符是惟一的,常将其用作其他标识符的一部分以保证其惟一性.虽然是惟一的,但是进程ID可以重用.(大