进程间通信
参考:https://linuxgazette.net/104/ramankutty.html
参考:https://linuxgazette.net/105/ramankutty.html
进程间通信机制分为以下几类:
管道
- pipes
- fifos
系统IPC
- shared memory
- mapped memory
- message queues
- semaphores
Socket
- sockets
使用情景
匿名管道( pipe ):匿名管道是一种半双工的通信方式,通常是在父子进程间使用。
命名管道 (named pipe) :命名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
消息队列( message queue ) : 消息队列是消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
信号 ( sinal ) :信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
套接字( socket ) :套接字也是一种进程间通信机制。
管道
pipe
int pipe(int pipefd[2]);
pipe()函数创建了一个单向通道,该通道可以用来进程间通信。该函数的参数包含两个文件描述符(输入一个数组,调用该函数后数组内的值自动更新),pipefd[0]指的是管道读的一端,而pipefd[1]指的是管道写的一端。写入写一端通道的数据先缓存在内核中,直到它从读一端通道读出来。
pipe创建的单向通道在进程结束后就无效了。
fifos(first in, first out)
int mkfifo(const char *pathname, mode_t mode);
mkfifo()函数创建了一个fifo文件,这个会一直存在于文件系统中。一旦创建了fifo文件,任何一个进程都能像打开一个普通文件一样对它进行读写操作。然而需要注意的是,它必须同时存在读写两端才行,否则程序会阻塞在read函数处一直到另一个进程进行write操作为止。调用成功返回0。
优点是不需要同步机制,它是线程安全的。
缺点(同pipe)是只能是单向的。
Shared Memory
共享内存四步:
- Fetching an identifier for the shared memory area - shmget (shared memory get)
- Using the identifier to get the shared memory address - shmat (shared memory attach),
- Detaching the shared memory area after use - shmdt (shared memory detach) and
- Finally using the address to control accesses, permissions, receive information and destroy the shared memory area - shmctl (shared memory control).
int shmget(key_t key, size_t size, int shmflg);
shmget返回一个指向共享内存的标识符
key值未仔细研究,好像可以通过ftok函数来获得一个唯一key
size是共享内存的大小,shmflg是标志,可查资料得。
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmat函数会将共享内存空间与调用该函数的进程的地址空间连接到一起。
shmid是shmget的返回值
shmaddr参数为空时系统会自动帮我们选择一个地址来绑定共享内存空间
shmflg同shmget中的shmflg
返回值是共享内存块的地址
int shmdt(const void *shmaddr);
该函数将共享内存与当前与之绑定的进程解绑
调用成功返回0
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
对共享内存块进行相应的操作
cmd是相应的操作,IPC_RMID宏命令的意思是:在最后一个用到该共享内存的进程detach后,该共享内存块自动释放
当用户按下键之后才打开检测程序,这时候检测程序检测不到之前的按键,该怎么解决这个问题?
- /proc/sysvipc/shm文件中将会列出所有在用的共享内存。
- 共享内存是线程间通信最快的方式
- 共享内存一般是和信号量一起使用
- 与将东西存在文件中相比,共享内存要更快,因为从文件中读取数据包括:1.调用系统函数open()、read()、write()等;2.读取硬盘中的文件包括了硬件上的读写操作,这个比从内存中读取要慢得多
Memory Mapping
- 消息映射机制处理的是将文件系统中的文件映射到内存的一部分
- 当一个文件中包含用户想要的信息,用户可以写一个函数来读取文件并同时截取想要的内容。但若需要很多次这种操作的话,会很耗时。因为读取文件包括两个操作:1.调用系统函数open()、read()、write()(会经常在用户空间、内核空间切换从而耗时)等;2.硬盘的读写操作
内存映射是将文件与虚拟内存的一部分或用户空间联系在一起,任何的读写操作都转化为简单的内存访问。
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
- addr为空,内核将帮我们选择建立映射的地址
- offset是从文件最初位置算起偏移的量,length是一共可以共享的文件长度
- prot和flags看man手册吧,fd是文件描述符
两个文件分别是listing1.c和listing2.c,第一个用的是内存映射,第二个是用系统函数读取文件,各自循环了500000次,效果很明显,内存映射要快得多。
//内存映射
real 0m2.266s
user 0m0.452s
sys 0m1.094s
//系统函数调用
real 0m3.974s
user 0m0.765s
sys 0m2.949s
- 当进程终止时,内存映射区域会自动取消。关闭文件描述符不会自动取消内存映射区。要手动取消内存映射区域,使用munmap函数。
Message Queues
- 消息队列使得进程间可以异步传输消息
- 消息队列由内核创建和维护
int msgget(ket_t key, int msgflg);
1. 同shmget函数,得到消息队列标识符
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
1. 对消息队列进行对应操作
int msgsnd(int msqid, struct msgbuf *msgp, size_t msgsz, int msgflg);
1. 该函数是将消息发送到消息队列中,主要是将msgp中的消息的拷贝直接添加在消息队列后面
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
1. 该函数是从消息队列中获取消息,并将其从消息队列中移除
Semaphores
- P是使用信号量(信号量值减一) ,V是释放信号量(信号量值加一)
int semget(key_t key, int nsems, int semflg);
- 得到一个信号量集标识符或创建一个信号量集对象
- key:大于0的32位整数:视参数semflg来确定操作,通常要求此值来源于ftok返回的IPC键值
- nsems:创建信号量集中信号量的个数,该参数只在创建信号量集时有效
- semflg:IPC_CREAT:当semflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的信号量集,则新建一个信号量集;如果存在这样的信号量集,返回此信号量集的标识符;IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的信号量集,则新建一个消息队列;如果存在这样的信号量集则报错
- 返回值:成功则返回信号量集的标识符,失败则错误原因存于error中
int semop(int semid, struct sembuf *sops, unsigned nsops)- semid:信号量集标识符
- sops:指向进行操作的信号量集结构体数组的首地址,此结构的具体说明如下:
struct sembuf {
short semnum; /*信号量集合中的信号量编号,0代表第1个信号量*/
short val;/*若val>0进行V操作信号量值加val,表示进程释放控制的资源 */
/*若val<0进行P操作信号量值减val,若(semval-val)<0(semval为该信号量值),则调用进程阻塞,直到资源可用;若设置IPC_NOWAIT不会睡眠,进程直接返回EAGAIN错误*/
/*若val==0时阻塞等待信号量为0,调用进程进入睡眠状态,直到信号值为0;若设置IPC_NOWAIT,进程不会睡眠,直接返回EAGAIN错误*/
short flag; /*0 设置信号量的默认操作*/
/*IPC_NOWAIT设置信号量操作不等待*/
/*SEM_UNDO 选项会让内核记录一个与调用进程相关的UNDO记录,如果该进程崩溃,则根据这个进程的UNDO记录自动恢复相应信号量的计数值*/
};
- nsops:进行操作信号量的个数,即sops结构变量的个数,需大于或等于1。最常见设置此值等于1,只完成对一个信号量的操作
- 返回值:成功返回信号量集的标识符;出错返回-1,错误原因存于error中
int semctl(int semid, int semnum, int cmd, union semun arg)
- semid:信号量集标识符
- semnum:信号量集数组上的下标,表示某一个信号量
- cmd:查man手册吧
- arg是一个数据结构
作者很巧妙的在开头使用了一个signal函数,这样用户在按下ctrl+c时会自动执行删除信号量的操作
socket
ipc命令
ipcs会显示消息队列、共享内存以及信号量内容