moc 发表于 2018-11-7 22:47:41

012-信号基础

本帖最后由 moc 于 2018-11-9 15:24 编辑

1、基本概念
1. 中断
异步事件的响应:进程执行代码的过程中可以随时被打断,然后去执行异常处理程序
中断: 是系统对于异步事件的响应 ==> 中断信号、中断源、现场信息保护、中断处理程序、中断向量表。
计算机系统的中断场景:
        中断源发出中断信号,CPU判断中断是否屏蔽屏蔽、保护现场 ,cpu执行中断处理程序, cpu恢复现场,继续原来的任务。
中断的其他概念:
        1> 中断向量表保存了中断处理程序的入口地址。
        2> 中断个数固定,操作系统启动时初始化中断向量表。
        3> 中断有优先级。
        4> 中断可以屏蔽。
中断分类:
        1> 硬件中断(外部中断)
                外部中断是指由外部设备通过硬件请求的方式产生的中断,也称为硬件中断.
        2> 软件中断(内部中断)
                内部中断是由CPU运行程序错误或执行内部程序调用引起的一种中断,也称为软件中断。
2. 信号
1. 软中断信号(signal,又简称为信号)用来通知进程发生了异步事件。
        1> 在软件层次上是对中断机制的一种模拟,在原理上一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。
        2> 信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。
        3> 进程之间可以互相通过系统调用kill发送软中断信号。内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。
信号的本质 是软件层次上对中断的一种模拟。它是一种异步通信的处理机制,事实上,进程并不知道信号何时到来。
信号和中断的联系与区别:
信号与中断的相似点:
        1)采用了相同的异步通信方式;
        2)当检测出有信号或中断请求时,都暂停正在执行的程序而转去执行相应的处理程序;
        3)都在处理完毕后返回到原来的断点;
        4)对信号或中断都可进行屏蔽。
信号与中断的区别:
        1)中断有优先级,而信号没有优先级,所有的信号都是平等的;
        2)信号处理程序是在用户态下运行的,而中断处理程序是在核心态下运行;
        3)中断响应是及时的,而信号响应通常都有较大的时间延迟。
2.信号来源
        1)程序错误,如非法访问内存;
        2)外部信号,如按下了CTRL+C;
        3)通过kill或sigqueue向另外一个进程发送信号。
3.信号种类
信号分为可靠信号与不可靠信号,可靠信号又称为实时信号,非可靠信号又称为非实时信号。
信号代码从1到32是不可靠信号,不可靠信号主要有以下问题:
        1)每次信号处理完之后,就会恢复成默认处理,这可能是调用者不希望看到的
        2)存在信号丢失的问题
现在的Linux对信号机制进行了改进,因此,不可靠信号主要是指信号丢失。
        信号代码从SIGRTMIN到SIGRTMAX之间的信号是可靠信号。可靠信号不存在丢失,由sigqueue发送,可靠信号支持排队。
可靠信号注册机制:
        内核每收到一个可靠信号都会去注册这个信号,在信号的未决信号链中分配sigqueue结构,因此,不会存在信号丢失的问题。
不可靠信号的注册机制:
        而对于不可靠的信号,如果内核已经注册了这个信号,那么便不会再去注册,对于进程来说,便不会知道本次信号的发生。
可靠信号与不可靠信号与发送函数没有关系,取决于信号代码,前面的32种信号就是不可靠信号,而后面的32种信号就是可靠信号。
4.信号响应的方式
        1)采用系统默认处理SIG_DFL,执行缺省操作
        2)捕捉信号处理,即用户自定义的信号处理函数来处理
        3)忽略信号SIG_IGN ,但有两种信号不能被忽略SIGKILL,SIGSTOP.
2、常用信号
shell命令:
kill –l ==>可以查看linux内核支持的信号
man 7signal ==> 查看信号的默认动作、信号的含义

编号        |信号名称|描述|默认处理动作
1        |SIGUP|终端挂起或者控制进程终止|终止进程
2        |SIGINT|键盘中断<Ctrl+C>|终止进程
3        |SIGQUIT|键盘退出<ctrl+\>|终止进程并进行内核映像转储
10        |SIGUSR1|用户定义信号1|终止进程
12        |SIGUSR2|用户定义信号2|终止进程
14        |SIGALRM|定时器超时|终止进程。
17        |SIGCHLD|子进程结束时,父进程会收到这个信号|忽略信号
19        |SIGTSTP|停止进程的运行<ctrl+z>|暂停进程

3、信号处理
处理信号的三种方式:
1> 忽略信号
        不采取任何操作; 有两个信号不能被忽略:SIGKILL(9号信号)和SIGSTOP(19号信号)。
        如果应用程序可以忽略这2个信号,系统管理无法杀死、暂停进程,无法对系统进行管理; SIGKILL和SIGSTOP信号是不能被捕获的。
2> 捕获并处理信号
        内核中断正在执行的代码,转去执行先前注册过的处理程序。
3> 执行默认操作
        默认操作通常是终止进程,取决于被发送的信号。通过 man 7 signal 可查看信号的默认操作。
                erm: 表示终止当前进程.
                Core: 表示终止当前进程并且Core Dump(Core Dump 用于gdb调试).
                Ign: 表示忽略该信号.
                Stop: 表示停止当前进程.
                Cont: 表示继续执行先前停止的进程.
4、信号基本API
1. signal信号安装函数
作用1:站在应用程序的角度,注册一个信号处理函数。
作用2:忽略信号、设置信号默认处理信号的安装和恢复
头文件:#include <signal.h>
原型:        typedef void (*sighandler_t)(int);
                sighandler_t signal(int signum, sighandler_t handler);
参数:        signal是一个带signum和handler两个参数的函数,准备捕捉或屏蔽的信号由参数signum给出,接收到指定信号时将要调用的函数由handler给出handler这个函数必须有一个int类型的参数(即接收到的信号代码),它本身的类型是void.
                        handler也可以是:SIG_IGN=> 屏蔽该信号、   SIG_DFL =>恢复默认行为
void handler(int num)
{
        printf("recv num :%d \n", num);
        if (num == SIGQUIT)
        {
                printf("handler...do \n");
                exit(0);
        }
}
void main()
{
        char tmpchar;
        // 注册一个信号
        // SIGINT 是ctrl+c 会产生2号信号.....中断应用程序
        if (signal(SIGINT, handler) == SIG_ERR)
        {
                perror("singal err");//errno
                exit(0);
        }
       
        printf("如果你键入字符a,那么将恢复 SIGNAL的行为哦\n");
        while ((tmpchar = getchar()) != 'a')
        {
                printf(":%c", tmpchar);
                sleep(1);
        }
       
        // 把个信号恢复默认的信号行为
        signal(SIGINT, SIG_DFL);
               
        while(1)
        {
                pause();
        }
        printf("main...end\n");
}2. kill函数
头文件:    #include <sys/types.h>、 #include <signal.h>
原型:   int kill(pid_t pid, int sig);
作用:    kill既可以向自身发送信号,也可以向其他进程发送信号。
参数:
        pid>0 ==>将信号sig发给pid进程;
        pid=0 ==>将信号sig发给同组进程;
        pid=-1 ==> 将信号sig发送给所有进程,调用者进程有权限发送的每一个进程(除了1号进程之外,还有它自身);
        pid<-1 ==>将信号sig发送给进程组是pid(绝对值)的每一个进程.
注意:如果在fork之前安装信号,则子进程可以继承信号。
kill+sleep=>sleep函数几点说明
        1> sleep函数作用,让进程睡眠。
        2> 能被信号打断,然后处理信号函数以后,就不再睡眠了。直接向下执行代码.
        3> sleep函数的返回值,是剩余的秒数.
3. raise函数
原型: int raise(int signo);
        等价于: kill(getpid(), sig);
4. pause函数
        将进程置为可中断睡眠状态。然后它调用内核函数schedule(),使linux进程调度器找到另一个进程来运行。
        pause使调用者进程挂起,直到一个信号被捕获
5. alarm函数
原型:unsigned int alarm(unsigned int seconds);
        alarm函数,设置一个闹钟延迟发送信号,告诉linux内核n秒中以后,发送SIGALRM信号。
typedef struct _Teacher
{
        int age;
        int num;       
} Teacher;
Teacher g_t;

void printfGlobTeacher()
{
        printf("**age:%d**\n", g_t.age);
        printf("**num:%d**\n", g_t.num);
}

void myhandler(int num)
{
        printf("recv signal id num :%d \n", num);
        printfGlobTeacher();
        alarm(1);// 执行再定时1s,间接递归
}

int main(void)
{
        Teacher t1, t2;
        t1.age = 30;
        t1.num = 30;
        t2.age = 40;
        t2.num = 40;
       
        printf("main.... Begin\n");
        if (signal(SIGALRM, myhandler) == SIG_ERR)
        {
                perror("func signal err\n");
                return 0;
        }
        alarm(1);
       
        while(1)
        {
                //pause();
                g_t = t1;
                //printf("@@@@@@@@@ \n");
                g_t = t2;
                //printf("pause return \n");
        }
        return 0;
}5、可重入和不可重入函数
为了增强程序的稳定性,在信号处理函数中应使用可重入函数。
        可重入函数是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会出错。因为进程在收到信号后,就将跳转到信号处理函数去接着执行。如果信号处理函数中使用了不可重入函数,那么信号处理函数可能会修改原来进程中不应该被修改的数据,这样进程从信号处理函数中返回接着执行时,可能会出现不可预料的后果。不可再入函数在信号处理函数中被视为不安全函数。
满足下列条件的函数多数是不可再入的:
        (1)使用静态的数据结构,如getlogin(),gmtime(),getgrgid(),getgrnam(),getpwuid()以及getpwnam()等等;
        (2)函数实现时,调用了malloc()或者free()函数;
        (3)实现时使用了标准I/O函数的.
结论: 在信号处理函数中,尽量不使用全局变量和静态变量的函数。特别是这个变量在程序中随时读写。
页: [1]
查看完整版本: 012-信号基础