Chinaunix首页 | 论坛 | 博客
  • 博客访问: 388292
  • 博文数量: 85
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 1707
  • 用 户 组: 普通用户
  • 注册时间: 2013-08-27 11:18
个人简介

学无止境……

文章分类

全部博文(85)

分类: LINUX

2014-07-04 14:21:06

linux多任务间通信
-----信号机制         
         
Q:什么是linux多任务间通信,为什么需要通信?
A:由于linux中运行的进程是工作在独立的内存空间中,不同的进程之间是无法直接访问到对方的内存空间。但由于程序功能上的需求,常常需要不同进程协作完成一项任务。于是乎矛盾就产生啦,linux下解决办法之一就是 “信号机制”,当然还有相当多的方法比如:“管道” ,“消息列队” , “共享内存” , “套接字”。这里主要介绍 “信号” ,其他多任务间通信在以后文章中再做介绍。(IPC = Inter-Process Communication

概念:

信号机制是进程之间相互传递消息的一种方法,信号全称为软中断信号,也有人称作软中断(在软件层次上对中断机制的一种模拟)。从它的命名可以看出,它的实质和使用很象中断。
原理上,一个进程收到一个信号与处理器收到一个中断基本上是一样的。“信号"也是异步的,一个进程不必通过轮询查找等待信号的来到,也不无法预知信号何时会来。



信号的类型:
信号被定义在>中,都以"SIG"为前缀。
此外在系统终端用命令 ” $kill -l “ 也可以查看系统中定义的 signal 。

”信号“定义的种类比较多,但是可以按照一定规律分类。根据网友的分类如下。
(1) 与进程终止相关的信号。当进程退出,或者子进程终止时,发出这类信号。 
(2) 与进程例外事件相关的信号。如进程越界,或企图写一个只读的内存区域(如程序正文区),或执行一个特权指令及其他各种硬件错误。 
(3) 与在系统调用期间遇到不可恢复条件相关的信号。如执行系统调用exec时,原有资源已经释放,而目前系统资源又已经耗尽。 
(4) 与执行系统调用时遇到非预测错误条件相关的信号。如执行一个并不存在的系统调用。 
(5) 在用户态下的进程发出的信号。如进程调用系统调用kill向其他进程发送信号。 
(6) 与终端交互相关的信号。如用户关闭一个终端,或按下break键等情况。 
(7) 跟踪进程执行的信号。 
此外根据信号的可靠性又可以分成:可靠信号、不可靠信号;根据时间关系分为:实时信号,非实时信号
        "不可靠信号":那些建立在早期机制上的信号,信号值小于SIGRTMIN(可用 kill -l 命令查看,SIGRTMIN=34,SIGRTMAX=64)的信号都是不可靠信号。
“实时信号”:非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。





信号的产生:

信号的产生主要有两个来源:硬件来源,软件来源。
硬件来源: 终端中用户输入 “Ctrl + C” 将对当前程序产生 “SIGKILL”信号  、硬件异常(除数为0,无效的存储访问)等
软件来源:程序调用 kill 等产生信号的函数或用户使用kill命令产生信号、某种软件条件达到产生信号(SIGPIPE:管道读进程终止 ,SIGALRM:进程设定的闹钟时间到达)
 “Ctrl + C”---->SIGINT             “Ctrl + /” ---->SIGQUIT

产生信号相关函数介绍:

kill() & raise()
  1. #includ<signal.h>    // 定义函数的头文件

  2. int kill(pid_t pid, int signo);   //将信号发送给一个进程或进程组
  3. int raise(int signo);            // 把信号发送给进程自身
raise()函数的实质是调用了kill()
  1. #include<signal.h>   // kill() 头文件
  2. #include<unistd.h>   // getpid()头文件

  3. kill(getpid(), signo);    // getpid() 取得目前进程的进程识别码
因此重点介绍kill()函数。kill()的 signo参数为欲发送的信号值,pid 参数取值如下表:
pid值 信号对象
pid > 0 进程ID为pid的进程
pid < 0 ( pid != -1) 进程组ID为 |pid| 的所有进程
pid = -1 将信号广播给除自身外的所有进程
pid = 0 同一个进程组的进程

alarm() & pause()
  1. #include<unistd.h> // 函数定义头文件

  2. unsigned int alarm(unsigned int seconds);
  3. int pause(void);
alarm():顾名思义
闹钟函数,其功能也表现无遗。用来设置信号SIGALRM,在经过参数 seconds 指定的秒数后传送给目前的进程(每个进程只能有一个闹钟,调用alarm()后之前设置的闹钟将失效)。如果参数 seconds 为 0,则之前设置的闹钟会被取消,并将剩下的时间返回
pause():令当前的进程暂停(进入睡眠状态),直到被信号(signal)所中断

abort()
此函数向进程自身发送SIGABORT信号,默认操作会使进程异常退出。
  1. #include<stdlib.h>

  2. void abort(void);

sigqueue()
在队列中向指定进程发送一个信号和数据,是较新的发送信号系统调用,发送信号时支持带参数,一般与sigaction()配合使用。
  1. #include<signal.h>

  2. int sigqueue(pid_t pid,int sig,const union sigval value);

  3. *************************************************************
  4. parm:
  5.     pid:目标进程的进程号
  6.     sig:信号代号
  7.     value:是一个联合体,表示信号附带的数据(传递给sigaction(),它也有此类型参数,),附带数据可以是一个整数也可以是一个指针,定义的原型如下:
  8.     
  9.     union sigval {
  10.         int sival_int;
  11.         void *sival_ptr;//指向要传递的信号参数
  12.     }sigval_t;
注:相比kill()函数,sigqueue()功能更强大,但是sigqueue()只能向一个进程发送信号,而不能发送信号给进程组



信号的处理

程序收到信号可以根据信号做出相应的响应,一般处理方式有如下三种。
1> 捕捉信号,类似中断机制,对需要处理的信号,进程执行指定函数。
2> 忽略信号,忽略接收到的信号,不做任何处理。充耳不闻,就像没有发生过一样。(但是SIGKILL和SIGSTOP不能忽略)
3> 默认操作,每个信号系统都定义了默认的操作。

此外进程需要处理某个信号就必须在进程中注册该信号,让信号值和进程针对该信号的操作对应起来。注册信号有两个函数signal()和sigaction(),如下:

  1. 1 #include<signal.h>
  2. 2
  3. 3 void (*signal(int signum, void (*handler)(int))) (int);
  4. 4 int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
signal()

原函数定义的有点夸张,其实也可以这样定义,也方便理解

  1. typedef void sign(int);
  2. sign *sinal(int, handler *)

参数signum指定信号值;
参数handler指定该信号对应的处理函数地址(设为SIG_IGN忽略,SIG_DEF默认操作)。signal()调用成功返回最后一次注册信号signum而调用的handler值,失败返回SIG_ERR。
signal()函数使用比较简单,一般只用于非实时性信号的注册(SIGRTMIN之前的信号)。


点击(此处)折叠或打开

  1. /* signal()函数使用示例 */
  2. #include<signal.h>
  3. #include<stdio.h>
  4. #include<unistd.h>

  5. void sig_int(int sig)   // sig 参数为 signal()注册信号时传递的 信号值参数
  6. {
  7.      printf("Get signal: %d \n",sig);
  8.      signal(SIGINT, sig_int);    //重新注册信号,否则该程序收到此信号将执行默认操作
  9. }

  10. void main()
  11. {
  12.     signal(SIGINT, sig_int);    //注册 SIGNIT 信号,以及对应的操作函数
  13.     while(1)
  14.     {
  15.          printf("LuoYe !\n");
  16.          sleep(1);
  17.     }
  18. }

运行结果如下:




(重难点)

? sigaction() ?
用于改变进程接收到特定信号后的行为,函数原型:

  1. #include <signal.h>
  2. int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));

  3. /*******************************************/
  4. parm:
  5.     signum:信号值,可以为除SIGKILL及SIGSTOP外的任何一个特定有效的信号(因为这两个信号定义自己的处理函数,将导致信号安装错误)
  6.     act:指定了对特定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉哪些函数等等,当设为NULL时将以默认方式处理信号
  7.     oldact:用来保存原来对相应信号的处理,也可设为NULL

  8. 注:如果把第二、第三个参数都设为NULL,那么该函数可用于检查信号的有效性。

参数 act 是函数中最重要的参数,它被定义为一个sigaction的结构。那么里面究竟包含些什么信息呢?

  1. struct sigaction {
  2. void (*sa_handler)(int);
  3. void (*sa_sigaction)(int, siginfo_t *, void *);   //siginfo_t 中带有信号相关的参数
  4. sigset_t sa_mask;
  5. int sa_flags;
  6. void (*sa_restorer)(void);  // 已过时,POSIX不支持它,不应再被使用
  7. }
1)void (*sa_handler)(int)和void (*sa_sigaction)(int, siginfo_t *, void *)都是用于指定信号关联函数,到底采用哪个要看sa_flags中的设置。
当使用sa_handler时指定的处理函数只有一个参数(信号值),跟注册信号就跟调用signal()类似。
使用sa_sigaction指定处理函数时指定的信号处理函数带有3个参数:

第一个参数为信号值,

第三个参数没有使用,

第二个参数是指向 siginfo_t 结构的指针,结构中包含信号携带的数据值

  1. typedef struct siginfo_t{
  2. int si_signo;//信号编号
  3. int si_errno;//如果为非零值则错误代码与之关联
  4. int si_code;//说明进程如何接收信号以及从何处收到
  5. pid_t si_pid;//适用于SIGCHLD,代表被终止进程的PID
  6. pid_t si_uid;//适用于SIGCHLD,代表被终止进程所拥有进程的UID
  7. int si_status;//适用于SIGCHLD,代表被终止进程的状态
  8. clock_t si_utime;//适用于SIGCHLD,代表被终止进程所消耗的用户时间
  9. clock_t si_stime;//适用于SIGCHLD,代表被终止进程所消耗系统的时间
  10. sigval_t si_value;
  11. int si_int;
  12. void * si_ptr;
  13. void* si_addr;
  14. int si_band;
  15. int si_fd;
  16. };

  17. 其中第10行中sigval_t为联合数据类型,其原型为:
  18. typedef union sigval {
  19.         int sival_int;       
  20.         void *sival_ptr;
  21.         }sigval_t
sigaction()通过参数siginfo_t.si_valu获得sigqueue(pid_t pid, int sig, const union sigval val) 传递过来的val参数。其实siginfo_t.si_int直接与sigval.sival_int关联siginfo_t.si_ptr直接与sigval.sival_ptr关联,通过这两个参数也同样可以获得sigqueue()传递的第三个参数。


2)sa_mask:用来设置信号屏蔽字,该屏蔽字由一个信号集定义。
3)sa_flags:指定了对信号处理的选项。下表为常用字段:

选项 定义
SA_NODEFER 不把信号添加到信号屏蔽字中
SA_SIGIFO 指定信号处理函数需要三个参数,所以指定处理信号操作时应选择sa_sigaction而不是sa_handler
SA_RESETHAND 调用信号处理函数后,把信号处理函数重置为默认(SIG_DFL).此时效果相当于signal(),它默认会重置。
SA_RESTART 由此信号中断的系统调用会自动启动
SA_NOMASK 就是SA_NODEFER的别名,它不支持POSIX,所以为了软件的可移植性一般不使用此标志

点击(此处)折叠或打开

  1. /** sigaction() 函数简单例程 ***/
  2. #include<signal.h>
  3. #include<stdio.h>
  4. #include<unistd.h>

  5. void sig_int(int sig) // 信号的响应函数
  6. {
  7.     printf("Get signal: %d\n",sig);
  8. }

  9. void main()
  10. {
  11.     /** 初始化 sigaction 结构 **/
  12.     struct sigaction act;
  13.     act.sa_handler = sig_int;
  14.     sigemptyset(&act.sa_mask);  //将参数set信号集初始化并清空
  15.     act.sa_flags = 0;

  16.     sigaction(SIGINT, &act, 0);  // 注册信号。只需注册一次,区别与使用signal()函数时每次都需要重新注册
  17.     while(1)
  18.     {
  19.      printf("LuoYe !\n");
  20.      sleep(1);
  21.     }
  22. }

执行的结果跟 signal()例程一样。




还有一些与信号机制相关的要点(信号集)及函数,在另外的blog在介绍。http://blog.chinaunix.net/uid-29145190-id-4339938.html
本人在阅读 ser2net 源码时发现自己对“信号机制”的理解有所欠缺。所以重新捡起“信号机制”,本文就是在学习过程中的一些记录,顺便分享下。本人菜鸟一个,主要参考了网上大神们的技术帖,稍稍加入了一些个人的理解。可能会有些错误,仅作参考。

 


阅读(1892) | 评论(0) | 转发(0) |
0

上一篇:指针&数组

下一篇:“信号”唤醒sleep()

给主人留下些什么吧!~~