linux信号

linux的信号
更新:2013-09-03
//


好久没有写博客了。

下面是我在一次对linux信号的学习,是查阅,交流和个人理解所得,说到一些地
方我会想到一些问题,可以当做一次学习的过程。因为自己对操作系统懂的不多,
的理解也不够深刻,内容中存在错误或不足难免,希望大家能够指出纠正。文中
还有很多地方对一些概念的叙述是点到即止。计算机系统非常复杂,是个大课题,
非一时能学好,非一时能说清,要想了解更多,记得动手查阅资料。

因文章相对较长,给个目录。
1)信号的概述
2)信号的分类
3)操作系统对信号机制的支持
4)信号的生命周期。
5)《linux内核设计的艺术》关于信号的范例。

6)一些相关函数

7)信号的使用

8)参考资料


1)信号的概述

    信号的机制产生的历史及发展过程。
        (在网上搜过,但我找到的资料不多,特别是信号的产生历史。不过信号
            这东西肯定不是突然间蹦出来的^_^)

    信号本质是在软件层次上对硬件中断机制的一种模拟。
        (思考一下硬件的中断和软件层的中断区别?)

    信号机制提供一种处理异步事件的方法。
        一个进程不用通过任何操作等待信号的到来。
        一个进程任何时候都可以发送信号。
        (在此处注意与信号有关的权限问题。root可以向任意进程发送信号,
            非root权限的进程只能向属同一session或者同一个用户进程发送信
            号。内核也可以因为内部事件给进程发送信号。)

    信号是进程间唯一的异步通信方式。
        (这句话我在《linux c编程实战》看到,“唯一”,为什么?
            通信方式比较:
            管道,有名管道,信号量,消息队列,信号,共享内存,套接字,
            等等。同步通信?异步通信?)
    
    很多的信号与机器体系结构密切相关。
        信号机制虽然是软件层次的一个东西,但它是操作系统的一种机制,
        而操作系统与机器是密切相关。作为进程间的一种通信方式,作为
        内核和用户进程的一种通信方式,信号自然和机器体系相关性很大。
    
    硬件中断、软中断和信号的异同。
        软中断是利用硬件中断的概念,用软件的方式进行模拟,实现宏观上的
        异步执行效果,很多情况下,软中断和“信号”有些类似,同时软中断有
        时和硬中断相对应的,“硬中断是外部设备对CPU的中断”,“软中断通常
        是硬中断服务程序对内核的中断”,“信号则是由内核(或其他进程)对
        某个进程的中断”(linux内核源代码情景分析》第三章)。
                                                            ——百度百科

        软中断能修改特权级和根据中断号查找中断向量表,是实现系统API函数
        调用的手段。

        硬中断,主要是CPU和外设的一种沟通方式,不多说,自己查阅。

2)信号的分类。

    2.1)可靠性分类。

        2.1.1)不可靠信号。
            早期的uniux系统的每个信号有确定用途和含义。
            早期的linux信号机制是从unix系统中继承来的,31个。
            早期的不可靠信号的一些特性:
                a.在处理信号时不能屏蔽相同的信号再次发生。
                b.不支持排队,信号丢失。
                c.每次进程处理信号时,会将信号处理函数重置为缺省。

        2.1.2)可靠信号。
            实践证明需要对原有信号进行改造和扩充。
            原来定义的信号已有许多应用。
            linux根据POSIX.4定义新的信号,可靠信号。
            用一对宏SIGRTMIN(34)~SIGRTMAX(64)来限定信号的取值范围。
            可靠信号的一些特性:
                a.支持处理时屏蔽本信号
                b.支持排队,信号不丢失。
                c.携带信息。
                d.优先级。对于多个可靠信号,信号值越小被递送的优先级越高。
                e.信号处理函数在处理信号后不会被重置为缺省。

        在自己的机子上试验了可靠信号和不可靠信号,它们都可用sigaction()
        设置信号处理方法和用sigqueue()发送信号。
        现在的不可靠信号和可靠信号的区别:是否支持排队。

        这里简单说一下“排队”。信号支持排队就是说信号在未被处理前,可以多
        次被记录(用术语说就是信号可以在未决信号集中注册多次)。

    2.2)时间关系分类

        2.2.1)非实时信号。
            即不可靠信号。

        2.2.2)实时信号。
            即可靠信号。

3)操作系统对信号机制的支持
    信号能实现异步通信,是因为操作系统对信号机制支持。普通程序内部不
    需实现信号接收和发送的逻辑,只需告诉操作系统对要对哪个信号进行响
    应和怎么样响应。

    操作系统主要有三方面的支持:
    3.1)支持信号的发送和接收。
        3.1.1)linux系统的进程管理结构中设置了接收信号的数据结构。
            每当系统接收到信号,便会设置相应的信号位,还有信号
            信息链,用于记录和信号有关的信息。在下边会较为详细说到

        3.1.2)信号的发送可以通过中断服务程序或相关的库函数。

    3.2)系统及时检测到进程接收的信号。
        3.2.1)在进程结束系统调用(内核态),返回(用户态)前。
        3.2.2)时钟中断,中断服务程序结束前。

    3.3)系统支持对信号的处理。
        3.3.1)在用户不需时,信号处理函数不干扰用户进程执行。
        3.3.2)用户程序需要处理信号时,用户进程停止执行转而执行信号
            处理函数,在处理函数执行完毕,用户进程能恢复“暂停的现场”。

4)信号的生命周期。
    传说中的信号的一生大概会有怎样的经历呢?可以先瞅瞅:
        信号诞生。
        信号在进程中注册。
        信号的执行和注销。

    4.1)信号的诞生。
        本来想来个硬软件来源的分类,但发现有不少概念仍模糊,
        于是在这里就举一些例子。
            )硬件故障。
            )硬件异常,除0操作。
            )无效内存引用。
            )用户输入,如键盘Ctrl+c。
            )用户输入kill命令。
            )进程调用kill或sigqueue函数。
            )进程的终止。
            )当检测到某种软件条件已经具备时发出信号,如alarm,settimer。
            )程序错误,非法运算等操作。

        硬件和软件信号来源的比较
        信号是软件层的东西,那怎么和硬件关联呢?软件不是凭空就能运转
        的东西,特别是操作系统软件是和硬件密切相关的。
        (硬软件的信号来源的不同和共同点?
                硬件:这里是中断服务程序给进程发送信号。
                软件:通过特定的库函数给进程发送信号。
                共同点:最终操作系统会接收信号,并发送给目标进程。)
    
        在linux终端输入kill -l命令可以看到系统支持的信号哦。大家注意,
        没有0值信号,POSIX.1将该值信号为空信号,它对kill()函数有另外的用
        途(更多内容自己查)。通过kill -l命令,有兴趣的人可以将你看到的
        非实时信号(1~31)的产生、缺省处理方式等等查一下,还有实时信号。

    4.2)信号在进程中注册。
        进程管理块(PCB)
        在进程表的表项中有一个软中断信号域,该域中每一位对应一个信号。
        内核给一个进程发送软中断信号的方法,是在进程所在的进程表项的信
        号域设置对应于该信号的位。如果进程没有对该信号忽略,则设置该位。
        如果信号发送给一个正在睡眠的进程,如果进程睡眠在可被中断的优先
        级上,则唤醒进程;否则仅设置进程表中信号域相应的位,而不唤醒进
        程。如果发送给一个处于可运行状态的进程,则只置相应的域即可。

        进程管理结构粗略描述

        struct task_struct{
            //...
            struct sigpending pending; //未决信号

            struct sigaction sigaction[]; //信号响应行为设置

            sigset_t blocked; //进程的屏蔽信号集
            //...
        };
        struct sigaction{
            /*这里有信号处理函数*/
            /*这里有信号被响应时的屏蔽集,缺省是屏蔽本信号*/
            int sa_flag;
            void (*sa_restorer)(void); //已过时,POSIX不支持,不应使用
        };
        struct sigpending{
            struct sigqueue *head, *tail; //该队列存放信号有关的信息
            sigset_t signal; //未决信号集
        };
        struct sigqueue{
            struct sigqueue *next;
            siginfo_t info; //存放信号信息
        };

        示意图:

   进程信号管理结构示意
        以上结构可以看到,
            信号的处理设置是在sigaction[],
            信号的注册是pending,
            信号的信息保存在head,tail队列中,
            进程的屏蔽集在blocked中。

        可靠信号的注册和不可靠信号的注册不同。
            可靠信号可以多次在未决信号集中注册并且在信号信息队列里登记
            信号的信息。
            不可靠信号在未决信号集中注册并在信号信号信息队列里登记信号的
            信息后,在该信号被注销之前,其后继来到相同的信号都不会被登记。

    4.3)信号的执行和注销。
        进程对未决信号的信号的处理发生在:进程由内核态返回用户态。

        当一个任务陷入内核代码中执行时,它处于内核态,特权级为0;
        当一个任务在执行自己的代码时,它处于用户态,特权级为3。

        用户进程什么时候会从内核态返回用户态呢?一般主要是三种情况:
            系统调用(用户进程主动进入内核),
            中断(用户进程被动进入内核),
            被调度执行(用户进程从等待执行变为正在执行,注意,有时注册
                了信号后进程即可被唤醒,看4.2)。

        4.3.1)信号的处理设置
            忽略信号。
                SIG_IGN
                信号不注册。
            执行缺省操作。
                SIG_DFL
                终止进程(所有的实时信号,一些非实时信号)(产生内核映像)
                忽略信号。
            捕捉信号,由用户定义方式处理。
                在信号发生时调用用户函数。
                注意不要使用不可重入函数。
    
            SIGKILL, SIGSTOP信号
                SIGKILL终止进程。
                SIGSTOP停止进程。
                不可被忽略,捕捉和阻塞。因为它们向超级用户提供使进程终止
                或停止的可靠方法,显然这两个信号的处理也不能被重新设置。
    
            未决信号。
                信号产生和递送之间的时间间隔,称为信号未决。
                (和信号未决相关的:
                    信号被阻塞。
                    进程不可中断等待。)
    
        信号的执行和注销条件:
            信号没有被阻塞。
            在进程处于可中断状态。

        4.3.2)信号的注销
            信号相关信息在信号信息链中被卸掉,对于非实时信号,其也会在未
            决信号中被删除(注销完毕);对于实时信号,如果其在信号信息链
            中还存在记录,即其不会在未决信号中被删除(注销完毕)。
    
        4.3.3)信号处理函数的执行:
            条件:信号已在进程中注销。

            一个用户进程会有一个用户栈和内核栈(为什么?我觉得是安全)。
            现在就信号,说说我对内核栈和用户栈的交互的简单理解。
            用户进程进入内核态时,其会在内核栈中记录其返回地址及中断现场。
            当内核发现进程有信号需要处理,就会撤销信号,如果用户自定义信号
            处理函数,就会对用户从内核态返回到用户态的地址进行修改,改为用
            户处理函数的地址。而在这之前,会将进程上次保留的返回信息备份
            到用户栈上。这样,进程根据内核栈的地址在返回用户态时便会进入自
            定义信号处理函数,进程在执行完信号处理函数后,根据用户栈的内容
            就会继续上次中断的执行。
                (对于这样做的优点,我的理解是:1.内核栈相对较小,如果一直
                    使用内核栈,后果是内核栈会溢出2.用户自定义函数不能够在
                    内核态执行,因为后果无法控制,这样就巧妙地跳转到用户自
                    定义的处理函数。)

            至此信号对进程的影响结束。

            可中断等待。
                可以被信号中断的等待。
            不可中断等待。
                等待进程直接在硬件条件等待,并且任何情况下都不可中断。

5)《linux内核设计的艺术》关于信号的范例。

    一个信号的生存周期示意

    其中pid1为信号目标进程,pid2为信号发送进程,事件的发生如左自顶

    向下。(图片做得挺简陋的*_^)

   
    该例子中,可能会有些东西已经过时,但仍可作一次理解的学习。

6)一些相关函数
    信号的安装
    signal();该函数在不同的版本的unix/linux系统里实现不同。
    sigaction();signal的实现需要调用sigaction。
    跳转
    setjmp()和sigsetjmp();
    longjmp()和siglongjmp();
    信号的发送
    raise();向进程自身发送信号ANSI C,非POSIX标准。
    kill();可以向多个进程发送信号。
    sigqueue();支持信号带有参数,可以只进行信号目标pid的有效性与信号
        发送权限检查,和sigaction()配合使用。
    alarm();可作定时器。
    getitimer();和setitimer();
    abort();即使SIGABORT被进程设置为阻塞信号,调用abort()后,SIGABORT
        仍然能被进程接收。
    挂起等待信号到来
    pause();
    信号集操作
    sigemptyset();
    sigfillset();
    sigaddset();
    sigdelset();
    sigismember();
    线程的信号阻塞与未决。
    sigprocmask();
    sigpending();
    sigsuspend();原子操作。

7)信号的使用。

    对信号编程的实践还是非常少,这里是从书或网上搜集的一些说法。


    防止信号的丢失。
        了解系统信号机制,清楚信号在什么时候或什么的地方可能会被丢失。

    可移植性。
        尽量采用POSIX信号函数,POSIX信号函数主要分为两类:
        POSIX 1003.1信号函数: Kill()、sigaction()、sigaddset()、
        sigdelset()、sigemptyset()、sigfillset()、sigismember()、
        sigpending()、sigprocmask()、sigsuspend()。
        POSIX 1003.1b信号函数。POSIX 1003.1b在信号的实时性方面对
        POSIX 1003.1做了扩展,包括以下三个函数: sigqueue()、
        sigtimedwait()、sigwaitinfo()。

    可重入性。
        不使用静态数据结构,
        使用本地数据或全局数据的拷贝。
        使用全局数据则通过关中断,信号量(P,V操作)保护。
        不使用非可重入函数。使用malloc()和free(),标准I/O。
        The Open Group视下列函数为可再入:
        _exit()、access()、alarm()、cfgetispeed()、
        cfgetospeed()、cfsetispeed()、cfsetospeed()、
        chdir()、chmod()、chown() 、close()、creat()、
        dup()、dup2()、execle()、execve()、fcntl()、
        fork()、fpathconf()、fstat()、fsync()、getegid()、
        geteuid()、getgid()、getgroups()、getpgrp()、
        getpid()、getppid()、getuid()、kill()、link()、
        lseek()、mkdir()、mkfifo()、 open()、pathconf()、
        pause()、pipe()、raise()、read()、rename()、
        rmdir()、setgid()、setpgid()、setsid()、setuid()、
        sigaction()、sigaddset()、sigdelset()、sigemptyset()、
        sigfillset()、sigismember()、signal()、sigpending()、
        sigprocmask()、sigsuspend()、sleep()、stat()、
        sysconf()、tcdrain()、tcflow()、tcflush()、
        tcgetattr()、tcgetpgrp()、tcsendbreak()、tcsetattr()、
        tcsetpgrp()、time()、times()、 umask()、uname()、
        unlink()、utime()、wait()、waitpid()、write()。
        即使信号处理函数使用的都是"安全函数",同样要注意进入处理函
        数时,首先要保存errno的值,结束时,再恢复原值。因为,信号
        处理过程中,errno值随时可能被改变。另外,longjmp()以及
        siglongjmp()没有被列为可再入函数,因为不能保证紧接着两个函
        数的其它调用是安全的。
        参考:
        http://www.unix.org/whitepapers/reentrant.html

    信号临界区保护。
        临界区(critical region):访问和操作共享数据段的代码段。
        某些代码在操作的过程中要求不能被某些信号中断。这时就要对
        信号进行屏蔽。

    返回跳转。
        使用sigsetjmp()和sigjmp()。

    exec函数。
        除了程序忽略的信号,信号的处理方式都是缺省。
    fork函数。
        子进程会继承父进程的信号处理方式。

    中断系统调用

        早期系统的一个特点是:如果进程在执行一个低速系统调用而阻塞
        期间捕捉到一个信号,则该系统调用就被中断不再继续执行。该系
        统调用返回出错,其errno被设置为EINTR。这样处理的理由是因为
        一个信号发生了,进程捕捉到了它,这意味着已经发生了某种事情
        ,所以是个应当唤醒阻塞的系统调用的好机会。
        系统调用分成两类:低速系统调用和其它系统调用。低速系统调用
        是可能会使进程永远阻塞的一类系统调用。
        可以被中断系统调用这种处理方法来处理的一个例子是:一
        个进程启动了读中断操作,而使用该终端设备的用户却离开该
        终端很长时间。在这种情况下进程可能处于阻塞状态几个小时
        甚至数天,除非系统停机,否则一直如此。
        为了帮助应用程序使其不必处理被中断的系统调用,4.2BSD引
        入了某些被中断系统调用的自动重启动。自动重启动的系统调
        用包括ioctl、read、readv、write、writev、wait和waitpid
        。其中前5个函数只有对低速设备进行操作时才被中断。而wait
        和waitpid在捕捉到信号时总是被中断。因为这种自动重启动的
        处理方式也会带来问题,所以某些应用程序并不希望这些函数
        被中断后重启动。为此4.3BSD允许进程基于每个信号禁用此功
        能。
    
8)参考资料
    《linux c编程实战》
    《uniux环境高级编程》
    《linux内核设计的艺术》
    《Manual pager》
    internet

    ...


本博客版权声明点击打开链接


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值