IPC之碎碎念
1. 亲缘进程从实践的角度来看是始于SHELL登录界面以及由该界面登录后派生的所有进程。
2. linux 文件系统可以分为内核层文件系统和应用层文件系统, 通常的文件系统概念是立足于内核的,内核支配和管理着文件系统,主要针对于superblock,group description,inode bitmap,inode,data bitmap, data进行有效管理,也维护文件系统在内存中的元数据,这些元数据是即将或者在某个时刻由内核刷入给后备存储器的,它涵盖了上述内核管理文件系统的信息。linux 同样存在应用层的文件系统, 如proc,sys,debugfs,tmpfs,usbfs等等,它们主要的应用是从内核读取内核信息和配置一些内核参数。
进程间的通信若使用文件系统作为依托,那么进程间的访问时刻要由内核管理来自进程间的请求,反复进行内核空间的切换,开销是可观的,但安全性确是完好的。
3. IPC对象从持续性角度分为两种: 进程持续性和内核持续性;进程持续性体现在应用层进程管理,不涉及到内核对该对象的管理,当进程消亡的时候,IPC对象也就消失了,举出的IPC类型有互斥锁,读写锁,信号量,无名管道,基于内存的信号灯。内核持续性体现在内核对这些IPC对象变量进行管理,进程的IPC操作需要深入到内核来进行对象通信,只有在内核自举或者显式地关闭它时,才会释放IPC对象,举出的IPC类型有消息队列,有名管道FIFO,二值信号灯,PV操作,共享内存区,消息队列。
IPC对象从标识角度分两种:IPC有名和IPC无名, 前者是指内核根据文件系统选定某个文件路径来进行标识访问的; 后者是指是指内核分配的某个键值作为标识符进行访问,该键值是存在于内存当中的(通常是在内核层)。
4. 在创建file description(fd)时候,需要的一些参数O_CREAT, O_EXCL,O_RDONLY,O_WRONLY,O_RDWR.注意都是以O_xxx开头的。
在访问权限(mode)检查的时候,需要的参数为S_IRUSER/S_IWUSER(用户),S_IRGRP/S_IWGRP(组),S_IROTH/S_IWOTH(其他)。注意都是以S_Ixxx,常用S_ISxxx函数来检查具体的值。
在创建IPC对象的时候,有类似于fd的参数,对应为IPC_PRIVATE(创建唯一的IPC对象),IPC_EXCL(排他性创建一个IPC对象),IPC_NOWAIT(没有可创建对象时无阻塞返回),IPC_SET(删除结构体ipc_perm--ipc权限体),IPC_STAT(获得ipc_perm结构体,对内部mode做访问检查), 还有一些其他的应用参数可以详细参考./linux/ipc.h头文件。
5.管道的几个关键概念与编程
管道是亲缘进程之间通信的方法。管道分为有名管道和无名管道,有名管道也叫FIFO,由mkfifo系统调用实现,它立足于文件节点fd描述符访问。无名管道是我们通常所述的管道,由pipe()系统调用实现,它依赖于固定整数参数,用于模仿描述符。因此有名管道是依托文件系统的随内核持续的,无名管道随进程持续的。无论是有名管道还是无名管道,在应用编程上,两者都是半双工的,如何让管道实现全双工,通常依赖于fork的复制。
管道编程:
1)无名管道, 通常的方法是建立两组pipe,fork后变成四组pipe, 子进程继承父进程管道通道,选择两组管道,在此关闭多余的整数句柄,形成一条父子间的一条半双工管道,这一条管道可以命其为父读子写,我们可以用同样的方法形成另一条半双工管道,将另一条管道命其为子读父写, 这样就可以全双工了。
2)有名管道, 通常的方法也是建立两对fd,用文件路径表示,由于mkfifo系统调用基于fd描述符,那么可以用O_xxxx参数指定哪个fd读哪个fd写,那么在fork之后,可以比无名管道更方便地建立两条半双工通道,从而形成一条全双工通信。
6. 管道使用规则
1) 若写入管道的数据小于管道容量的上限,写入是原子的。若写入的数据大于管道容量,原子特性是不保证的。
2) 若进程同时写入同一个管道并且写入数据均不会超过管道容量,某一个进程写入时都是原子一次性的写入,而不是两个进程交叠分部分地写入。
3) 若管道仅剩的空间不足于存放另一个进程写入的数据,那么可以写入的数据合并之前写入的数据一起作为write的返回值,并且对无法全写入的进程报告EAGAIN错误。
4) 若进程向一个没有打开或打开用于读的管道写入数据时候,内核发送SIGPIPE信号,一般对该信号的处理采用忽略SIG_IGN,内核返回EPIPE值给用户处理。
7. 管道概念补充:
1) 管道是面向字节流的I/O操作,它没有记录边界,也就是说对管道的数据操作不会加入头信息标识。
2) 管道的PIPE_BUF可以通过系统设置的,但很少这样做。
3) 管道在最后被关闭的时候,在管道上的数据将被丢弃。
8. 消息队列posix标准:
1) mq_atrr属性结构体记录和管理着消息队列标记,最大队列长度,每个消息长度,及其当前队列中消息的个数。(消息的集合即消息队列)。
2) posix编程包含的常用函数为mq_open(), mq_close(),mq_getattr(), mq_setattr, mq_send(),mq_receive(),mq_notify();
3) SIGEV_SIGNVAL表示当某个消息队列为空的时候获得某个信号通知, 注意在mq_notify处理中先注册(先调用notify),再接受mq_receive.
4)mq_notify机制实际上是一种异步信号通知机制,它可以在信号句柄函数中处理需要的应用;异步信号处理存在着安全函数问题,上述函数在信号句柄内部是不安全的,那么必须采用句柄标记全局变量的方式来处理notify的信号通知应用。
5)sigprocmask(),sigsupend(&zeromask),sigpending(&zeromask), sigwait(SIG_USER,signo).扩展解决mq_notify信号句柄处理不安全函数问题。思路:i)阻塞信号->阻塞进程打开信号sigsuspend)->等待就绪。ii)设置为非阻塞,errno = EAGAIN循环监测循环接收。iii)sigwait阻塞进程直到设置的信号到来。
9. system V 的消息队列编程与Posix类似,围绕msgid_ds数据结构进行对队列的操控,主要函数是msgget,msgctl,msgsend,msgrecv,msgclose,msgunlink, 一个systemV的队列主要是依据这些函数进行处理的。
10. 读写锁其实是一种共享-独占锁,表示可以多读但单一写,在编程过程中主要是向系统如何申请读锁,申请写锁,以及释放锁unlock.在这些锁申请之后,按照共享独占规则编排临界资源访问。读写锁常常针对文件的访问,是由内核来进行维护的。读写锁也有类似于线程互斥锁pthread_mutex方法一样的锁属性设置。
11. 线程互斥锁和读写锁均可以通过属性设置共享到不同的进程中使用。锁是保护临界资源的,条件变量是发送和等待临界资源数据是否具有可获得性(被取走和可填入)。
12. 记录锁是以fcntl文件控制来实现锁的,它主要针对文件来进行互斥操作,依赖于flock数据结构,并且他可以对文件内部区域段进行上锁,由此引出劝告性锁和强制性锁。劝告性锁主要立足于文件的不同区段可以被多个进程同时访问,但系统建议性地不要这样处理文件但还是允许的,因为他们之间可能存在潜在风险;强制性锁主要立足于read/write的时机,也就是使用强制锁,在每次读写的时候会检查是否有其他的锁干扰或者干扰其他的锁,从而搅混文件的访问。
13. posix 信号量是二值信号量也称为PV信号值,根本原理是检查一个数值是否为0,若是,则将当前进程挂起,等待另一个进程发送信号到来。若大于0,将该值减一,使用临界段。使用完临界段之后,发送一个信号量,将该数值加1。当中的0和1的变化,就是二值。信号量遵从上面的“测试并减一”规则。 posix信号量主要函数为sem_open,close,wait,post,getvalue。
posix信号量主要分为两种实现方式:一是有名信号量,即立足于文件系统里面某个路径文件,它是随内核的;另一个是基于共享内存的,它分两种方向,一是一个进程中的多个线程,由于线程是共用进程空间的,那么从这个共用的空间划出某个值给多个线程进行互斥,由于是在进程内,那么当然它在此可以视为随进程的了。二是多个进程采用共享内存方式,让多个进程使用同一共享的内存区,对它划出某个值给多个进程使用作为互斥,当然由于是分化出共享内存,这必然就是随内核的了。
14. posix信号量是二值或者是计数信号,SystemV信号量扩展了这种能力,即描述为一个或多个信号量的集合,简称为信号集。对systemV的信号操作正是由于维护和管理这个集合而增加了许多复杂性,从程序设计的角度来讲,主要关注以下几个数据结构: semid_ds(类似于消息队列msgid_ds),当中有perm权限设置,以及sem结构体还有创建时间与访问修改时间; sem结构体,当中有semval信号量值,这是外界可修改的用于标识信号量当前值的变量,还有指向信号集的指针sem_base用于指向具体的一个信号量; sembuf结构体,当中有semval,它类似于sem结构体的semval,有semnum标识使用信号集哪个信号量,以及semflag常设置为SEM_UNDO; semun联合体通常要自己定义,比如定义用于初始化信号量值的变量,定义semis_ds结构体指针来记录当前进程使用信号的各种信息等。那么如何处理上述结构体是这些函数要做的事情:semget()从内核维护的semid_ds中创建或者获取一个信号集;semctl()函数控制信号量集合的行为如初始化信号量值,主要针联合体semun;semop()函数用于修改信号量集的工作行为如预备何值为互斥起始(wait)何值为互斥完成(post),主要针对结构体sembuf.
15. 内存共享是随内核的,内核维护并管理着这个划分出来的内存空间,这个内存空间是在用户层的,进程间不需要显式地通过内核进行数据交换。内存共享编程主要出发点是建立共享空间对象,再对该对象描述符进行mmap映射。systemV和posix共享空间的编程有一套完整的可直接用的函数接口。
浙公网安备 33010602011771号