赞
踩
Linux下的进程通信机制基本是从unix平台继承而来的。传统的Unix进程间通信方式包括无名管道、有名管道以及信号。这些通信方式同样适用于Linux,在Linux中又增加了另外一些通信方式,如消息队列、信号量以及共享内存这些进程通信方式称为IPC,现在Linux中使用较多的进程间通信方式主要有以下几种。
(1)无名管道(pipe)以及有名管道(fifo)。无名管道可用于具有亲缘关系进程间通信(通过fork函数建立的父子进程通信);有名管道除此之外还可用于无亲缘关系直接进程的通信使用。
(2)信号。信号是在软件层次上对中断机制的一种模拟,是一种比较复杂的通信方式,用于通知进程某一事件发生。
(3)消息队列。消息队列是消息的链接表,他克服了前两种通信方式之间信息量有限的缺点,当进程拥有写权限时可向消息队列中添加新消息,当具有读权限时可向消息队列中读取信息。
(4)共享内存。这种通信方式是进程中最有效的一种通信方式。这种通信方式使得多个进程可以访问一块内存空间,不同进程可以及时看到对方进程对共享内存中数据的修改更新,但这种通信机制需要依靠某种同步机制,如互斥锁和信号量等。
(5)信号量。主要作为进程之间以及同一进程的不同线程之间的同步和互斥手段。‘
(6)套接字。套接字是一种使用更为广泛的进程间通信机制,可用于网络中不同主机之间的进程通信,应用非常广泛。
这种通信方式使Linux中管道通信的一种原始方式。
父进程与子进程共同访问内核实现通信。他具有以下几个特点:
(1)只能用于具有亲缘关系的进程间通信(就是写在同一个代码中的两个进程)
(2)是一个单工的通信模式,具有固定的读端和写端。
(3)管道也可以看做一个特殊的文件。对于他的读写也可以使用read()、write()等函数来进行操作。但是他并不存在与任何文件系统中,只存在于内存中。
无名管道的创建是基于文件描述符的通信方式。当一个管道建立时,他会创建两个文件描述符:fd[0]和fd[1]。其中fd[0]固定用于读管道,fd[1]用于写管道,这样通过fd[0]和fd[1]就构建起了一个单向的数据通道。
管道创建通过下面这个函数进行
#include <unistd.h>
int pipe(int pipefd[2]);
参数:
pipefd:存放无名管道读端和写端的数组首地址
pipefd[0] -- 读端
pipefd[1] -- 写端
返回值:
成功返回0,失败返回-1;
该函数创建的管道两端处于一个进程中,由于管道主要是用于两个不同进程之间的通信,通常是先创建管道再调用fork()函数创建一个子进程,该子进程会继承父进程的管道。这时子进程中就具有了总共4个文件描述符,由于无名管道是单工的工作方式,即要么只能读要么只能写。因此,我们在后续使用管道是应关闭其中一个,要么父进程写子进程读,要么子进程写父进程读。
读特性:当写端存在时:
管道有数据:返回读到的字节数
管道无数据:阻塞
当写端不存在时:
管道有数据:返回读到的字节数
管道无数据:返回0
写特性:当读端存在时:
管道有空间:返回写入的字节数
管道无空间:阻塞,直到有空间为止
当读端不存在时:
无论管道是否有空间,管道破裂
下面是创建有名管道的代码实现:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <strings.h> #include <unistd.h> #include <sys/types.h> #include <errno.h> int main() { pid_t pid; int pipe_fd[2]; int ret = pipe(pipe_fd); if(ret <0) { perror("pipe"); exit(-1); } if((pid=fork()) == 0) //创建子进程 { char buf[32]={0}; close(pipe_fd[0]);//关闭子进程的读端 while(1){ fgets(buf,32,stdin); buf[strlen(buf)-1]='\0'; int num = write(pipe_fd[1],buf,strlen(buf)); } } close(pipe_fd[1]);//关闭父进程的写端 char ch[32]={0}; while(1) { memset(ch,0,sizeof(ch)); read(pipe_fd[0],ch,32); printf("%s\n",ch); wait(NULL); } }
有名管道是对无名管道的一种改进,它具有一下特点。
(1)它可以使两个互不相关的进程实现通信。
(2)该管道可以通过路径名来指出,并且在系统文件中是可见的。在建立好管道后,两个进程可以把他当作普通文件来进行读写操作,使用非常方便。
(3)FIFO遵守着先进先出规则,对管道及FIFO的读写总是从开始处返回数据,写操作是在末尾操作,没有固定的读写端口。但不支持文件定位操作(lseek()函数)。
有名管道的创建可以通过使用mkfifo()函数来进程创建,在创建好后可以使用open()、read()、write()等函数进行操作。
mkfifo()函数:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
参数:
pathname:创建管道文件的文件名
mode:创建管道文件的权限
返回值:
成功返回0,失败返回-1
对于读进程:
如果当前FIFO内没有数据,读进程将一直阻塞到有数据写入或是FIFO写端都被关闭。
对于写进程:
只要FIFO有空间,数据就可以被写入。若空间不足,写进程将会被阻塞,直到数据都被阻塞为止。
下面实例为写入操作:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <strings.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> int main() { int ret = mkfifo("fifo",0665); //创建管道文件,文件名为fifo,权限为0665 if(ret <0) { perror("mkfifo"); exit(-1); } int fd = open("fifo",O_WRONLY); //以只写的方式打开文件 if(fd < 0) { perror("open"); exit(-1); } char buf[32]={0}; while(1){ fgets(buf,32,stdin); buf[strlen(buf)-1]='\0'; //向文件写入数据,fgets自带换行,去除换行 write(fd,buf,strlen(buf)); } close(fd); return 0; }
下面为读操作
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <strings.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> int main() { int fd = open("fifo",O_RDONLY); //以只读方式打开文件 if(fd < 0) { perror("open"); exit(-1); } char buf[32]={0}; while(1){ read(fd,buf,32); printf("%s\n",buf);//将读取到的内容打印在终端 memset(buf,0,32); //清空buf缓冲区数据 } close(fd); return 0; }
信号通信相对比前两个复杂一点,这里先介绍一下什么是信号。
信号是在软件层次上上对一个中断机制的一种模拟。在原理上,一个进程收到一个信号与处理去收到一个中断请求是一样的。一个进程不必通过任何操作来等待信号的到来,事实上进程也不知道信号什么时候到达。这里可以这样理解,当你在打游戏的时候你妈妈跟你说去买瓶酱油,然后你停下游戏去买酱油,这里你妈妈说的“去买瓶酱油”就是一个中断请求这里你,也不用一直等着你妈妈叫你去买酱油,你也不知道你妈妈什么时候会叫你去买酱油。信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了那些系统事件。它可以在任何时候发给某一进程,而无需知道该进程的状态。如果该进程处于不可执行态,则该信号就又内核保存下来,知道该进程恢复可执行态。
信号是进程间通信机制中唯一异步通信机制。这里后续会介绍到什么是异步。
信号事件的产生有硬件来源和软件来源,硬件来源比如当你按下键盘或者其他硬件故障都会产生信号,软件来源包括一些非法运算符及一些相关函数如kill()、raise()、alarm()等,有兴趣的可以去看一下这几个函数。
进程可以通过三种方式来响应一个信号。
(1)忽略信号。及不对信号做任何处理,其中,有两个信号不能忽略:SIGKILL以及SIGSTOP。
(2)捕捉信号。定义信号处理函数,当信号发生时,执行相应的处理函数。
(3)执行默认操作。以下是一个信号表
信号相关函数包括信号的发送和设置:
(1)发送信号函数:kill()、raise()
(2)设置信号函数:signal()、sigaction()
(3)其他函数:alarm()、pause()
信号发送:kill()和raise()
kill()函数与kill系统命令是一样的,可以发送信号给进程或进程组(实际上kill系统命令就是用kill()函数实现的)。
#include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig); 参数: pid:指定进程号 sig:指定信号 返回值: 成功返回0,失败返回-1; --------------------------------------------------------------------------- #include <signal.h> int raise(int sig); 参数: sig:指定信号 返回值: 成功返回0值,失败返回非0值
信号设置:signal()和sigaction()
这里只介绍signal()函数
使用signal()函数时,只需要制定信号类型和信号处理函数即可。它主要是用于前32种非实时信号的处理,不支持信号传递信息。这也是程序员常用的一种信号处理函数。
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
参数:
signum:指定信号
handler:信号处理函数
SIG_IGN:选择以忽略方式处理指定信号
SIG_DFL:选择以默认方式处理指定信号
顾名思义,消息队列就是一些消息的列表。用户可在消息队列中添加消息和读取消息等,可以看出消息队列具有一定的FIFO特性。
消息队列的实现包括创建或打开消息队列、添加信息、读取信息和控制消息队列这4个操作。其中每个操作都有对应的函数,创建或打开消息队列使用msgget()函数,其创建的消息队列数量受系统消息队列数量限制;添加消息使用函数msgsend(),该函数添加的消息位置位于打开的消息队列末尾;读取消息使用函数msgrcv(),能够从消息队列中读取(拿走)消息,可以取走制定消息;控制消息队列的函数为msgctl(),可使用该函数完成多想功能。
下面是对消息队列的具体使用示例:
发送端:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <strings.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> typedef struct message { long msg_type;//用于标识发送端的消息类型,这里设置为本身进程号 char msg_buf[1024];//发送消息的内容 }Message; int main() { int pid; key_t key; Message msg; if((key = ftok(".",'a'))==-1)//由路径和关键字产生标准key { perror("ftok"); exit(-1); } /*创建消息队列*/ if((pid=msgget(key,IPC_CREAT|0666))== -1) { perror("msgget"); exit(-1); } while(1) { printf("请输入消息:"); if((fgets(msg.msg_buf,1024,stdin))== NULL ) { puts("没有消息"); exit(-1); } msg.msg_type = getpid();//保存消息类型,使用进程号标识 if((msgsnd(pid,&msg,strlen(msg.msg_buf),0))<0) { perror("message"); exit(-1); } if(strncmp(msg.msg_buf,"quit",4)==0) { break; } } return 0; }
接收端:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <strings.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> typedef struct message { long msg_type;//用于标识发送端的消息类型,这里设置为本身进程号 char msg_buf[1024];//发送消息的内容 }Message; int main() { int pid; key_t key; Message msg; if((key = ftok(".",'a'))==-1)//由路径和关键字产生标准key { perror("ftok"); exit(-1); } /*创建消息队列*/ if((pid=msgget(key,IPC_CREAT|0666))== -1) { perror("msgget"); exit(-1); } while(1) { memset(msg.msg_buf,0,1024); if(msgrcv(pid,(void *)&msg,1024,0,0)<0) { perror("msgrcv"); exit(-1); } printf("读取到的消息为:%s",msg.msg_buf); } return 0; }
运行结果如下:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。