信号及信号通讯

   通往牛逼的道路上不是坎坷和荆棘,而是蹲着一大堆傻逼,你就可能是最大的一个。

                                                ----------匿名

  先谈谈什么信号,信号就是进程正在执行,接受到信号,先执行信号,然后在继续执行进程,也是一种进程间通讯吧,就相当于JAVA中抛出异常。也是常说的中断中的软件中断,当然安全起见,不是所有的进程都可以接受任何信号,也不是所有的所有的进程都可以发送信号,要是一个恶意程序发一个SIGINT,所有的进程都终止了,这是个很严重的问题。 你进程中有多少信号可以用kill -l来查看,这是一部分,可以看出也是要注意的信号编号是从1开始不是从0;后面的编程会用到。想看每个信号的意义可以man一下。

  一:如何产生和处理信号?我们在shell中终止进程会用ctrl + C 这是一种属于按下终端键,还有硬件产生例如被0除,主要将给介绍的是kill传递,用kill结束进程只是信号传递的一种而已。

 

 

操作系统课都讲过我就不在这里赘述了。这里还有介绍的一种状态就是挂起状态,挂起是属于就绪状态但人为的想让他阻塞,把他从就需队列放到阻塞队列,用到的sleep函数就是这个原理,可以用pause();让他挂起,其他状态都是收到信号后,等到开始运行先处理信号在执行程序,挂起是收到信号就唤醒进程,开始执行信号。

二:  有三种处理信号的方式:1忽略信号,2是自己注册个信号处理函数,扑捉到时就用自己的处理函数。3是执行系统默认,因信号的不同而采用终止进程或忽略。

理论总是那么苍白,只有代码才有说服力,以后代码实现不了的理论就不写,凡贴出代码的,一定会贴出运行结果。

发送信号函数 :头文件 :#include<signal.h>

函数原型: int kill(pid_t pid,  int signo);

返回值: 成功返回0;失败返回-1;

kill可以向进程或进程组发送信号;  pid > 0: 发送给进程给pid的进程(常用);pid == -1:发送给系统所有进程; pid == 0: 发送给进程组ID和该进程相同的进程;

pid < 0: 发送给进程组内ID 为pid的进程;

进程间发送需要注意2点:进程要有向发送信号的进程的权限;信号当然不可以随便发.那样你就可以随便结束其他进程了;2是系统进程不能接受信号;理由一样:

根用户可以向系统内任意进程发送信号。得到根用户权限的一样,可以终止任何进程。

 也有向进程本身发送信号的函数:你可以用kill(getpid(), signo);有专用的函数  int raise(int signo);使用和kill 一样。

在这里唠叨两句其他的问题:你可以结束自己用raise(SIGKILL);这样你就结束了自己的进程但是没有做任何善后处理,拉完屎不檫屁股,不提裤子(例如关闭文件流,不把缓冲区的内容写入外存等),相当于_exit()函数,_exit和exit()函数不同,exit()会做善后处理工作。还有就是exit(0)是正常退出,exit(其他);是异常退出。

有的写成exit(EXIT_SUCCESS)和exit(EXIT_FAILURE);

设置信号处理函数:头文件#include<signal.h>

函数原型: void (*signal (int signo , void(* func) (int ) )) (int ); 别看这么麻烦,其实最简单,一会看例子。

返回值:返回值是一个指向函数的指针,这个函数指向上一次信号处理函数。

第二个参数是函数指针:有三个值:①SIG_IGN: 表示忽略该信号。注意的是SIGKILL 和 SIGSTOP不能忽略,要是能忽略,谁来终止他停止。②SIG_DEL表示默认的。③就是自己定义的函数指针。

再说2点就是进程不能创建信号,系统给用户两个信号用于通讯,SIGUSR1和SIGUSR2;

 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 #include<signal.h>
 4 
 5 //自己定义的信号处理函数
 6 void handler(int sigo)
 7 {
 8     switch(sigo)
 9     {
10         case SIGUSR1:
11             printf("parent :catch sigusr1 \n");
12             break;
13         case SIGUSR2:
14             printf("child ;catch sigusr2 \n");
15             break;
16         default:
17             printf("should't here \n");
18             break;
19     }
20     return ;
21 }
22 
23 int main(void)
24 {
25     pid_t ppid,cpid;
26 
27 //错误将返回SIG_ERR信号
28     if (signal(SIGUSR1,handler) == SIG_ERR)
29     {
30         perror("can't set handler for siguer1 \n");
31         exit(1);  //这是异常退出
32     }
33 
34     if(signal(SIGUSR2,handler) == SIG_ERR)
35     {
36         perror("can't set handler for siguer2 \n");
37         exit(1);
38     }
39 
40     ppid = getpid();
41 
42 //创建子进程
43     if( (cpid = fork()) < 0)
44     {
45     perror("fail to fork \n");
46     exit(1);
47     }
48     else if (cpid == 0)
49     {
        printf("我是孩子 \n");
50 //向父进程发送信号 51 if (kill(ppid, SIGUSR1) == -1) 52 { 53 perror("fail to send signal"); 54 exit(1); 55 } 56 57 while(1) ; 58 } 59 else 60 {63 //向子进程发送信号 64 65 if (kill(cpid, SIGUSR2) == -1) 66 { 67 perror(" fail to send signal"); 68 exit(1); 69 } 70 71 printf("kill child \n"); 72 //终止子进程的死循环 73 if (kill(cpid, SIGKILL) == -1) 74 { 75 perror ("fail to send wair \n"); 76 printf("孩子为啥你一直收不到啊"); 77 exit(1); 78 } 79 //防止产生僵尸进程 80 if(wait(NULL) == -1) 81 { 82 perror("fail to wait \n"); 83 exit(1); 84 } 85 } 86 87 return 0; 88 89 }

 这个程序调试了3个多小时,最后确定语法没问题,这个例子也可以说出上面那两个函数的用法,纠结的地方时子进程先运行还是父进程先运行,他们是异步的,他们是一个先运行,紧接着另一个也开始运行,并不是一个运行完了另一个才运行,还有就是父进程给子进程传信号,后面的就是终止子进程的信号,所以子进程老是收不到,因为他已经被kill了,在给子进程发送SIGKILL信号时前sleep(2);就差不多,这个是截图没用shell,用的集成环境,在shell中调试有点麻烦,在windows下用不成,出错信息是没有信号量,调试这个程序有种高中做证明题的感觉。最后就是在调错时试图给SIGKILL做个处理函数就是给他也handler一下,结果出错,这是当然的SIGKILL 和SIGSTOP 是不可屏蔽的。

三:谈谈定时器吧就是alarm闹钟。老规矩:

函数原型:#include<unisd.h>

函数原型: unsigned int alarm(unsigned int seconeds);

返回值: 定时器不存在。或已超时,返回0;定时器还没超时,返回剩余的秒数。

alarm参数是0时可以取消一个定时器。

还有要说的就是alarm确是不是不是一个精确的,因为他最小的单位是秒,对于计算机执行一条指令所要花费的时间你懂得。

还有他可以用于定时等待I/o外设不可用时,超过了某一时间就不等了,例如网络的设备中数据包未到达。你会问前面的还有现在举了好多例子都是网络,端口,也没好多个也就几个,因为这些都是为了我们的终极目的网络编程打基础。

好了就算是这么函数也举个例子把。勿以函数小而不为之,

#include<stdio.h>
#include<signal.h>
#include<stdlib.h>

void time_handler(int signo)
{
    if (signo == SIGALRM)
    {
        printf("the time is : \n");
        exit(0);//退出程序了;不会返回到扑捉信号的那个函数了
    }
    else 
    printf("unexpecd signal \n");

}

int main()
{
    if (signal(SIGALRM,time_handler) == SIG_ERR)
    {
        perror("fail to signal \n");
        exit(1);
    }

    alarm(1);
    while(1)
    printf("hello word \n");

    printf("上面是个死循环,不会到这 \n");

    return 0;
}

1S 就运行了这么多,见了这么多头文件为#include<unistd.h>就百度了一下:是POSIX标准定义的unix类系统定义符号常量的头文件,包含了许多UNIX系统服务的函数原型,例如read函数、write函数和getpid函数.

 再谈一个puse()函数吧,

头文件:#include<unistd.h>

函数原型: int pause(void);

返回值:返回值只有-1,为数不多,至少目前我见的第一个正确返回是-1;错误给errno设置为了EINTER;

pause()函数就是前面说的挂起了,需要一个信号才行,否则一直挂起,不响应一些SIGTERM等函数,但是SIGKILL必须响应,也响应SIGUSR等,

你有没有灵光一现,sleep();那不就是封装了alarm和pause这两个函数吗哈哈,其实就是。

信号屏蔽: 信号集: 进程进程捕捉并处理的信号的集合。

用个例子介绍几个函数更清楚点。

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

int  main(void)
{
 sigset_t sig_set;
sigsemtyset(&sig_set);
sigaddset(&sig_set, SIGHUP-1);
       
}

先将信号集清空再设置,设置的时候信号集是从0开始的,而signo编码是下哦那个一开始,所以要减1;

前面说过默认处理方式,也有忽略,和这个屏蔽的区别是,前者是受到了信号处理时是忽略,而后者是根本都不接受,这样就产生了未决信号。

头文件:#include<signal.h>

函数原型: int sigpromask(int how,const sigset_t *restrict set, sigset_t *restrict oset);

返回值: 成功返回0 错误返回-1;

讲下how的三个值:SIG_BLOCK:set包含了处理的信号集,表示可以屏蔽了,

        SIG_UNBLOCK: 从set 中杰出信号,意思是就不屏蔽了。

        SIG_SETMASK:设置当前信号集未set所指向的值;

举两个简单的函数:sigpromask(SIG_BLOCK,&set,NULL) 把set设置里屏蔽信号可以屏蔽了,就是未决信号了。

        sigpromask(SIG_UNBLOCK,&set,NULL) 把set设置的屏蔽信号解除了,然后未决信号就可以发送了,不必重新发送,他是自动的。

下集预告是:进程见通讯。

转载于:https://www.cnblogs.com/xiaoyaoc/archive/2013/04/11/3014330.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值