《UNIX环境高级编程》读书笔记之信号(1)

1.信号的概念

信号时软中断,它提供了一种处理异步时间的方法。

很多条件都会产生信号:

(1)用户按某些键时,引发终端产生信号。

(2)硬件异常产生信号:除数0,无效的内存引用等。

(3)进程调用kill,可以将任意信号发送给任意进程或进程组。

(4)当检测到某种软件条件已经发生时。例如SIGURG,SIGPIPE和SIGALRM。

当某种信号出现时,可以告诉内核使用下列三种方式来处理:

(1)忽略此信号。

(2)捕捉信号。为了做到这一点,用户需之前就定义好相应的处理函数,并设定函数为特定信号的处理函数。

(3)执行系统默认的动作。下图给出了系统默认的动作。注意,对大部分信号系统默认的处理动作都是终止进程。

2.函数signal

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

#include <signal.h>

void (*signal(int signo,void (*func)(int)))(int);

signo的值为信号名。func的值是常量SIG_IGN,常量SIG_DEF或当接到此信号后要调用的函数的地址。如果指定SIG_IGN,则表示忽略此信号。如果指定SIG_DFL,则表示按默认的操作(上图)处理此信号。当指定函数地址时,则在信号发生时调用该函数。

3.可重入函数

Single UNIX Specification说明了再信号处理程序中保证调用安全的函数。这些函数时可重入的并被称为是异步信号安全的。

应当了解,即使信号处理函数都是调用的可重入函数。但每个线程都只有一个errno变量,所以信号处理程序可能会修改其原来的值。因此,作为一个通用的规则当在信号处理函数中调用上述函数时,应先保存errno的值,调用完后再将其恢复。

4.可靠信号术语和语义

当造成信号的事件发生时,为进程产生一个信号。当一个信号产生时,内核通常在进程表中以某种形式设置一个标识,我们就说向进程递送了一个信号。在信号产生和递送之间的时间间隔内,称信号是未决的。

进程可以选用“阻塞信号递送”。如果为进程产生了一个阻塞的信号,而且对该信号的动作是系统默认动作或捕捉该信号,则该信号将保持未决状态。直到该进程为此信号解除了阻塞,或将对此信号的动作更改为忽略。内核在递送一个原来被阻塞的信号给进程时,才决定对它的处理方式。于是进程在信号递送给它之前仍可改变对信号的动作。

每个进程都有一个信号屏蔽字,它规定了当前要阻塞递送到该进程的信号集。对于每种可能的信号,屏蔽字中都有一位与之对应。

5.函数kill和raise

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

#include<signal.h>

int kill(pid_t pid,int signo);

int raise(int signo)

对于kill函数:(1)pid>0时,该信号发送给id为pid的信号。

(2)pid=0时,该信号发送给与发送进程属同一进程组的所有进程。

(3)pid<0时,将该信号发送给其进程组ID等于pid绝对值的进程。

(4)pid=-1时,将该信号发送给发送进程有权限向他们发送的所有进程。

6.函数alarm和pause

使用alarm函数可以设置一个定时器,当定时器超时时,产生SIGALRM信号。如果忽略或不捕捉此信号,则其默认动作时终止调用该alarm函数的进程。

#include <unistd.h>

unsigned int alarm(unsigned int seconds);

pause函数使调用进程挂起直至捕捉到一个信号。只有执行了一个信号处理程序并从其返回时,pause才返回。

#include <unistd.h>

int pause(void);

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

static void sig_alrm(int signo)
{

}

unsigned int sleep(unsigned int seconds)
{
	if(signal(SIGALRM,sig_alrm) == SIG_ERR)
		return seconds;
	alarm(seconds);
	pause();
	return

7.信号集

我们需要由一个能表示多个信号的数据类型——信号集。POSIX.1定义数据类型sigset_t以包含一个信号集,并且定义了下列5个处理信号的函数。

#include <signal.h>

int sigemptyset(sigset_t *set);

int sigfillset(sigset_t *set);

int sigaddset(sigset_t *set);

int sigdelset(sigset_t *set);

int sigismember(const
sigset_t *set,int signo);

8.函数sigprocmask

函数sigprocmask可以检测或更改程序的信号屏蔽字。

#include <signal.h>

int sigprocmask(int how,const sigset_t * restrict set,sigset_t *restrict oset)

如果oset是非空指针,则进程的当前信号屏蔽字通过oset返回。如果set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。how可选SIG_BLOCK,SIG_UNBLOCK,SIG_SETMASK。

特别注意,sigprocmask是仅针对单线程定义的。处理多线程进程中信号的屏蔽使用另一个函数。

例:

void pr_mask(const char *str)//打印当前被阻塞的信号
{
        sigset_t sigset;
        int errno_save;

        errno_save = errno; /* we can be called by signal handlers */
        if (sigprocmask(0, NULL, &sigset) < 0)
                perror("sigprocmask error");

        printf("mask: %s", str);
        if (sigismember(&sigset, SIGINT)) printf("SIGINT ");
        if (sigismember(&sigset, SIGQUIT)) printf("SIGQUIT ");
        if (sigismember(&sigset, SIGUSR1)) printf("SIGUSR1 ");
        if (sigismember(&sigset, SIGUSR2)) printf("SIGUSR2 ");
        if (sigismember(&sigset, SIGALRM)) printf("SIGALRM ");

        /* remaining signals can go here */

        printf("\n");
        errno = errno_save;
}

9.函数sigpending

sigpending函数返回一信号集,对于调用进程而言,其中的各信号是阻塞不能传递的保持未决状态的信号。该信号通过set参数返回。

#include <signal.h>

int sigpending(sigset_t *set);

例:

#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");
	sigemptyset(&newmask);
	sigaddset(&newmask,SIGQUIT);
	if(sigprocmask(SIG_BLOCK,&newmask,&oldmask) < 0)
		err_sys("SIG_BLOCK error");

	sleep(5);

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

	if(sigprocmask(SIG_SETMASK,&oldmask,NULL) < 0)
		err_sys("SIG_SETMASK error");
	printf("SIGQUIT unblocked\n");

	sleep(5);
	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");
}
时间: 2024-10-11 12:56:34

《UNIX环境高级编程》读书笔记之信号(1)的相关文章

UNIX环境高级编程(阅读笔记)---多线程信号

多线程信号 1.默认情况下,信号将由主进程接收处理,就算信号处理函数是由子线程注册的 2. 每个线程均有自己的信号屏蔽字,可以使用sigprocmask函数来屏蔽某个线程对该信号的响应处理,仅留下需要处理该信号的线程来处理指定的信号. 3. 对某个信号处理函数,以程序执行时最后一次注册的处理函数为准,即在所有的线程里,同一个信号在任何线程里对该信号的处理一定相同 4. 可以使用pthread_kill对指定的线程发送信号 5. 在主进程中对sigmask进行设置后,主进程创建出来的线程将继承主进

UNIX环境高级编程学习笔记(第一章UNIX基础知识)

总所周知,UNIX环境高级编程是一本很经典的书,之前我粗略的看了一遍,感觉理解得不够深入. 听说写博客可以提高自己的水平,因此趁着这个机会我想把它重新看一遍,并把每一章的笔记写在博客里面. 我学习的时候使用的平台是Windows+VMware+debian,使用secureCRT来连接(可以实现多个终端连接). 因为第一章是本书大概的描述,所以第一章的我打算写得详细一点,而且书本的原话占的比例会比较多,重点的东西会用粗体显示出来. 1.1  引言 所有操作系统都为他们所运行的程序提供服务.典型的

Unix环境高级编程学习笔记(五):进程控制

1 getpid函数,getppid函数,得到进程id,得到父进程id #include<unistd.h> pid_t getpid(void) pid_t getppid(void) uid_t getuid(void)得到实际用户id uid_t geteuid(void)得到有效用户id gid_t getgid(void)得到实际组id gid_t getegid(void)得到有效组id 2 fork函数,当前进程创建新进程 #include<unistd.h> pid

《UNIX环境高级编程》笔记——3.文件IO

一.引言 说明几个I/O函数:open.read.write.lseek和close,这些函数都是不带缓冲(不带缓冲,只调用内核的一个系统调用),这些函数不输入ISO C,是POSIX的一部分: 多进程共享资源(包括文件)时,会有很多额外的烦恼,需要对共享资源.原子操作等概念深入理解,需要理解涉及的内核有关数据结构,这些数据结构对理解文件.共享有重要作用: 最后介绍dup.fcntl.sync.fsync和ioctl函数. 二.文件描述符 open或creat文件时,内核--文件描述符fd-->

Unix环境高级编程学习笔记(七):线程

1 线程包含线程ID,一组寄存器的值,栈,调度优先级和策略,信号屏蔽字,errno变量,以及线程私有数据.进程的所有信息对于该进程的所有线程都是共享的,包括可执行程序文本,程序全局内存和堆内存,栈以及文件描述符. 线程可以通过pthread_self函数获得自身线程ID #include<pthread.h> pthread_t pthread_self(void) 新增进程可以通过pthread_create函数创建 #include <pthread.h> int pthrea

Unix环境高级编程学习笔记(四):进程环境

1 exit函数与_Exit函数 #include<stdlib.h> void exit(int status) void _Exit(int status) 这两个函数的不同之处在于exit函数先执行清理工作后再进入内核(清理I/O缓冲),_Exit函数直接进入内核 2 atexit函数,登记函数,在exit的时候执行 int atexit(void (* func) (void)); 被登记的函数称为终止处理函数,这些函数的调用顺序与登记顺序相反,如果一个函数被登记多次,也会被调用多次

UNIX环境高级编程第10章信号10.3singal函数

// signals/sigusr.c 10-1 #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_E

Unix环境高级编程学习笔记(三):标准I/O , 系统数据文件和信息

1 标准I/O函数不同于read,write函数,是其在流上进行操作, 当首次调用标准I/O函数时,系统会首先调用malloc,为流创造缓冲区, 2 fopen函数 #include<stdio.h> file * fopen(const char* pathname, const char * restrict name); 打开返回指针,出错返回NULL, type的取指有r(读),w(写),a(追加),r+/w+(读+写),a+(读+写+追加) int fclose(file* fp)

《Unix环境高级编程》读书笔记 第7章-进程环境

1. main函数 int main( int argc, char *argv[] ); argc是命令行参数的数目,包括程序名在内 argv是指向参数的各个指针所构成的数组,即指针数组 当内核执行C程序时(使用exec函数),在调用main前先调用一个特殊的启动例程.可执行程序文件将此启动例程指定为程序的起始地址——这是由连接器设置的,而连接器则是由C编译器调用.启动例程从内核取得命令行参数和环境变量值,然后按上述方式调用main函数做好安排. 2. 进程终止 有8种方式使进程终止,其中5种

《Unix环境高级编程》读书笔记 第3章-文件I/O

1. 引言 Unix系统的大多数文件I/O只需用到5个函数:open.read.write.lseek以及close 本章描述的函数经常被称为不带缓冲的I/O.术语不带缓冲指的是在用户的进程中对其不会自动缓冲,每个read和write都调用内核中的一个系统调用.但是,所有磁盘I/O都要经过内核的块缓存区(也称为内核的缓冲区高速缓存).唯一例外的是对原始磁盘设备的I/O. 2. 文件描述符 对于内核而言,所有打开的文件都通过文件描述符引用.文件描述符是一个非负整数,其变化范围是0~OPEN_MAX