• 管道和有名管道
  • 消息队列
  • 共享内存
  • 信号
  • 套接字

由于进程之间的并不会像线程那样共享地址空间和数据空间,所以进程之间就必须有自己特有的通信方式,这篇博客主要介绍自己了解到的几种进程之间的通信方式,内容讲的比较浅,目的相当于做学习笔记.
一:管道和有名管道
管道是一种半双工的通信方式(即数据只能单方面流动),TCP协议提供的就是一种全双工的通信方式,并且管道只能在具有亲缘关系的进程之间通信,例如父子进程,兄弟进程.有名管道与管道不同点在于

1:有名管道支持所有进程之间的通信.
2:有名管道在操作上和文件相似,较管道而言,操作更加方便.
3:管道存在与内存之中,但是有名管道存在与磁盘上,是文件.

下面介绍几个主要的函数

int pipefd[2];
int pipe(int pipefd[2]);
pipe()函数用来创建管道,返回的两个文件描述符,fd[0]:读文件描述符,fd[1]:写文件描述符,用来进行信息交流.一般文件进行操作的I/O函数也适用于管道.

注意点:
1:如果进程希望向管道中写数据,那么必须关闭fd[0]文件描述符,同时管道的另一端关闭fd[1]文件描述符
2:只有当管道的读端存在时,写才有意义,否则,向管道中写入数据的进程将收到来自内核的信号,write出错.

  1. void read_from_pipe(int fd) //定义读函数
  2. {
  3. char message[100];
  4. read(fd,message,100);
  5. printf("read from pipe:%s",message);
  6. }
  7. void write_to_pipe(int fd) //定义写函数
  8. {
  9. char *message = "hello world\n";
  10. write(fd,message,strlen(message)+1);
  11. }
  12. int main(int argc,char *argv[])
  13. {
  14. int fd[2];
  15. pid_t pid;
  16. int stat_val;
  17. if(pipe(fd) != 0) //创建管道
  18. {
  19. my_err("pipe",__LINE__);
  20. }
  21. pid = fork(); //创建进程
  22. switch(pid)
  23. {
  24. case -1:
  25. {
  26. my_err("fork error",__LINE__);
  27. }
  28. case 0:
  29. {
  30. close(fd[1]); //关闭子进程的写端
  31. read_from_pipe(fd[0]); //从读端去读
  32. exit(0);
  33. }
  34. default:
  35. {
  36. close(fd[0]); //关闭读端
  37. write_to_pipe(fd[1]); //写入
  38. wait(&stat_val);
  39. exit(0);
  40. }
  41. }
  42. return 0;
  43. }

我们经常会在fork一个子进程后让它去执行exec系列函数,但是此时子进程不会继承父进程的文件描述符,这使得我们无法使用管道,为了避免这个问题,我们可以先将文件描述符调用dup或者dup2复制到标准输入或者别的地方,之后我们再对另一个文件描述符操作,达到我们进程管道之间通信的目的.
下面我们继续讨论有名管道:

int mknod(const char *pathname, mode_t mode, dev_t dev);
pathname:管道名 mode :管道的权限,”S_IFIFO|0666”表示建立一个有名管道并且存取权限为0666,dev默认为0;
int mkfifo(const char *pathname, mode_t mode);
参数与mknod前两个参数一致
有名管道的使用方法与管道相似,但是由于它是一个设备,所以必须在调用之前利用open 打开.但是它的打开方式如果不是以可读可写的方式,那么将会阻塞,即如果以只读打开,则调用open的进程将会被阻塞,直到有写.

二:消息队列
消息队列是一个存放在内核中的链表,每个消息队列都由消息队列表示符标识,与管道不同,消息队列存放在内核之中,我们可以调用msgctl函数来删除一个消息队列,或者将内核重启也可以删除消息队列.
1:熟悉有关的几个数据结构

struct msgbuf : 消息缓冲机构
struct msqid_ds : 每个消息队列都会维护这样一个结构体,里面是此消息队列的具体信息.
struct ipc_perm : 它是msqid_ds的一个成员,里面主要包含消息队列的一些重要信息,比如key,用户ID,组ID等

2:消息队列的创建与读写及获取和设置相关属性.

key_t key;
int msqid;
struct msgbuf
key = ftok(const char *pathname, int proj_id); //用来获取一个键值,
msqid = msgget(key_t key, int msgflg); //获取文件描述符
初始化结构体msgbuf的信息
msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); //写一个消息到消息队列
msgrcv(int msqid, void *msgp, size_t msgsz, long msgtype,int msgflg); //从消息队列中读取一个消息.
msgctl(int msqid, int cmd, struct msqid_ds *buf); //用来获取或者设置消息队列
cmd:IPC_CREAT:用来获取消息队列对应的msqid_ds数据结构,将其保存到buf指向的数据空间
IPC_SET:设置属性,将要设置的属性存储在buf里
IPC_RMID:从内核删除qid的消息队列

下面附上一段代码:

  1. void my_err(char *err_string,int line)
  2. {
  3. fprintf(stderr,"line : %d",line);
  4. perror("err_string");
  5. exit(0);
  6. }
  7. void getmsgattr(int msgqid,struct msqid_ds msq_info)
  8. {
  9. if(msgctl(msgqid,IPC_STAT,&msq_info) == -1)
  10. my_err("msgctl",__LINE__);
  11. printf("\ninformation of message queue\n");
  12. printf("last message time is %s\n",ctime(&(msq_info.msg_stime)));
  13. printf("msg uid is %d\n",msq_info.msg_perm.uid);
  14. }
  15. int main(int argc,char *argv[])
  16. {
  17. struct mymsgself
  18. {
  19. long msgtype;
  20. char ctrlstring[1024];
  21. }msgbuffer;
  22. key_t key;
  23. int msgqid;
  24. int msglen;
  25. struct msqid_ds msq_attr;
  26. if((key = ftok(".",32)) == -1) //创建key值
  27. my_err("ftok",__LINE__);
  28. if((msgqid = msgget(key,IPC_CREAT|0660)) == -1) //返回msgqid
  29. my_err("msgget",__LINE__);
  30. getmsgattr(msgqid,msq_attr); //获取属性
  31. msgbuffer.msgtype = 1;
  32. strcpy(msgbuffer.ctrlstring,"hello i am yang");
  33. msglen = sizeof(struct mymsgself) - 4;
  34. if(msgsnd(msgqid,&msgbuffer,msglen,0) == -1) //放松一个消息到消息队列
  35. my_err("msgsnd",__LINE__);
  36. getmsgattr(msgqid,msq_attr); //再次查看属性
  37. if(msgrcv(msgqid,&msgbuffer,msglen,1,0) == -1) //读出来消息
  38. my_err("msgrcv",__LINE__);
  39. printf("read from msgqueue : %s\n",msgbuffer.ctrlstring);
  40. getmsgattr(msgqid,msq_attr);
  41. sleep(10); //sleep 10秒之后删除队列
  42. if(msgctl(msgqid,IPC_RMID,NULL) == -1) //删除消息队列
  43. my_err("msgctl",__LINE__);
  44. return 0;
  45. }

在程序执行过程我们可以调用ipcs命令查看内核的消息队列状态

$: watch -n 1 “ipcs”

三:共享内存
共享内存就是分配一块能被其他进程访问的内存,每个共享内存段在内核中维护着一个内部的结构struct shmid_ds(类似与消息队列和信号量).下面介绍共享内存的创建以及一些操作:

key_t key;
key = ftok(const char *pathname, int proj_id); //创建一个key值
shmid = shmget(key_t key, size_t size, int shmflg); //建立一块共享内存
void *shmat(int shmid, NULL, int shmflg); //将共享内存与进程建立连接,成功返回指向这块内存的指针,第二个参数默认设置为NULL.
int shmdt(const void *shmaddr); //将进程与共享内存断开连接,shmaddr是shmat的返回值.
int shmctl(int shmid, int cmd, struct shmid_ds *buf); //cmd:IPC_RMID:从系统中删除这块共享内存,IPC_SET:设置shmid_ds结构, IPC_STAT,读取这块共享内存的shmid_ds结构,将其存储到buf指向的地址中.

四:信号
具体的基础知识在这篇博客中写过,可以参考:Linux&C—–信号以及信号处理
下面在举一个例子,让信号之间传递数据.

  1. //send_data_signo.c
  2. int main(int argc,char *argv[])
  3. {
  4. union sigval value;
  5. int signum = SIGTERM; //默认结束进程的信号
  6. pid_t pid;
  7. int i;
  8. value.sival_int = 0; //初始化
  9. for(i = 1;i < argc;i++) //解析各个参数
  10. {
  11. if(!strcmp(argv[i],"-d"))
  12. {
  13. value.sival_int = atoi(argv[i+1]);
  14. continue;
  15. }
  16. if(!strcmp(argv[i],"-s"))
  17. {
  18. signum = atoi(argv[i+1]);
  19. continue;
  20. }
  21. if(!strcmp(argv[i],"-p"))
  22. {
  23. pid = atoi(argv[i+1]);
  24. continue;
  25. }
  26. }
  27. if(sigqueue(pid,signum,value) < 0) //利用sigqueue给pid发送信号,并且携带数据value
  28. {
  29. perror("sigqueue");
  30. exit(1);
  31. }
  32. return 0;
  33. }
  1. //recv_data_signo.c
  2. void handler_sigint(int signo,siginfo_t *siginfo,void *pvoid)
  3. {
  4. printf("recv SIGINT ,the data value is :%d\n",siginfo->si_int);
  5. }
  6. int main(int argc,char *argv[])
  7. {
  8. struct sigaction act;
  9. act.sa_sigaction = handler_sigint;
  10. act.sa_flags = SA_SIGINFO; //表明使用sigaction中第二个结构体中的sa_sigaction来设置参数
  11. //接收进程可以从siginfo_t 结构的si_value域取得信号发送时携带的数据
  12. sigaction(SIGINT,&act,NULL); //捕捉ctrl+c
  13. while(1);
  14. return 0;
  15. }

./recv_data_signo
ps -a
./send_data_signo -s 2 -d <要传输的数据> -p <进程ID>

从运行结果我们可以看出来-d 参数后面的数据确实被传递了过去.

五:套接字
有关套接字编程,具体见这一篇博客:三次握手”分析——以一个简单的“服务器”和“客户端”为例.其中服务器和客户端的代码每一步都有详细的注释.

Linux&C———进程间通信的更多相关文章

  1. 浅析Linux下进程间通信:共享内存

    浅析Linux下进程间通信:共享内存 共享内存允许两个或多个进程共享一给定的存储区.因为数据不需要在客户进程和服务器进程之间复制,所以它是最快的一种IPC.使用共享内存要注意的是,多个进程之间对一给定 ...

  2. 【转载】Linux的进程间通信-信号量

    原文:Linux的进程间通信-信号量 Linux的进程间通信-信号量 版权声明: 本文章内容在非商业使用前提下可无需授权任意转载.发布. 转载.发布请务必注明作者和其微博.微信公众号地址,以便读者询问 ...

  3. <转>Linux环境进程间通信(二): 信号(上)

    原文链接:http://www.ibm.com/developerworks/cn/linux/l-ipc/part2/index1.html 原文如下: 一.信号及信号来源 信号本质 信号是在软件层 ...

  4. Linux 环境进程间通信(六):

    linux下进程间通信的几种主要手段: 管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允 ...

  5. Linux环境进程间通信(五): 共享内存(下)

    linux下进程间通信的几种主要手段: 管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允 ...

  6. Linux环境进程间通信(五): 共享内存(上)

    linux下进程间通信的几种主要手段: 管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允 ...

  7. Linux环境进程间通信(四):信号灯

    linux下进程间通信的几种主要手段: 管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允 ...

  8. Linux环境进程间通信(三):消息队列

    linux下进程间通信的几种主要手段: 管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允 ...

  9. Linux环境进程间通信(二):信号(下)

    linux下进程间通信的几种主要手段: 管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允 ...

  10. Linux环境进程间通信(二): 信号(上)

    linux下进程间通信的几种主要手段: 管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允 ...

随机推荐

  1. Java基础系列(17)- 顺序结构

    顺序结构 JAVA的基本结构就是顺序结构,除非特别说明,否则就按照顺序一句一句执行 顺序结构是最简单的算法结构 语句与语句之间,框与框之间是按从上到下的顺序进行的,它是由若干个依次执行的处理步骤组成的 ...

  2. Oracle Haip无法启动问题学习

    一.目标:Oracle Haip 启动报错 需求:日常运维过程中,已经遇到两次由于HAIP引发的问题,特此进行记录. 本次问题是看着大佬-李海清操作,整完了记录一下,上一次HAIP折腾了4个小时. O ...

  3. WireShark基础用法

    特点 免费 开源 跨平台 抓包原理 内部原理 抓取网卡 抓包环境 抓取本地数据 抓取外部数据 利用hub 流量镜像span.rspan.erspan 界面介绍.首选项.抓包选项 界面介绍 菜单栏 帮助 ...

  4. nextcloud 中文乱码解决方案

    参考地址 :https://www.yht7.com/news/13909 我是使用的第二种方法, 修改/nextcloud/lib/public/AppFramework/Http/FileDisp ...

  5. Redis-Cluster分片扩容

    redis分片分片场景在业务量相对较小的时候,可以将所有数据都存到一台机器上,只使用redis单机模式,不存在分片问题.如果业务的数据量超过一台物理机器的内存大小时,则会面对扩展问题,需要多台机器去存 ...

  6. 关于selenium添加使用代理ip

    最近在爬某个网站,发现这个网站的反爬太厉害了,正常时候的访问有时候都会给你弹出来验证,验证你是不是蜘蛛,而且requests发的请求携带了请求头信息,cookie信息,代理ip,也能识别是爬虫,他应该 ...

  7. 《DotNet Web应用单文件部署系列》三、混淆dll文件

    众所周知,C#编译后的dll文件可被反编译,网上搜索"C# 反编译"会出现一大堆资料.为了提高反编译成本,我们必须对dll文件进行混淆处理. 目前,C#混淆工具很多,我推荐obfu ...

  8. WPF进阶技巧和实战03-控件(5-列表、树、网格03)

    数据视图 数据视图是在后台工作的,用于协调绑定数据的集合.使用数据视图可以添加导航逻辑.实现数据过滤.排序.分组. 当将集合或者DataTable绑定到ItemsControl控件时,会不加通告地在后 ...

  9. 踩坑系列《十一》完美解决阿里云vod视频点播无法播放音频和视频点播控制台里的媒资库里面的视频无法播放

    刚开始项目部署的时候,音频还是正常播放,后面直接报了 获取m3u8文件失败(manifestLoadError) 的错误,原因是 我的域名 xxx.com 这个域名没有解析到点播提供的CNAME上,所 ...

  10. node-gyp项目命名BUG

    当我们编写node原生模块的时候,免不了对node-gyp项目进行命名,在node-gyp进行build的时候,会跟binding.gyp配置文件中的target_name生成对应的原生模块.但是,如 ...