Linux - 进程间通信

进程间通信

参考:https://linuxgazette.net/104/ramankutty.html
参考:https://linuxgazette.net/105/ramankutty.html

进程间通信机制分为以下几类:

管道

  1. pipes
  2. fifos

系统IPC

  1. shared memory
  2. mapped memory
  3. message queues
  4. semaphores

Socket

  1. 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);

    1. addr为空,内核将帮我们选择建立映射的地址
    2. offset是从文件最初位置算起偏移的量,length是一共可以共享的文件长度
    3. 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会显示消息队列、共享内存以及信号量内容

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值