APUE学习之------------信号

在学习一个东西的时候我总是喜欢去问这样做的理由是什么?也喜欢去究竟他的历史。从中你可以发现所有的设计都在不断改进出来的,从来就没有一个设计是一开始就是完美的。好比是人,之初,性也许是善的,如果我们不通过后天的学习去让自己的心灵完美的话,他就只停留在了人的初级阶段了。

对于信号(signal)也是如此,硬件的中断中得出他的模型,然后不断的的去完善它。当一个事件发生时内核就会对该进程发送相应的信号,比如,内存错误了就是发送SIGSEGV,这个也就是我们常看到的段错误。再比如当我们做除零操作是就会发送SIGFPE。具体的信号定义的话可以参照http://www.cs.utah.edu/dept/old/texinfo/glibc-manual-0.02/library_21.html#SEC336 。下面就来看看具体的代码函数。

1.信号的注册。

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

        这里不管你在做什么事情,产生一个软中断后内核deliver一个信号给进程,然后进程马上中止当前的任务去做信号处理相关的动作,但是我之前的事情也不能丢掉啊,等你做完信号处理的紧急的事情之后你应该继续把之前的时候完成 吧。于是乎就出现了可重入函数,等执行完信号处理函数之后,进程再次的回到之前的处理位置接着执行。关于那些事可重入的函数可以参考http://man7.org/linux/man-pages/man7/signal.7.html. 一个解决方案的引入通常带入了另外一个问题,如果这个函数是不可重入的呢?,在某一段代码中我们接收到信号,但是,我们又不想中断当前的函数?直接忽略这个型号吗?NO,操作系统又给我们提供了一些和信号集相关的函数。如果一段代码中你不想接受到某些信号你可以先将其阻挡在外,等处理完之后可以看下该型号是不是在block列表里,如果一个信号在被阻挡的列表了你可以对其单独的处理。这里的处理正的和生活中的某些处理很相似。看来我们的程序设计也是来源于生活的。下面使一些与之相关的函数:

int sigemptyset(sigset_t *set);

int sigfillset(sigset_t *set);

int sigaddset(sigset_t *set, int signum);

int sigdelset(sigset_t *set, int signum);

int sigismember(const sigset_t *set, int signum);
int sigpending(sigset_t *set);

下面是一个简单samplecode

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

static int got_signal = 0;

static void hdl (int sig)
{
    got_signal = 1;
    printf("signal handle hd1");
}

int main (int argc, char *argv[])
{
    sigset_t mask;
    sigset_t orig_mask;

    if (signal(SIGTERM,hdl)) {
        perror ("signal error");
        return 1;
    }

    sigemptyset (&mask);
    sigaddset (&mask, SIGTERM);

    if (sigprocmask(SIG_BLOCK, &mask, &orig_mask) < 0) {
        perror ("sigprocmask");
        return 1;
    }

    sleep (10);
     if (sigpending(&mask) == 0){
        printf("Signal TERM is pending\n");
    }
    if (sigprocmask(SIG_SETMASK, &orig_mask, NULL) < 0) {
        perror ("sigprocmask");
        return 1;
    }

    sleep (1);

    if (got_signal)
        puts ("Got signal");

    return 0;
}

一个函数的引入通常也引入了另一个问题,好像我的bug都是这样产生的,一个设计方案的导入通常会产生新的bug。现在有这样一个问题,我们希望变更一些信号处理函数,当前如果产生SIGA我们去做handA的操作,但是我们希望之后按照handB的操作去处理,最后又回到handA中又应该怎么处理呢?我们会使用以下的处理方式吗?

signal(SIGA, handA);

signal(SIGA, handB);

signal(SIGA, handA);

这种模式的缺点又在哪里呢?由于handA---->handB, handB--->handA的过程中不是原子操作,在handB--->handA的过程中产生一个SIGA信号会出现什么样的情况?这个信号岂不是丢失了。这里提供了两个信号处理函数无缝切换的函数。

int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact);

关于信号这块剩余的最后一个函数就是 sigsuspend了。我们先来看看他出现的理由吧!

现在考虑一下的模型:

.

.

.

sigprocmask(SIG_SETMASK, &oldmask, NULL);

pause();

以上的模型中如果在sigprocmask和pause之间发生了该信号,会发生什么事情?还没有执行pause函数,进程 接受到该信号后也不会被唤醒。最后导致该信号被丢失。导致信号丢失的原因在于sigprocmask和pause不是原子操作,在sigprocmask和pause之间有一个时间段,从而导致了信号丢失的发生。解决这个问题之要将sigprocmask和pause合并成要给原子操作就可以,于是就有了如下函数:

int sigsuspend(const sigset_t *sigmask);

以上是对APUEsignal一章的学习总结,错误之处还望指出。

时间: 2024-12-27 23:15:36

APUE学习之------------信号的相关文章

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可以重用.(大

APUE 学习笔记(九) 高级I/O

1. 非阻塞I/O 低速系统调用时可能会使进程永远阻塞的一类系统调用,包括以下调用: (1)某些文件类型你(网络socket套接字.终端设备.管道)暂无可使用数据,则读操作可能会使调用者永远阻塞 (2)如果数据不能立即被(1)中文件类型接受,则写操作会使调用者永远阻塞 (3)某些进程间通信函数 非阻塞I/O使我们可以调用open.read.write这样的I/O操作,并使这些操作不会永远阻塞,如果这种操作不能完成,则调用立即出错返回 对于一个给定的文件有两种方法对其指定非阻塞I/O: (1)调用