学习IPC进程间通信的总结(第八周)
10月24日晚上,回到宿舍准备写这周的总结时,看了下课表突然意识到这已经是第八周,不得不说时间过得真快,与前两年轻松的、无任务的课程相比,今年真是充实了太多!这也许是感觉时间过得快的原因,但是最近我也遇到了许多问题,当接触的知识多了以后,发现自己要学的东西太多了,仅仅上课所学远远不够,所以课后自学了一些课程,遇到的问题一是自己好高骛远,总想急于求成,这看一点那看一点,没有系统的全面的琢磨一本书,问题二:对于应用方面的知识学到手很快,但也会很快的忘记,到了应用时只记得一些概念了。对于这两个问题,我进行了一些思考,施行如下:在学习的时候把重要的知识点记录下来,我自己用的是有道云笔记,新学到的知识或者 不会的问题,通过查阅资料解决的 都把他们记录下来。效果不错,每周都可以回顾查看。最近开设了自己的博客,写博客是为了总结每日所学,反思自身。古人说“吾日三省吾身”还是很有道理的。在自己博客中进行知识总结,以及每周每月思考回顾总结,感觉收获良多。另外,自己也有了一些新的感悟,我们每每感叹许多技术大牛,博客名人如何厉害,其实我们自己就可以成为技术上的leader,其一就是知识及项目经验的不断积累,其二,我认为就是学习知识时对细节的把握,其三,可以持之以恒,不断的学习新技术,平时不断的回顾,“温故而知新”。说了很多,实践为真,接下来还要继续自己的求知之旅。好了下面进行本周的知识总结。
本周进行了两天的授课,和三天的实训;主要讲解了Linux进程与线程编程中的进程之间的通信这一方面知识。总结如下:
下面是我对这几种方法的理解:
1、无名管道(pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在父子进程间通信。管道的缓冲区是有限的(管道存在于内存中,在管道创建时,为缓冲区分配一个页面大小);管道所传送的是无格式字节流,要求管道的读出方和写入方必须事先约定好数据的格式。
进程A fork()出一个进程B,进程B是复制进程A的副本,得到了进程A的全部资源,因此,进程B也会有指向pd[1]的写指针,指向pd[0]的读指针。
所以管道这样设计:
进程A:
Close( fd [0]);
Write( fd[1],buffer,sizeof(buffer));
进程B:
Close(fd [1]);
Read( fd[0],buf,sizeof(buf));
2、命名管道(fifo):命名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。诸如不支持lseek()等文件定位操作。
(1)管道应用的一个重大限制是它没有名字,因此,只能用于具有亲缘关系的进程间通信,在有名管道(named pipe或FIFO)提出后,该限制得到了克服。FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。值得注意的是,FIFO严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。
(2)有名管道的创建
int mkfifo(const char * pathname, mode_t mode);
该函数的第一个参数是一个普通的路径名,也就是创建后FIFO的名字。第二个参数与打开普通文件的open()函数中的mode 参数相同。 如果mkfifo的第一个参数是一个已经存在的路径名时,会返回EEXIST错误,所以一般典型的调用代码首先会检查是否返回该错误,如果确实返回该错误,那么只要调用打开FIFO的函数就可以了。一般文件的I/O函数都可以用于FIFO,如close、read、write等等。
(3)有名管道的打开规则
有名管道比管道多了一个打开操作:open。
FIFO的打开规则:
如果当前打开操作是为读而打开FIFO时,若已经有相应进程为写而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为写而打开该FIFO(当前打开操作设置了阻塞标志);或者,成功返回(当前打开操作没有设置阻塞标志)。
如果当前打开操作是为写而打开FIFO时,如果已经有相应进程为读而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为读而打开该FIFO(当前打开操作设置了阻塞标志);或者,返回ENXIO错误(当前打开操作没有设置阻塞标志)。
小结:
管道常用于两个方面:(1)在shell中时常会用到管道(作为输入输入的重定向),在这种应用方式下,管道的创建对于用户来说是透明的;(2)用于具有亲缘关系的进程间通信,用户自己创建管道,并完成读写操作。
FIFO可以说是管道的推广,克服了管道无名字的限制,使得无亲缘关系的进程同样可以采用先进先出的通信机制进行通信。
管道和FIFO的数据是字节流,应用程序之间必须事先确定特定的传输"协议",采用传播具有特定意义的消息。要灵活应用管道及FIFO,理解它们的读写规则是关键。
3、消息队列(message queue ):消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。可以用非先进先出方式处理;消息队列是异步通信的;消息队列有大小限制,通常只用于小数据量的发送;只适用于单台主机的进程间通信;
消息队列:
提供从一个进程向另一个进程发送一个数据块的方法
int msgget(key_t key,int msgflg);
int msgctl(int magid,int cmd,struct msgid_ds *buf);
int msgsnd(int msgid,void *msg_ptr,size_t msg_sz,int msgflag);
int msgrcv(int msgid,void *msg_ptr,size_t msg_sz,long int msg_type,int msgflag);
4、共享内存( shared memory):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的IPC方式。效率高,进程可以直接读写内存,而不需要任何数据的拷贝;在共享内存段中都是以字符串的默认结束符为一条信息的结尾。每个进程在读写时都遵守这个规则,就不会破坏数据的完整性。
共享内存
允许两个不像关的进程访问同一个逻辑地址
1、int shmget(key_t key,size_t size,int shmflag);
2、void *shmat(int shm_id,const void *shm_addr,int shm_flag);
3、int shmctl(int shm_id,int cmd,struct shmid_ds *buf);
4、int shmdt(const void *shm_addr);
5.信号量(semophore ):信号量是一个计数器,可以用来控制多个进程对共享资源的访问。主要作为进程间以及同一进程内不同线程之间的同步手段。二元信号量:与Mutex类似。
信号量:
int semget(key_t key,int num_sems,int sem_flgs);
int semctl(int sem_id,int sem_num,int command...);
int semop(int sem_id,struct sembuf *sem_ops,size_t num_sem_ops);
每个函数的功能和参数的意义在手册中都能找到。
1、semge创建一个信号量或者获取一个已知信号量的键
key:不相关的进程可以通过他来访问同一个信号量
num_sems:需要的信号量数目。几乎总是1
sem_flags:一组标志,例如IPC_CREAT;
返回值是信号量标志符,给其他信号量函数使用
2、semctl:
sem_id:信号量标志符,semget的返回值
sem_ops:信号量编号,一般取0
command: 就不解释了,在手册中有详细的解释,我就偷偷懒吧。
3、semop
用于改变信号量的值
struct sembuf{
short sen_nums; //信号量编号,一般取0
short sem_op; //一般-1是P操作,1是V操作
short sem_flag; //通常设置为UNDO
}