一、进程间通信概述
进程通信有如下一些目的:
a、数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几m字节之间
b、共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到。
c、通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
d、资源共享:多个进程之间共享同样的资源。为了作到这一点,需要内核提供锁和同步机制。
e、进程控制:有些进程希望完全控制另一个进程的执行(如debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
linux 进程间通信(ipc)以下以几部分发展而来:
早期unix进程间通信、基于system v进程间通信、基于socket进程间通信和posix进程间通信。
unix进程间通信方式包括:管道、fifo、信号。
system v进程间通信方式包括:system v消息队列、system v信号灯、system v共享内存、
posix进程间通信包括:posix消息队列、posix信号灯、posix共享内存。
现在linux使用的进程间通信方式:
(1)管道(pipe)和有名管道(fifo)
(2)信号(signal)
(3)消息队列
(4)共享内存
(5)信号量
(6)套接字(socket)
二、管道通信
普通的linux shell都允许重定向,而重定向使用的就是管道。例如:
ps | grep vsftpd .管道是单向的、先进先出的、无结构的、固定大小的字节流,它把一个进程的标准输出和另一个进程的标准输入连接在一起。写进程在管道的尾端写入数据,读进程在管道的道端读出数据。数据读出后将从管道中移走,其它读进程都不能再读到这些数据。管道提供了简单的流控制机制。进程试图读空管道时,在有数据写入管道前,进程将一直阻塞。同样,管道已经满时,进程再试图写管道,在其它进程从管道中移走数据之前,写进程将一直阻塞。管道主要用于不同进程间通信。
管道创建与关闭
创建一个简单的管道,可以使用系统调用pipe()。它接受一个参数,也就是一个包括两个整数的数组。如果系统调用成功,此数组将包括管道使用的两个文件描述符。创建一个管道之后,一般情况下进程将产生一个新的进程。
系统调用:pipe();
原型:int pipe(int fd[2]);
返回值:如果系统调用成功,返回0。如果系统调用失败返回-1:
errno=emfile(没有空亲的文件描述符)
emfile(系统文件表已满)
efault(fd数组无效)
注意:fd[0]用于读取管道,fd[1]用于写入管道。
图见附件
管道的创建
#include
#include
#include
#include
int main()
{
int pipe_fd[2];
if(pipe(pipe_fd)<0){
printf(pipe create error\n);
return -1;
}
else
printf(pipe create success\n);
close(pipe_fd[0]);
close(pipe_fd[1]);
}
管道的读写
管道主要用于不同进程间通信。实际上,通常先创建一个管道,再通过fork函数创建一个子进程。图见附件。
子进程写入和父进程读的命名管道:图见附件
管道读写注意事项:
可以通过打开两个管道来创建一个双向的管道。但需要在子理程中正确地设置文件描述符。必须在系统调用fork()中调用pipe(),否则子进程将不会继承文件描述符。当使用半双工管道时,任何关联的进程都必须共享一个相关的祖先进程。因为管道存在于系统内核之中,所以任何不在创建管道的进程的祖先进程之中的进程都将无法寻址它。而在命名管道中却不是这样。管道实例见:pipe_rw.c
#include
#include
#include
#include
#include
int main()
{
int pipe_fd[2];
pid_t pid;
char buf_r[100];
char* p_wbuf;
int r_num;
memset(buf_r,0,sizeof(buf_r));数组中的数据清0;
if(pipe(pipe_fd)0){
printf(%d numbers read from be pipe is %s\n,r_num,buf_r);
}
close(pipe_fd[0]);
exit(0);
}else if(pid>0){
close(pipe_fd[0]);
if(write(pipe_fd[1],hello,5)!=-1)
printf(parent write success!\n);
if(write(pipe_fd[1], pipe,5)!=-1)
printf(parent wirte2 succes!\n);
close(pipe_fd[1]);
sleep(3);
waitpid(pid,null,0);
exit(0);
}
}
标准流管道
与linux中文件操作有文件流的标准i/o一样,管道的操作也支持基于文件流的模式。接口函数如下:
库函数:popen();
原型:file *open (char *command,char *type);
返回值:如果成功,返回一个新的文件流。如果无法创建进程或者管道,返回null。管道中数据流的方向是由第二个参数type控制的。此参数可以是r或者w,分别代表读或写。但不能同时为读和写。在linux 系统下,管道将会以参数type中第一个字符代表的方式打开。所以,如果你在参数type中写入rw,管道将会以读的方式打开。
使用popen()创建的管道必须使用pclose()关闭。其实,popen/pclose和标准文件输入/输出流中的fopen()/fclose()十分相似。
库函数:pclose();
原型:int pclose(file *stream);
返回值:返回系统调用wait4()的状态。
如果stream无效,或者系统调用wait4()失败,则返回-1。注意此库函数等待管道进程运行结束,然后关闭文件流。库函数pclose()在使用popen()创建的进程上执行wait4()函数,它将破坏管道和文件系统。
流管道的例子。
#include
#include
#include
#include
#define bufsize 1024
int main(){
file *fp;
char *cmd=ps -ef;
char buf[bufsize];
buf[bufsize]='\0';
if((fp=popen(cmd,r))==null)
perror(popen);
while((fgets(buf,bufsize,fp))!=null)
printf(%s,buf);
pclose(fp);
exit(0);
}
命名管道(fifo)
基本概念
命名管道和一般的管道基本相同,但也有一些显著的不同:
a、命名管道是在文件系统中作为一个特殊的设备文件而存在的。
b、不同祖先的进程之间可以通过管道共享数据。
c、当共享管道的进程执行完所有的i/o操作以后,命名管道将继续保存在文件系统中以便以后使用。
管道只能由相关进程使用,它们共同的祖先进程创建了管道。但是,通过fifo,不相关的进程也能交换数据。
命名管道创建与操作
命名管道创建
#include
#include
int mkfifo(const char *pathname,mode_t mode);
返回:若成功则为0,若出错返回-1
一旦已经用mkfifo创建了一个fifo,就可用open打开它。确实,一般的文件i/o函数(close,read,write,unlink等)都可用于fifo。当打开一个fifo时,非阻塞标(o_nonblock)产生下列影响:
(1)在一般情况中(没有说明o_nonblock),只读打开要阻塞到某个其他进程为写打开此fifo。类似,为写而打开一个fifo要阻塞到某个其他进程为读而打开它。
(2)如果指一了o_nonblock,则只读打开立即返回。但是,如果没有进程已经为读而打开一个fifo,那么只写打开将出错返回,其errno是enxio。类似于管道,若写一个尚无进程为读而打开的fifo,则产生信号sigpipe。若某个fifo的最后一个写进程关闭了该fifo,则将为该fifo的读进程产生一个文件结束标志。
fifo相关出错信息:
eacces(无存取权限)
eexist(指定文件不存在)
enametoolong(路径名太长)
enoent(包含的目录不存在)
enospc(文件系统余空间不足)
enotdir(文件路径无效)
erofs(指定的文件存在于只读文件系统中)
fifo_write.c
#include
#include
#include
#include
#include
#include
#include
#define fifo /tmp/myfifo
main(int argc,char** argv)
{
char buf_r[100];
int fd;
int nread;
if((mkfifo(fifo,o_creat|o_excl)0将信号发送给进程id为pid的进程。
(2)pid==0将信号发送给其进程组id等于发送进程的进程组id,而且发送进程有许可权向其发送信号的所有进程。
(3)pid<0将信号发送给其进程组id等于pid绝对值,而且发送进程有许可权向其发送信号的所有进程。如上所述一样,“所有进程”并不包括系统进程集中的进程。
(4)pid==-1 posix.1未定义种情况
kill.c
#include
#include
#include
#include
#include
int main()
{
pid_t pid;
int ret;
if((pid==fork())<0){
perro(fork);
exit(1);
}
if(pid==0){
raise(sigstop);
exit(0);
}else {
printf(pid=%d\n,pid);
if((waitpid(pid,null,wnohang))==0){
if((ret=kill(pid,sigkill))==0)
printf(kill %d\n,pid);
else{
perror(kill);
}
}
}
}
alarm和pause函数
使用alarm函数可以设置一个时间值(闹钟时间),在将来的某个时刻时间值会被超过。当所设置的时间被超过后,产生sigalrm信号。如果不忽略或不捕捉引信号,则其默认动作是终止该进程。
#include
unsigned int alarm(unsigned int secondss);
返回:0或以前设置的闹钟时间的余留秒数。
参数seconds的值是秒数,经过了指定的seconds秒后产生信号sigalrm。每个进程只能有一个闹钟时间。如果在调用alarm时,以前已为该进程设置过闹钟时间,而且它还没有超时,则该闹钟时间的余留值作为本次alarm函数调用的值返回。以前登记的闹钟时间则被新值代换。
如果有以前登记的尚未超过的闹钟时间,而且seconds值是0,则取消以前的闹钟时间,其余留值仍作为函数的返回值。
pause函数使用调用进程挂起直至捕捉到一个信号
#include
int pause(void);
返回:-1,errno设置为eintr
只有执行了一信号处理程序并从其返回时,pause才返回。
alarm.c
#include
#include
#include
int main()
{
int ret;
ret=alarm(5);
pause();
printf(i have been waken up.\n,ret);
}
信号的处理
当系统捕捉到某个信号时,可以忽略谁信号或是使用指定的处理函数来处理该信号,或者使用系统默认的方式。信号处理的主要方式有两种,一种是使用简单的signal函数,别一种是使用信号集函数组。
signal()
#include
void (*signal (int signo,void (*func)(int)))(int)
返回:成功则为以前的信号处理配置,若出错则为sig_err
func的值是:(a)常数sigign,或(b)常数sigdfl,或(c)当接到此信号后要调用的的函数的地址。如果指定sigign,则向内核表示忽略此信号(有两个信号sigkill和sigstop不能忽略)。如果指定sigdfl,则表示接到此信号后的动作是系统默认动作。当指定函数地址时,我们称此为捕捉此信号。我们称此函数为信号处理程序(signal handler)或信号捕捉函数(signal-catching funcgion).signal函数原型太复杂了,如果使用下面的typedef,则可以使其简化。
type void sign(int);
sign *signal(int,handler *);
实例见:mysignal.c
#include
#include
#include
void my_func(int sign_no)
{
if(sign_no==sigint)
printf(i have get sigint\n);
else if(sign_no==sigquit)
printf(i have get sigquit\n);
}
int main()
{
printf(waiting for signal sigint or sigquti\n);
signal(sigint,my_func);
signal(sigquit,my_func);
pasue();
exit(0);
}
信号集函数组
我们需要有一个能表示多个信号——信号集(signal set)的数据类型。将在sigprocmask()这样的函数中使用这种数据类型,以告诉内核不允许发生该信号集中的信号。信号集函数组包含水量几大模块:创建函数集、登记信号集、检测信号集。
图见附件。
创建函数集
#include
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;
signemptyset:初始化信号集合为空。
sigfillset:初始化信号集合为所有的信号集合。
sigaddset:将指定信号添加到现存集中。
sigdelset:从信号集中删除指定信号。
sigismember:查询指定信号是否在信号集中。
登记信号集
登记信号处理机主要用于决定进程如何处理信号。首先要判断出当前进程阻塞能不能传递给该信号的信号集。这首先使用sigprocmask函数判断检测或更改信号屏蔽字,然后使用sigaction函数改变进程接受到特定信号之后的行为。
一个进程的信号屏蔽字可以规定当前阻塞而不能递送给该进程的信号集。调用函数sigprocmask可以检测或更改(或两者)进程的信号屏蔽字。
#include
int sigprocmask(int how,const sigset_t* set,sigset_t* oset);
返回:若成功则为0,若出错则为-1
oset是非空指针,进程是当前信号屏蔽字通过oset返回。其次,若set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。
用sigprocmask更改当前信号屏蔽字的方法。how参数设定:
sig_block该进程新的信号屏蔽字是其当前信号屏蔽字和set指向信号集的并集。set包含了我们希望阻塞的附加信号。
sig_nublock该进程新的信号屏蔽字是其当前信号屏蔽字和set所指向信号集的交集。set包含了我们希望解除阻塞的信号。
sig_setmask该进程新的信号屏蔽是set指向的值。如果set是个空指针,则不改变该进程的信号屏蔽字,how的值也无意义。
sigaction函数的功能是检查或修改(或两者)与指定信号相关联的处理动作。此函数取代了unix早期版本使用的signal函数。
#include
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_def或sig_ign;
sa_mask是一个信号集,它可以指定在信号处理程序执行过程中哪些信号应当被阻塞。
sa_flags中包含许多标志位,是对信号进行处理的各种选项。具体如下:
sa_nodefer\sa_nomask:当捕捉到此信号时,在执行其信号捕捉函数时,系统不会自动阻塞此信号。
sa_nocldstop:进程忽略子进程产生的任何sigstop、sigtstp、sigttin和sigtou信号
sa_restart:可让重启的系统调用重新起作用。
sa_oneshot\sa_resethand:自定义信号只执行一次,在执行完毕后恢复信号的系统默认动作。
检测信号是信号处理的后续步骤,但不是必须的。sigpending函数运行进程检测“未决“信号(进程不清楚他的存在),并进一步决定对他们做何处理。
sigpending返回对于调用进程被阻塞不能递送和当前未决的信号集。
#include
int sigpending(sigset_t * set);
返回:若成功则为0,若出错则为-1
信号集实例见:sigaction.c
#include
#include
#include
#include
#include
void my_func(int signum){
printf(if you want to quit,please try sigquit\n);
}
int main()
{
sigset_t set,pendset;
struct sigaction action1,action2;
if(sigemptyse(&set)<0)
perror(sigemptyset);
if(sigaddset(&set,sigquit)<0)
perror(sigaddset);
if(sigaddset(&set,sigint)<0)
perror(sigaddset);
if(sigprocmask(sig_block,&set,null)<0)
perror(sigprcmask);
esle{
printf(blocked\n);
sleep(5);
}
if(sigprocmask(sig_unblock,&set,null)
perror(sigprocmask);
else
printf(unblock\n);
while(1){
if(sigismember(&set,sigint)){
sigemptyset(&action1.sa_mask);
action1.sa_handler=my_func;
sigaction(sigint,&action1,null);
}else if(sigismember(&set,sigquit)){
sigemptyset(&action2.sa_mask);
action2.sa_handler=sig_del;
sigaction(sigterm,&action2,null);
}
}
}
磁翻板液位计选型需要考虑的七大因素
三星和苹果创新不足,mate20有利于进一步冲击高端市场
高科技的应用让健身房变得更智能更便捷
云塔电子科技成功研制出全球首对5G与WiFi共存滤波器模组
华芳纺织稳步推进汽车动力电池项目
Linux进程间通信
PS-2205S-RS螺丝扭力测试仪的架构
通过PROFINET通信实现V90 PN基本定位控制
腾讯地图、腾讯位置服务发布2019十一出行大数据预测报告
干货满满:寒冬下如何从电池容量的角度谈动力电池
如何将化镓器件耐压提升到更高水平
2019 年Q2欧洲智能手机出货量为4510万台,三星市场份额大幅跃升
48V BRS硬件在环集成测试解决方法
Metawave刚发布并展示其毫米波模拟相位控制器,是自动化的重要一步
AVR单片机开发经验
全差分放大器LTC6605
热电偶和热电阻的区别及设计方案
帷幕燃烧测试仪上海徽涛自动化
如何做好防晒?避开紫外线让皮肤更完美!
深度学习基础知识(2)