HardBirch

信号机制、共享内存和消息队列

时间:10-04-25 栏目:系统技术篇 作者:鲁智森也有文化 评论:0 点击: 1,645 次

 

Linux进程间通信类型:

1,管道通信    见上文

2,信号机制

3,共享内存

4,消息队列

5,信号量

6,socket通信

 

注:本文重点讲述黑体字3种进程间通信类型

 

信号机制

1,信号概述

信号是软件中断。信号(signal)机制是Unix系统中最为古老的进程之间的通信机制。它用于在一个或多个进程之间传递异步信号。

很多条件可以产生一个信号。

n   当用户按某些终端键时,产生信号。在终端上按DELETE键通常产生中断信号(SIGINT)。这是停止一个已失去控制程序的方法。(第11章将说明此信号可被映射为终端上的任一字符。)

n   硬件异常产生信号:除数为0、无效的存储访问等等。这些条件通常由硬件检测到,并将其通知内核。然后内核为该条件发生时正在运行的进程产生适当的信号。例如,对执行一个无效存储访问的进程产生一个SIGSEGV。

n  进程用kill( 2 )函数可将信号发送给另一个进程或进程组。自然,有些限制:接收信号进程和发送信号进程的所有者必须相同,或发送信号进程的所有者必须是超级用户。

n  用户可用kill( 1 )命令将信号发送给其他进程。此程序是kill函数的界面。常用此命令终止一个失控的后台进程。

n  当检测到某种软件条件已经发生,并将其通知有关进程时也产生信号。这里并不是指硬件产生条件(如被0除),而是软件条件。例如SIGURG (在网络连接上传来非规定波特率的数据)、SIGPIPE (在管道的读进程已终止后一个进程写此管道),以及SIGALRM(进程所设置的闹钟时间已经超时)。

 

内核为进程生产信号,来响应不同的事件,这些事件就是信号源。主要的信号源如下:

n  异常:进程运行过程中出现异常;

n  其它进程:一个进程可以向另一个或一组进程发送信号;

n  终端中断:Ctrl-C,Ctrl-/等;

n  作业控制:前台、后台进程的管理;

n  分配额:CPU超时或文件大小突破限制;

n  通知:通知进程某事件发生,如I/O就绪等;

n  报警:计时器到期。

 

1) SIGHUP       2) SIGINT         3) SIGQUIT      4) SIGILL

5) SIGTRAP       6) SIGIOT         7) SIGBUS        8) SIGFPE

9) SIGKILL        10) SIGUSR1     11) SIGSEGV     12) SIGUSR2

13) SIGPIPE      14) SIGALRM   15) SIGTERM   17) SIGCHLD

18) SIGCONT    19) SIGSTOP     20) SIGTSTP      21) SIGTTIN

22) SIGTTOU    23) SIGURG      24) SIGXCPU    25) SIGXFSZ

26) SIGVTALRM        27) SIGPROF              28) SIGWINCH 29) SIGIO          30) SIGPWR     

下面是几个常见的信号。

n  SIGHUP: 从终端上发出的结束信号;

n  SIGINT: 来自键盘的中断信号(Ctrl-C);

n  SIGQUIT:来自键盘的退出信号(Ctrl-/);

n  SIGFPE: 浮点异常信号(例如浮点运算溢出);

n  SIGKILL:该信号结束接收信号的进程;

n  SIGALRM:进程的定时器到期时,发送该信号;

n  SIGTERM:kill 命令发出的信号;

n  SIGCHLD:标识子进程停止或结束的信号;

n  SIGSTOP:来自键盘(Ctrl-Z)或调试程序的停止执行信号

…………

可以要求系统在某个信号出现时按照下列三种方式中的一种进行操作。

(1) 忽略此信号。大多数信号都可使用这种方式进行处理,但有两种信号却决不能被忽略。它们是:SIGKILL和SIGSTOP。这两种信号不能被忽略的原因是:它们向超级用户提供一种使进程终止或停止的可靠方法。另外,如果忽略某些由硬件异常产生的信号(例如非法存储访问或除以0),则进程的行为是未定义的。

(2) 捕捉信号。为了做到这一点要通知内核在某种信号发生时,调用一个用户函数。在用户函数中,可执行用户希望对这种事件进行的处理。如果捕捉到SIGCHLD信号,则表示子进程已经终止,所以此信号的捕捉函数可以调用waitpid以取得该子进程的进程ID以及它的终止状态。

(3) 执行系统默认动作。对大多数信号的系统默认动作是终止该进程。

每一个信号都有一个缺省动作,它是当进程没有给这个信号指定处理程序时,内核对信号的处理。有5种缺省的动作:

n  异常终止(abort):在进程的当前目录下,把进程的地址空间内容、寄存器内容保存到一个叫做core的文件中,而后终止进程。

n  退出(exit):不产生core文件,直接终止进程。

n  忽略(ignore):忽略该信号。

n  停止(stop):挂起该进程。

n  继续(continue):如果进程被挂起,则恢复进程的运行。否则,忽略信号。

 

2,信号发送与捕捉

2.1  kill()和raise()

kill()不仅可以中止进程,也可以向进程发送其他信号。

与kill函数不同的是,raise()函数运行向进程自身发送信号。

#include <sys/types.h>

#include <signal.h>

int kill(pid_t pid, int signo) ;

int raise(int signo) ;

         两个函数返回:若成功则为0,若出错则为-1。

kill的pid参数有四种不同的情况:

n   pid>0 将信号发送给进程ID为pid的进程。

n   pid == 0 将信号发送给其进程组I D等于发送进程的进程组ID,而且发送进程有许可权向其发送信号的所有进程。

n   pid < 0 将信号发送给其进程组ID等于pid绝对值,而且发送进程有许可权向其发送信号的所有进程。如上所述一样,“所有进程”并不包括系统进程集中的进程。

n  pid ==-1 POSIX.1未定义此种情况。

kill()实例见:kill.c

2.2  alarm和pause函数

使用alarm函数可以设置一个时间值(闹钟时间),在将来的某个时刻该时间值会被超过。当所设置的时间值被超过后,产生SIGALRM信号。如果不忽略或不捕捉此信号,则其默认动作是终止该进程。

#include <unistd.h>

unsigned int alarm(unsigned int seconds) ;

返回:0或以前设置的闹钟时间的余留秒数

参数seconds的值是秒数,经过了指定的seconds秒后会产生信号SIGALRM。

每个进程只能有一个闹钟时间。如果在调用alarm时,以前已为该进程设置过闹钟时间,而且它还没有超时,则该闹钟时间的余留值作为本次alarm函数调用的值返回。以前登记的闹钟时间则被新值代换。

如果有以前登记的尚未超过的闹钟时间,而且seconds值是0,则取消以前的闹钟时间,其余留值仍作为函数的返回值。

pause函数使调用进程挂起直至捕捉到一个信号。

#include <unistd.h>

int pause(void);

         返回:-1,errno设置为EINTR

只有执行了一个信号处理程序并从其返回时,pause才返回。

实例见:alarm.c

 

3,信号的处理

当系统捕捉到某个信号时,可以忽略该信号或是使用指定的处理函数来处理该信号,或者使用系统默认的方式。

信号处理的主要方法有两种,一种是使用简单的signal函数,另一种是使用信号集函数组。

3.1 signal()

#include <signal.h>

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

         返回:成功则为以前的信号处理配置,若出错则为SIG_ERR

func的值是: (a)常数SIG_IGN,或(b)常数SIG_DFL,或(c)当接到此信号后要调用的函数的地址。如果指定SIG_IGN ,则向内核表示忽略此信号(有两个信号SIGKILL和SIGSTOP不能忽略)。如果指定SIG_DFL,则表示接到此信号后的动作是系统默认动作。当指定函数地址时,我们称此为捕捉此信号。我们称此函数为信号处理程序(signal handler)或信号捕捉函数(signal-catching function)。

signal函数原型太复杂了,如果使用下面的typedef,则可使其简化。

typedef void sign(int);

sign *signal(int, handler *);

实例见:mysignal.c

 

3.2 信号集函数组

我们需要有一个能表示多个信号——信号集(signal set)的数据类型。将在sigprocmask()这样的函数中使用这种数据类型,以告诉内核不允许发生该信号集中的信号。信号集函数组包含几大模块: 创建函数集、登记信号集、检测信号集。

(1)创建函数集

#include <signal.h>

int sigemptyset(sigset_t * set) ;

int sigfillset(sigset_t * set) ;

int sigaddset(sigset_t * set,int signo) ;

int sigdelset(sigset_t * set,int signo) ;

         四个函数返回:若成功则为0,若出错则为-1

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

         返回:若真则为1,若假则为0。

sigemptyset: 初始化信号集合为空。

 sigfillset: 初始化信号集合为所有信号的集合。

 sigaddset: 将指定信号添加到现存集中。

 sigdelset: 从信号集中删除指定信号。

 sigismember: 查询指定信号是否在信号集合中。

 

(2)登记信号集

登记信号处理机主要用于决定进程如何处理信号。首先要判断出当前进程阻塞能不能传递给该信号的信号集。这首先使用sigprocmask函数判断检测或更改信号屏蔽字,然后使用sigaction函数改变进程接受到特定信号之后的行为。

一个进程的信号屏蔽字可以规定当前阻塞而不能递送给该进程的信号集。调用函数sigprocmask可以检测或更改(或两者)进程的信号屏蔽字。

# include <signal.h>

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

         返回:若成功则为0,若出错则为-1

oset是非空指针,进程的当前信号屏蔽字通过oset返回。其次,若set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。

用sigprocmask更改当前信号屏蔽字的方法,how参数设定:

n   SIG_BLOCK该该进程新的信号屏蔽字是其当前信号屏蔽字和set指向信号集的并集。set包含了我们希望阻塞的附加信号。

n   SIG_UNBLOCK该该进程新的信号屏蔽字是其当前信号屏蔽字和set所指向信号集的交集。set包含了我们希望解除阻塞的信号。

n   SIG_SETMASK该该进程新的信号屏蔽是set指向的值。

如果set是个空指针,则不改变该进程的信号屏蔽字, how的值也无意义。

sigaction函数的功能是检查或修改(或两者)与指定信号相关联的处理动作。此函数取代了UNIX早期版本使用的signal函数。

#include <signal.h>

int sigaction(int signo, const struct sigaction * act,struct sigaction * oact) ;

         返回:若成功则为0,若出错则为- 1

 参数signo是要检测或修改具体动作的信号的编号数。若act指针非空,则要修改其动作。如果oact指针非空,则系统返回该信号的原先动作。此函数使用下列结构:

struct sigaction {

         void (*sa_handler)(int signo);

         sigset_t sa_mask;
         int sa_flags;

         void (*sa_restore);

} ;

sa_handler是一个函数指针,指定信号关联函数,可以是自定义处理函数,还可以SIG_DFL或 SIG_IGN。

sa_mask是一个信号集,它可以指定在信号处理程序执行过程中哪些信号应当被阻塞。

sa_flags中包含许多标志位,是对信号进行处理的各种选项。具体如下:

n       SA_NODEFER/SA_NOMASK: 当捕捉到此信号时,在执行其信号捕捉函数时,系统不会自动阻塞此信号。

n       SA_NOCLDSTOP: 进程忽略子进程产生的任何SIGSTOP、SIGTSTP、SIGTTIN和SIGTTOU信号

n       SA_RESTART: 可让重启的系统调用重新起作用。

n       SA_ONESHOT/SA_RESETHAND: 自定义信号只执行一次,在执行完毕后恢复信号的系统默认动作。

 

(3)检测信号集

检测信号是信号处理的后续步骤,但不是必须的。 sigpending函数运行进程检测“未决”信号(进程不清楚他的存在),并进一步决定对他们做何处理。

 sigpending返回对于调用进程被阻塞不能递送和当前未决的信号集。

#include <signal.h>

int sigpending(sigset_t * set) ;

         返回:若成功则为0,若出错则为-1

信号集实例见:sigaction.c

 

 

 

共享内存

共享内存区域是被多个进程共享的一部分物理内存。如果多个进程都把该内存区域映射到自己的虚拟地址空间,则这些进程就都可以直接访问该共享内存区域,从而可以通过该区域进行通信。共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。

共享内存实现分为两个步骤:

 一、创建共享内存,使用shmget函数。

 二、映射共享内存,将这段创建的共享内存映射到具体的进程空间去,使用shmat函数。

 

系统调用:shmget( ) ;

原型:int shmget ( key_t key, int size, int shmflg );

         返回值:如果成功,返回共享内存段标识符。

         如果失败,则返回- 1:

         errno = EINVAL (无效的内存段大小)

                      EEXIST (内存段已经存在,无法创建)

                      EIDRM (内存段已经被删除)

                      ENOENT (内存段不存在)

                     EACCES (权限不够)

                      ENOMEM (没有足够的内存来创建内存段)

 

系统调用:shmat();

原型:int shmat ( int shmid, char *shmaddr, int shmflg);

         返回值:如果成功,则返回共享内存段连接到进程中的地址。

         如果失败,则返回- 1:

         errno = EINVAL (无效的IPC ID 值或者无效的地址)

                      ENOMEM (没有足够的内存)

                      EACCES (存取权限不够)

 

当一个进程不在需要共享的内存段时,它将会把内存段从其地址空间中脱离。

系统调用:shmdt();

调用原型:int shmdt ( char *shmaddr );

         返回值:如果失败,则返回- 1:

                   errno = EINVAL (无效的连接地址)

共享内存实例见:shmadd.c

 

 

 

 

消息队列

消息缓冲队列通信机制

1.消息缓冲队列通信机制简介

    由于消息缓冲机制中所使用的缓冲区为公用缓冲区,因此使用消息缓冲机制传送数据时,两通信进程必须满足如下条件。

第一,在发送进程把写入消息的缓冲区挂入消息队列时,应禁止其他进程对该消息队列的访问,否则,将引起消息队列的混乱。同理,当接收进程正从消息队列中取消息时,也应禁止其他进程对该队列的访问。

第二,当缓冲区中无消息存在时,接收进程不能接收到任何消息;而发送进程是否可以发送消息,则只由发送进程是否能够申请到缓冲区决定。

2.消息缓冲队列通信机制中的数据结构

(1)消息缓冲区

    typedefstruct message buffer

    {

       sender;   //发送者进程标识符

       size;                  //消息长度

       text;         //消息正文

       next;        //指向下一个消息缓冲区的指针

    }

(2)PCB中有关进程通信的数据项

    typedefstruct message block

    {

      …

      mq;   //消息队列队首指针

      mutex;//消息队列互斥信号量,初值为1

      sm;    //消息队列资源信号量,用于消息队列中的消息计数,初值为0

      …

}

 

消息队列的实现

消息队列就是消息的一个链表,它允许一个或多个进程向它写消息,一个或多个进程从中读消息。具有一定的FIFO的特性,但是可实现消息的随即查询。这些消息存在于内核中,由“队列ID”来标识。

 消息队列的实现包括创建和打开队列、添加消息、读取消息和控制消息队列这四种操作。

         msgget:创建和打开队列,其消息数量受系统限制。

         msgsnd:添加消息,将消息添加到消息队列尾部。

         msgrcv:读取消息,从消息队列中取走消息。

         msgctl:控制消息队列。

 

nt msgget (key_t key, int flag)

   key:返回新的或已有队列的ID,IPC_PRIVATE

int msgsnd (int msqid, struct msgbuf              *msgp, size_t msgsz, int flag)

         其中:msqid是消息队列的队列ID;

n   msgp是消息内容所在的缓冲区;

n   msgsz是消息的大小;

n   msgflg是标志,IPC_NOWAIT若消息并没有立交发送而调用进程会立即返回。

 

struct msgbuf

{

         long mtype; /* type of message */

         char mtext[1]; /* message text */

};

int msgrcv (int msqid, struct       msgbuf *msgp, size_t msgsz,long          msgtyp, int flag)

n   msqid是消息队列的引用标识符;

n   msgp是接收到的消息将要存放的缓冲区;

n   msgsz是消息的大小;

n   msgtyp是期望接收的消息类型;

n   msgflg是标志。

 

int msgctl (int msqid, int cmd, struct msqid_ds *buf)

msqid是消息队列的引用标识符;

cmd是执行命令;

buf是一个缓冲区。

cmd参数指定对于由msqid规定的队列要执行的命令:

n   IPC_STAT 取此队列的msqid_ds结构,并将其存放在buf指向的结构中。

n   IPC_SET 按由buf指向的结构中的值,设置与此队列相关的结构中的下列四个字段:

         msg_perm.uid、msg_perm.gid、msg_perm;mode和msg_qbytes。此命令只能由下列两种进程执行:一种是其有效用户ID等于msg_perm.cuid或msg_perm.uid;另一种是具有超级用户特权的进程。只有超级用户才能增加msg_qbytes的值。

n   IPC_RMID 从系统中删除该消息队列以及仍在该队列上的所有数据。这种删除立即生效。仍在使用这一消息队列的其他进程在它们下一次试图对此队列进行操作时,将出错返回EIDRM。

此命令只能由下列两种进程执行:一种是其有效用户ID等于msg_perm.cuid或msg_perm.uid;另一种是具有超级用户特权的进程。

消息队列实例见:msg.c

声明: 本文由( 鲁智森也有文化 )原创编译,转载请保留链接: 信号机制、共享内存和消息队列

信号机制、共享内存和消息队列:等您坐沙发呢!

发表评论


QQ群互动

Linux系统与内核学习群:194051772

WP建站技术学习交流群:194062106

魔豆之路QR

魔豆的Linux内核之路

魔豆的Linux内核之路

优秀工程师当看优秀书籍

优秀程序员,要看优秀书!

赞助商广告

友荐云推荐