进程间通信(Interprocess Communication,IPC)是一个描述两个进程彼此交换信息的通用术语。一般情况下,通信的两个进程即可以运行在同一台机器上,也可以运行在不同的机器上。进程间的通信是数据的交换,两个或多个进程合作处理数据或同步信息,以帮助两个彼此独立但相关联的进程调度工作,避免重复工作。进程间通信方式有很多种,比如可以使用socket、使用管道、消息队列、文件、共享内存等。
1、管道
管道是进程间通信中最古老的方式,他使得数据以一种数据流的方式在多个进程之间流动。管道相当于文件系统上的一个文件,用来缓存所要传输的数据,但是在某些特性上又不同于文件,例如,当数据读出后,管道中的数据就没有了, 单文件就没有这个特性。综合来说,管道具有以下特点:
1) 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;
2) 匿名管道只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程);
3) 单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。
管道分为pipe(无名管道)和fifo(命名管道)两种,除了建立、打开、删除的方式不同外,这两种管道几乎是一样的。他们都是通过内核缓冲区实现数据传输。前者用于父进程和子进程间的通信,后者用于运行于同一台机器上的任意两个进程间的通信
- pipe用于相关进程之间的通信,例如父进程和子进程,它通过pipe()系统调用来创建并打开,当最后一个使用它的进程关闭对他的引用时,pipe将自动撤销。
- FIFO即命名管道,在磁盘上有对应的节点,但没有数据块——换言之,只是拥有一个名字和相应的访问权限,通过mknode()系统调用或者mkfifo()函数来建立的。一旦建立,任何进程都可以通过文件名将其打开和进行读写,而不局限于父子进程,当然前提是进程对FIFO有适当的访问权。当不再被进程使用时,FIFO在内存中释放,但磁盘节点仍然存在。
pipe(无名管道)
Linux下使用pipe()创建一个匿名半双工管道,其函数原型如下:
1 #include <unistd.h> 2 int pipe(int fd[2]); // 返回值:若成功返回0,失败返回-1
参数fd是一个长度为2的文件描述符数组,fd[0]是读出端,fd[1]是写入端,函数返回0表示成功,返回-1则表示失败。
但函数返回成功,则表明自动维护了一个从fd[1]到fd[0]的数据通道。
要关闭管道只需将这两个文件描述符关闭即可。
单独操作一个进程管道是没有意义的,管道的应用一般体现在父子进程或者兄弟进程之间的通信上。如果要建立一个父进程到子进程的数据通道,需要先调用函数pipe(),紧接着调用函数fork(),由于子程序自动继承父进程的数据段,则子进程同时拥有管道的操作权,此时管道的方向取决于用户怎么维护该管道。
当用户想要一个父进程的数据管道是,需要先在父进程中关闭管道的独处端,然后相应的在子进程中关闭管道的输出端,相反,当维护子进程到父进程的数据通道时,则需要在父进程中关闭输出端,在子进程中关闭读入端即可。总之,使用函数pipe()和fork()创建子进程,维护父子进程中管道的数据方法是:在父进程中向子进程发送消息,在子进程接受消息。
若要数据流从父进程流向子进程,则关闭父进程的读端(fd[0]
)与子进程的写端(fd[1]
);反之,则可以使数据流从子进程流向父进程。
#include<stdio.h> #include<unistd.h> int main() { int fd[2]; // 两个文件描述符 pid_t pid; char buff[20]; if(pipe(fd) < 0) // 创建管道 printf("Create Pipe Error! "); if((pid = fork()) < 0) // 创建子进程 printf("Fork Error! "); else if(pid > 0) // 父进程 { close(fd[0]); // 关闭读端 write(fd[1], "hello world ", 12); } else { close(fd[1]); // 关闭写端 read(fd[0], buff, 20); printf("%s", buff); } return 0; }
程序运行结果如下:
FIFO(有名管道)
FIFO(First Input First Output)是一种文件类型,在文件系统中可以看到。通过FIFO,不相关的进程也能交换数据。
FIFO的通信方式类似于在进程中使用文件类传输数据,只不过FIFO类型的文件同时具有管道的特性,在数据读出时,FIFO中同时清除了数据。
创建FIFO类似于创建文件,FIFO就像普通文件一样,也可以通过路径名进行访问。Linux系统提供了函数mkfifo(),用于创建FIFO,函数原型如下:
#include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> int mkfifo(const char *pathname, mode_t mode); int mkfifoat(int dirfd, const char *pathname, mode_t mode);
函数mkfifo()中参数mode的规格说明与函数open()中的参数mode的规格说明相同。函数mkfifoat()与函数mkfifo()相似,但是函数mkfifoat()可以被用来在文件描述符dirfd表示的目录相关位置创建一个有名管道,有以下三种情形:
(1)如果参数pathname指定的是绝对路径名,则参数dirfd会被忽略,并且函数mikfifoat()的行为和函数mkfifo()的行为类似。
(2)如果参数pathname指定是相对相对路径名,则参数dirfd是一个打开目录的有效文件描述符,路径名和目录相关。
(3)如果参数pathname 制定的是相对路径名,则参数dirfd是一个特殊值AT_FDCWD,则路径名以当前目录开始,函数mkfifoat()于mkfifo()类似。
当使用函数open()打开一个有名管道时,非阻塞标识(O_NONBLOCK)会产生下列影响:
(1)在一般情况下(没有制定O_NONBLOCK),只读open()要阻塞到某个进程为写而打开这个FIFO为止;类似的,只写open()要阻塞到某个其他进程为读而打开它为止。
(2)如果指定了O_NONBLOCK,则只读立即返回;但是如果没有进程为读而打开一个FIFO,那么只写open()将返回-1,同时errno设置为ENXIO。
类似于管道,若写一个尚无进程为读而打开的FIFO,将产生信号SIGPIPE,若某个FIFO的最后一个写进程关闭了该FIFO,则将为该FIFO的读进程将产生一个文件结束标志。
示例:
编写fifo_write.c和fifo_read.c如下
fifo_write.c
#include<stdio.h> #include<stdlib.h> // exit #include<fcntl.h> // O_WRONLY #include<sys/stat.h> #include<time.h> // time int main() { int fd; int n, i; char buf[1024]; time_t tp; printf("I am %d process. ", getpid()); // 说明进程ID if((fd = open("fifo1", O_WRONLY)) < 0) // 以写打开一个FIFO { perror("Open FIFO Failed"); exit(1); } for(i=0; i<10; ++i) { time(&tp); // 取系统当前时间 n=sprintf(buf,"Process %d's time is %s",getpid(),ctime(&tp)); printf("Send message: %s", buf); // 打印 if(write(fd, buf, n+1) < 0) // 写入到FIFO中 { perror("Write FIFO Failed"); close(fd); exit(1); } sleep(1); // 休眠1秒 } close(fd); // 关闭FIFO文件 return 0; }
fifo_read.c
#include<stdio.h> #include<stdlib.h> #include<errno.h> #include<fcntl.h> #include<sys/stat.h> int main() { int fd; int len; char buf[1024]; if(mkfifo("fifo1", 0666) < 0 && errno!=EEXIST) // 创建FIFO管道 perror("Create FIFO Failed"); if((fd = open("fifo1", O_RDONLY)) < 0) // 以读打开FIFO { perror("Open FIFO Failed"); exit(1); } while((len = read(fd, buf, 1024)) > 0) // 读取FIFO管道 printf("Read message: %s", buf); close(fd); // 关闭FIFO文件 return 0; }
创建一个空的fifo1的文件后,先运行fifo_write,再运行fifo_read可得到如下运行结果
2、信号
信号是Linux系统响应某些条件而产生的一个事件,是进程间通信的经典方法。Linux有很多种信号,每个信号都有一个名字,这些名字都以三个字符SIG开头,常用的信号量如下表所示,可以使用Shell命令kill -l查看当前系统提供的信号。
Signal
|
Description
|
SIGABRT
|
由调用abort函数产生,进程非正常退出
|
SIGALRM
|
用alarm函数设置的timer超时或setitimer函数设置的interval timer超时
|
SIGBUS
|
某种特定的硬件异常,通常由内存访问引起
|
SIGCANCEL
|
由Solaris Thread Library内部使用,通常不会使用
|
SIGCHLD
|
进程Terminate或Stop的时候,SIGCHLD会发送给它的父进程。缺省情况下该Signal会被忽略
|
SIGCONT
|
当被stop的进程恢复运行的时候,自动发送
|
SIGEMT
|
和实现相关的硬件异常
|
SIGFPE
|
数学相关的异常,如被0除,浮点溢出,等等
|
SIGFREEZE
|
Solaris专用,Hiberate或者Suspended时候发送
|
SIGHUP
|
发送给具有Terminal的Controlling Process,当terminal被disconnect时候发送
|
SIGILL
|
非法指令异常
|
SIGINFO
|
BSD signal。由Status Key产生,通常是CTRL+T。发送给所有Foreground Group的进程
|
SIGINT
|
由Interrupt Key产生,通常是CTRL+C或者DELETE。发送给所有ForeGround Group的进程
|
SIGIO
|
异步IO事件
|
SIGIOT
|
实现相关的硬件异常,一般对应SIGABRT
|
SIGKILL
|
无法处理和忽略。中止某个进程
|
SIGLWP
|
由Solaris Thread Libray内部使用
|
SIGPIPE
|
在reader中止之后写Pipe的时候发送
|
SIGPOLL
|
当某个事件发送给Pollable Device的时候发送
|
SIGPROF
|
Setitimer指定的Profiling Interval Timer所产生
|
SIGPWR
|
和系统相关。和UPS相关。
|
SIGQUIT
|
输入Quit Key的时候(CTRL+)发送给所有Foreground Group的进程
|
SIGSEGV
|
非法内存访问
|
SIGSTKFLT
|
Linux专用,数学协处理器的栈异常
|
SIGSTOP
|
中止进程。无法处理和忽略。
|
SIGSYS
|
非法系统调用
|
SIGTERM
|
请求中止进程,kill命令缺省发送
|
SIGTHAW
|
Solaris专用,从Suspend恢复时候发送
|
SIGTRAP
|
实现相关的硬件异常。一般是调试异常
|
SIGTSTP
|
Suspend Key,一般是Ctrl+Z。发送给所有Foreground Group的进程
|
SIGTTIN
|
当Background Group的进程尝试读取Terminal的时候发送
|
SIGTTOU
|
当Background Group的进程尝试写Terminal的时候发送
|
SIGURG
|
当out-of-band data接收的时候可能发送
|
SIGUSR1
|
用户自定义signal 1
|
SIGUSR2
|
用户自定义signal 2
|
SIGVTALRM
|
setitimer函数设置的Virtual Interval Timer超时的时候
|
SIGWAITING
|
Solaris Thread Library内部实现专用
|
SIGWINCH
|
当Terminal的窗口大小改变的时候,发送给Foreground Group的所有进程
|
SIGXCPU
|
当CPU时间限制超时的时候
|
SIGXFSZ
|
进程超过文件大小限制
|
SIGXRES
|
Solaris专用,进程超过资源限制的时候发送
|
当引发信号的时间发生时,为进程产生一个信号,有以下两种情况:
(1)硬件情况:例如按下键盘或其他硬件故障
(2)软件产生:例如除0操作或者执行kill()函数、raise()函数等。
一个完整的信号周期包括信号的产生、信号在进程内的注册与注销以及执行信号处理的三个阶段。进程收到信号后有三种处理方式:
(1)捕捉信号:当信号发生时,进程可执行相应的自处理函数。
(2)忽略信号:对该信号不做任何处理,但SIGKILL与SIGSTOP信号除外。
(3)执行默认操作:Linux对每种信号都规定了默认操作。
下面是信号操作中常用的函数:
函数signal
函数signal()进行信号处理时,需要指出要处理的信号和处理函数信息,其函数原型如下:
#include <signal.h> typedef void (*sighandler_t)(int) sighandler_t signal(int signum,sighandler_t handler);
函数执行成功,返回以前的信号处理配置或者处理函数;执行失败返回SIGERR即-1。
参数signum用于指定待响应的信号;参数handler为信号处理函数,有以下三种情况:
(1)SIG_IGN:忽略该信号。
(2)SIG_DFL:默认方式为处理该信号。
(3)自定义信号处理函数指针,返回类型为void。
例子:下面看一个简单的捕捉SIGUSR1信号的处理函数。
#include<stdio.h> #include<signal.h> #include<unistd.h> void sig_handler(int signo) { if (signo == SIGINT) printf("received SIGINT "); } int main(void) { if (signal(SIGINT, sig_handler) == SIG_ERR) printf(" can't catch SIGINT "); // A long long wait so that we can easily issue a signal to this process while(1) sleep(1); return 0; }
在上面的代码中,我们使用无限循环模拟了一个长时间运行的进程。函数sig_handler用作信号处理程序。通过在main()函数中将系统调用'signal'作为第二个参数传递给内核,该函数被注册到内核。函数'signal'的第一个参数是我们希望信号处理程序处理的信号,在这种情况下是SIGINT。
在其后,函数sleep(1)的使用有一个原因。这个函数已经在while循环中使用,以便while循环在一段时间后执行(在这种情况下,即1秒)。这变得很重要,否则无限循环运行可能会消耗大部分CPU,会使计算机非常慢。
当进程运行,信号SIGINT由按下Ctrl-C发出,信号SIGQUIT由按下Ctrl-发出,可以得到如下结果:
需要提到的是signal函数是一个比较老的函数,在实际应用中应该避免使用该函数,而使用sigaction函数,后面将详细介绍这个函数。
函数sigaction
函数sigaction与函数signal功能类似,主要用于定义在接收到信号后应该采取的处理方式,其函数原型如下:
#include <signal.h> int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));
sigaction函数用于改变进程接收到特定信号后的行为。该函数的第一个参数为信号的值,可以为除SIGKILL及SIGSTOP外的任何一个特定有效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误)。第二个参数是指向结构sigaction的一个实例的指针,在结构sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理;第三个参数oldact指向的对象用来保存返回的原来对相应信号的处理,可指定oldact为NULL。如果把第二、第三个参数都设为NULL,那么该函数可用于检查信号的有效性。
第二个参数最为重要,其中包含了对指定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉哪些信号等等。
sigaction结构定义如下:
struct sigaction { union{ __sighandler_t _sa_handler; void (*_sa_sigaction)(int,struct siginfo *, void *); }_u sigset_t sa_mask; unsigned long sa_flags; }
其中,结构体的关键成员含义
1、联合数据结构中的两个元素_sa_handler以及*_sa_sigaction指定信号关联函数,即用户指定的信号处理函数。除了可以是用户自定义的处理函数外,还可以为SIG_DFL(采用缺省的处理方式),也可以为SIG_IGN(忽略信号)。
2、由_sa_sigaction是指定的信号处理函数带有三个参数,是为实时信号而设的(当然同样支持非实时信号),它指定一个3参数信号处理函数。第一个参数为信号值,第三个参数没有使用,第二个参数是指向siginfo_t结构的指针,结构中包含信号携带的数据值,参数所指向的结构如下:
siginfo_t { int si_signo; /* 信号值,对所有信号有意义*/ int si_errno; /* errno值,对所有信号有意义*/ int si_code; /* 信号产生的原因,对所有信号有意义*/ union{ /* 联合数据结构,不同成员适应不同信号 */ //确保分配足够大的存储空间 int _pad[SI_PAD_SIZE]; //对SIGKILL有意义的结构 struct{ ... }... ... ... ... ... //对SIGILL, SIGFPE, SIGSEGV, SIGBUS有意义的结构 struct{ ... }... ... ... } }
前面在讨论系统调用sigqueue发送信号时,sigqueue的第三个参数就是sigval联合数据结构,当调用sigqueue时,该数据结构中的数据就将拷贝到信号处理函数的第二个参数中。这样,在发送信号同时,就可以让信号传递一些附加信息。信号可以传递信息对程序开发是非常有意义的。
3、sa_mask指定在信号处理程序执行过程中,哪些信号应当被阻塞。缺省情况下当前信号本身被阻塞,防止信号的嵌套发送,除非指定SA_NODEFER或者SA_NOMASK标志位。
注:请注意sa_mask指定的信号阻塞的前提条件,是在由sigaction()安装信号的处理函数执行过程中由sa_mask指定的信号才被阻塞。
4、sa_flags中包含了许多标志位,包括刚刚提到的SA_NODEFER及SA_NOMASK标志位。另一个比较重要的标志位是SA_SIGINFO,当设定了该标志位时,表示信号附带的参数可以被传递到信号处理函数中,因此,应该为sigaction结构中的sa_sigaction指定处理函数,而不应该为sa_handler指定信号处理函数,否则,设置该标志变得毫无意义。即使为sa_sigaction指定了信号处理函数,如果不设置SA_SIGINFO,信号处理函数同样不能得到信号传递过来的数据,在信号处理函数中对这些信息的访问都将导致段错误(Segmentation fault)。
函数kill
函数kill()用于向自身或其他进程发送信号,函数原型如下:
#include <sys/types.h> #include <signal.h> int kill(pid_t pid,int signo)
参数sig用于指定要发送的信号。参数pid用于指定目标进程,设定值如下:
pid>0 进程ID为pid的进程
pid=0 同一个进程组的进程
pid<0 pid!=-1 进程组ID为 -pid的所有进程
pid=-1 除发送进程自身外,所有进程ID大于1的进程
Sinno是信号值,当为0时(即空信号),实际不发送任何信号,但照常进行错误检查,因此,可用于检查目标进程是否存在,以及当前进程是否具有向目标发送信号的权限(root权限的进程可以向任何进程发送信号,非root权限的进程只能向属于同一个session或者同一个用户的进程发送信号)。
Kill()最常用于pid>0时的信号发送。该调用执行成功时,返回值为0;错误时,返回-1,并设置相应的错误代码errno。下面是一些可能返回的错误代码:
EINVAL:指定的信号sig无效。
ESRCH:参数pid指定的进程或进程组不存在。注意,在进程表项中存在的进程,可能是一个还没有被wait收回,但已经终止执行的僵死进程。
EPERM: 进程没有权力将这个信号发送到指定接收信号的进程。因为,一个进程被允许将信号发送到进程pid时,必须拥有root权力,或者是发出调用的进程的UID 或EUID与指定接收的进程的UID或保存用户ID(savedset-user-ID)相同。如果参数pid小于-1,即该信号发送给一个组,则该错误表示组中有成员进程不能接收该信号。
函数raise
函数raise()用于进程向自身发送信号,其函数原型如下:
#include <signal.h> int raise(int signo)
参数signo为即将发送的信号值。调用成功返回 0;否则,返回 -1。
函数pause
函数pause()用于将调用进程挂起直至捕捉到信号为止,通常用于判断信号是否到达,其函数原型如下:
#include <unistd.h> int pause(void);
函数sigqueue
sigqueue()是比较新的发送信号系统调用,主要是针对实时信号提出的(当然也支持前32种),支持信号带有参数,与函数sigaction()配合使用。
#include <sys/types.h> #include <signal.h> int sigqueue(pid_t pid, int sig, const union sigval val)
sigqueue的第一个参数是指定接收信号的进程ID,第二个参数确定即将发送的信号,第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。
typedef union sigval {
int sival_int;
void *sival_ptr;
}sigval_t;
sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。如果signo=0,将会执行错误检查,但实际上不发送任何信号,0值信号可用于检查pid的有效性以及当前进程是否有权限向目标进程发送信号。
在调用sigqueue时,sigval_t指定的信息会拷贝到对应sig 注册的3参数信号处理函数的siginfo_t结构中,这样信号处理函数就可以处理这些信息了。由于sigqueue系统调用支持发送带参数信号,所以比kill()系统调用的功能要灵活和强大得多。
函数alarm
函数alarm()也称为闹钟函数,是专门为信号SIGALARM而设的,用于在指定的时间项进程本身发送SIGALARM信号,其函数原型如下:
#include <unistd.h>
unsigned int alarm(unsigned int seconds)
如果指定的参数seconds为0,则不再发送 SIGALRM信号。后一次设定将取消前一次的设定。该调用返回值为上次定时调用到发送之间剩余的时间,或者因为没有前一次定时调用而返回0。
函数setitimer
现在的系统中很多程序不再使用alarm调用,而是使用setitimer调用来设置定时器,用getitimer来得到定时器的状态,这两个调用的声明格式如下:
#include <sys/time.h> int getitimer(int which, struct itimerval *value); int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);
该系统调用给进程提供了三个定时器,它们各自有其独有的计时域,当其中任何一个到达,就发送一个相应的信号给进程,并使得计时器重新开始。三个计时器由参数which指定,如下所示:
TIMER_REAL:按实际时间计时,计时到达将给进程发送SIGALRM信号。
ITIMER_VIRTUAL:仅当进程执行时才进行计时。计时到达将发送SIGVTALRM信号给进程。
ITIMER_PROF:当进程执行时和系统为该进程执行动作时都计时。与ITIMER_VIR-TUAL是一对,该定时器经常用来统计进程在用户态和内核态花费的时间。计时到达将发送SIGPROF信号给进程。
定时器中的参数value用来指明定时器的时间,其结构如下:
struct itimerval { struct timeval it_interval; /* 下一次的取值 */ struct timeval it_value; /* 本次的设定值 */ };
该结构中timeval结构定义如下:
struct timeval { long tv_sec; /* 秒 */ long tv_usec; /* 微秒,1秒 = 1000000 微秒*/ };
在setitimer 调用中,参数ovalue如果不为空,则其中保留的是上次调用设定的值。定时器将it_value递减到0时,产生一个信号,并将it_value的值设定为it_interval的值,然后重新开始计时,如此往复。当it_value设定为0时,计时器停止,或者当它计时到期,而it_interval 为0时停止。调用成功时,返回0;错误时,返回-1,并设置相应的错误代码errno:
EFAULT:参数value或ovalue是无效的指针。
EINVAL:参数which不是ITIMER_REAL、ITIMER_VIRT或ITIMER_PROF中的一个。
下面是关于setitimer调用的一个简单示范,在该例子中,每隔一秒发出一个SIGALRM,每隔0.5秒发出一个SIGVTALRM信号:
#include <signal.h> #include <unistd.h> #include <stdio.h> #include <sys/time.h> int sec; void sigroutine(int signo) { switch (signo) { case SIGALRM: printf("Catch a signal -- SIGALRM "); break; case SIGVTALRM: printf("Catch a signal -- SIGVTALRM "); break; } return; } int main() { struct itimerval value,ovalue,value2; sec = 5; printf("process id is %d ",getpid()); signal(SIGALRM, sigroutine); signal(SIGVTALRM, sigroutine); value.it_value.tv_sec = 1; value.it_value.tv_usec = 0; value.it_interval.tv_sec = 1; value.it_interval.tv_usec = 0; setitimer(ITIMER_REAL, &value, &ovalue); value2.it_value.tv_sec = 0; value2.it_value.tv_usec = 500000; value2.it_interval.tv_sec = 0; value2.it_interval.tv_usec = 500000; setitimer(ITIMER_VIRTUAL, &value2, &ovalue); for (;;) ; }
程序运行结果如下:
函数abort
向进程发送SIGABORT信号,默认情况下进程会异常退出,当然可定义自己的信号处理函数。
#include <stdlib.h> void abort(void);
即使SIGABORT被进程设置为阻塞信号,调用abort()后,SIGABORT仍然能被进程接收。该函数无返回值。
信号集及信号集操作函数:信号集是一个能表示多个信号的数据类型
信号集就是用来放置多个信号,和select函数中的描述符集相似。系统也提供了一系列的信号集函数,这些函数原型如下:
#include <signal.h> int sigemptyset(sigset_t *set);//清空信号集set int sigfillset(sigset_t *set);//将所有信号填充到信号集set,set指向的信号集中将包含linux支持的64种信号; int sigaddset(sigset_t *set, int signum)//在set指向的信号集中加入signum信号; int sigdelset(sigset_t *set, int signum);//在set指向的信号集中删除signum信号; int sigismember(const sigset_t *set, int signum);//判定信号signum是否在set指向的信号集中。
函数sigprocmask
sigprocmask函数可以检测或更改(或两者)进程的信号屏蔽字,函数原型如下:
int sigpromask(int how,const sigset_t* set,sigset_t* oset);
参数oset,输出参数,若非空,则返回进程的当前屏蔽字。
参数set,输入参数,若非空,则表示需要修改的信号屏蔽字。
参数how,输入参数,表示以何种方式修改当前信号屏蔽字。如果set为空,则how无意义。
参数how的取值有:
(1)SIGBLOCK 该进程新的信号屏蔽字是其当前信号屏蔽字和set指向信号集的并集。set包含了我们希望阻塞的附加信号。
(2)SIGUBLOCK 该进程新的心啊后屏蔽字是当前信号除去set所指向的信号集。set包含了我们希望解除阻塞的信号。
(3)SIGSETMASK 赋值操作,该进程新的信号屏蔽字是set指向的值。
函数sigsuspend
sigsuspend函数就是在捕捉一个信号或发生了一个会终止该进程的信号之前,将进程投入睡眠,直到该信号来到并从信号处理函数中返回。sigsuspend函数原型如下:
int sigsuspend(const sigset_t *mask));
参数sigmask,将进程的信号屏蔽字设置为sigmask,也就是说进程会在睡眠后的信号屏蔽字。
因此在使用sigsuspend函数时,当该函数返回后,应该将进程原来的屏蔽字再重新设置回去。
函数sigpending
sigpending函数返回在送往进程的时候被阻塞挂起的信号集合。函数原型为:
int sigpending(sigset_t *set)
sigpending(sigset_t *set))获得当前已递送到进程,却被阻塞的所有信号,在set指向的信号集中返回结果。
通过一个实例进行理解
#include<stdlib.h> #include<stdio.h> #include<unistd.h> #include<signal.h> #include<sys/types.h> void print_sigset(sigset_t *set) { int i; for(i=1;i<64;++i) { if(sigismember(set,i)) { printf("1"); }else{ printf("0"); } } } int main() { sigset_t myset; sigemptyset(&myset);//清空信号集 sigaddset(&myset,SIGINT);//向信号集添加 sigaddset(&myset,SIGQUIT); sigaddset(&myset,SIGUSR1); print_sigset(&myset); return 0; }
信号集运行结果如图
3、消息队列
消息队列是消息的链接表,存放在内核中并由消息队列标识符标识。消息队列与FIFO有许多相似之处,但是少了管道打开文件和关闭文件的麻烦。它可用于不同进程间的通信,但是其重点还是线程之间的一种通信方式。现在首先详细对进程间的通信进行讲解。
1、msgget()
msgget用来创建和访问一个消息队列,函数原型如下:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget ( key_t key , int msgflg );
与其他的IPC机制一样,程序必须提供一个键来命名某个特定的消息队列。msgflg是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。msgflg可以与IPC_CREAT做或操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被忽略,而只返回一个标识符。
它返回一个以key命名的消息队列的标识符(非零整数),失败时返回-1.
2、msgsnd()
该函数用来把消息添加到消息队列中。它的原型为:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);
msgid是由msgget函数返回的消息队列标识符。
msg_ptr是一个指向准备发送消息的指针,但是消息的数据结构却有一定的要求,指针msg_ptr所指向的消息结构一定要是以一个长整型成员变量开始的结构体,接收函数将用这个成员来确定消息的类型。所以消息结构要定义成这样:
struct my_message { long int message_type; /* The data you wish to transfer */ };
msg_sz 是msg_ptr指向的消息的长度,注意是消息的长度,而不是整个结构体的长度,也就是说msg_sz是不包括长整型消息类型成员变量的长度。
msgflg 用于控制当前消息队列满或队列消息到达系统范围的限制时将要发生的事情。
如果调用成功,消息数据的一分副本将被放到消息队列中,并返回0,失败时返回-1.
3、msgrcv()
从消息队列中读取以及删除一条消息,并将内容复制进MSGP指向的缓冲区中,其函数原型如下:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgrcv( int msgid , struct msgbuf* msgp , int msgsz , long msgtyp, int msgflg); 成功时返回所获取信息的长度,失败返回-1,错误信息存于error
msgid, msg_ptr, msg_st 的作用也函数msgsnd()函数的一样。
msgtyp: 信息类型。 取值如下:
msgtyp = 0 ,不分类型,直接返回消息队列中的第一项 。
msgtyp > 0 ,返回第一项 msgtyp与 msgbuf结构体中的mtype相同的信息 。
msgtyp <0 , 返回第一项 mtype小于等于msgtyp绝对值的信息。
msgflg 用于控制当队列中没有相应类型的消息可以接收时将发生的事情。
调用成功时,该函数返回放到接收缓存区中的字节数,消息被复制到由msg_ptr指向的用户分配的缓存区中,然后删除消息队列中的对应消息。失败时返回-1。
4、msgctl()函数
该函数用来控制消息队列,它与共享内存的shmctl函数相似,它的原型为:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h>
int msgctl(int msgid, int command, struct msgid_ds *buf);
command是将要采取的动作,它可以取3个值,
- IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列的当前关联值覆盖msgid_ds的值。
- IPC_SET:如果进程有足够的权限,就把消息列队的当前关联值设置为msgid_ds结构中给出的值
- IPC_RMID:删除消息队列
buf是指向msgid_ds结构的指针,它指向消息队列模式和访问权限的结构。msgid_ds结构至少包括以下成员:
struct msgid_ds { uid_t shm_perm.uid; uid_t shm_perm.gid; mode_t shm_perm.mode; };
示例:消息队列进行进程通信。
msg_client.c
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <sys/stat.h> #define MSG_FILE "msg_server.c" #define BUFFER 255 #define PERM S_IRUSR|S_IWUSR struct msgtype { long mtype; char buffer[BUFFER+1]; }; int main(int argc,char **argv) { struct msgtype msg; key_t key; int msgid; if(argc!=2) { fprintf(stderr,"Usage:%s string a",argv[0]); exit(1); } if((key=ftok(MSG_FILE,'a'))==-1) { fprintf(stderr,"Creat Key Error:%sa ",strerror(errno)); exit(1); } if((msgid=msgget(key,PERM))==-1) { fprintf(stderr,"Creat Message Error:%sa ",strerror(errno)); exit(1); } msg.mtype=1; strncpy(msg.buffer,argv[1],BUFFER); msgsnd(msgid,&msg,sizeof(struct msgtype),0); memset(&msg,'