unix-c-IPC进程通信学习

进程通信的多种方式

管道

管道有两种,一种是无名管道,一种是有名管道
管道(无名管道):
无名管道值能作用在父子进程或者兄弟进程之间,进程之间使用描述符来标记
管道,子进程继承了父进程的描述符,但是父子进程之间的描述符并不是一样的
他们都指向管道,但他们的操作是独立的,且他们都指向的是同一个管道.
对于管道,父子进程都会有两个描述符,一个用来读,一个用来写。
当父进程需要发送信息给子进程的时候,关闭读的端口,而子进程则关闭写端口

有名管道:
有名管道时真实存在于硬盘上的,但是这个存储只是系统给管道分配了inode号,
但是,管道的真正内容实际上是存放在内存当中。当进程的操作结束,管道所指向
的内容就会消失。对于管道的操作类似于对文件的操作,可以使用writeread函数
相关函数
无名管道:

1
2
3
#include <unistd.h>

int pipe(int fildes[2]);

pipe函数用来创建一个无名管道,同时返回管道的读和写两种描述符
fildes[0] 用来读
fildes[1] 用来写

1
2
3
4
#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);

mkfifo创建一个管道文件,其他进程只需要知道该管道的据对路径就可以使用该管道
其他操作就类似文件,可以说,管道类似早期的各个进程用文件来通信,而管道就是
对简单的文件的优化,文件毕竟存放在硬盘上,比较慢,而管道只是inode存在硬盘,
它的真实内容依然是在内存中,所以快。

共享内存

共享内存是啥呢?
就是一片内存,多个进程都可以访问,一般来说,进程只能访问
到其进程范围内的内存,而且都是虚拟的地址,实际上,进程访问
的内存都是经过映射之后的,所以,对于一般的进程,两个进程是不能
访问到同一块内存的。
但是共享内存不同,它独立与进程之外,即使程序终止,内存里面的内容
依然是存在的,这样,不同的进程就可以访问同一片内存,实现通信

创建或者打开一片共享内存

1
int shmget(key_t key, size_t size, int shmflg);

函数的返回值是共享内存的标志符

得到标志符之后再使用

1
void *shmat(int shmid, const void *shaddr, int shmflg);

将共享内存的映射为本进程的地址,方便操作
经过之上的操作之后呢,就可以像普通内存操作了

信号量

信号量,表示资源的量,信号量本身也是一种资源。
信号量的使用是一种规则,这种规则来分配资源的利用,所以算是一个君子协定
只用各个进程都遵守这个协议,进程之间资源分配才会更加有效合理。
信号量,不单单是表示某一种资源的使用量,当多种资源可以实现同样的功能,
就可以把这几种资源都用这个信号量来标志。
当进程需要使用资源的时候,需要对信号量访问,如果信号量所代表的资源依然
存在,就可以申请资源,同时信号量减少一。如果可能资源已经用完,这样程序
会有两种情况一种是挂起,直到等到其它进程释放了资源,另一种就是直接出错
返回就好了。

1
int semget(key_t key, int nsems, int semflag);
1
int semctl(key_t key, int semnum, int cmd, ...);
1
int semop(int semid, struct sembuf *sops, unsigned nsops);

struct sembuf 结构体的结构包含里操作信息。
struct sembuf sops[2];
sops[0].sem_num = 0;
sops[0].sem_op = 0;
sops[0].sem_flg = 0;

sops[1].sem_num = 0;
sops[1].sem_op = 1;
sops[1].sem_flg = 0;

首先sops是指向struct sembuf数组的指针,nsops是数组的个数
semop是同时设置多个信号量
sem_num = 0是0号 信号量
sem_op 是指定信号量,如果为正,就是加上这么多的信号量。
如果是负的,如果当前信号量的值小于其绝对值,就会挂起等待,知道其他进程释放信号量
如果大于,则当前信号量减去sem_op的绝对值。
IPC_NOWAIT 信号量不满足的时候,不阻塞,直接返回。
IPC_UNDO 保证再程序退出后,将信号量初始化。

消息队列

消息队列也是用来多个进程相互通信,它属于一个异步的通信,发送方和接受方并
不需要同时进行,同时,无论是发送还是接受,消息队列的操作都是一个原子操作
发送时:进程需要看消息队列的空间是否够用,所以,不可能多个进程同时去发送
消息。
接受时:消息队列的接收也是原子操作,消息一旦被接受,消息就会消失。即使当
接受方只接受了消息的一部分,其余部分的消息也会消失。
消息队列可以有不同的类型的消息,这里的不同相当于编号,属于简单的加密。在接受
消息的时候,只有使用了相同的类型才能看到发送方发送的消息,否则,看到的就是空
的,没有消息。

1
2
3
4
5
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgget(key_t key, int msgflg);

msgget打开一个现有的队列,或者创建一个新的队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int msgctl(int msqid, int cmd, struct msgqid_ds *buf);
struct msqid_ds {
struct ipc_perm msg_perm; /* Ownership and permissions */
time_t msg_stime; /* Time of last msgsnd(2) */
time_t msg_rtime; /* Time of last msgrcv(2) */
time_t msg_ctime; /* Time of last change */
unsigned long __msg_cbytes; /* Current number of bytes in
queue (nonstandard) */

msgqnum_t msg_qnum; /* Current number of messages
in queue */

msglen_t msg_qbytes; /* Maximum number of bytes
allowed in queue */

pid_t msg_lspid; /* PID of last msgsnd(2) */
pid_t msg_lrpid; /* PID of last msgrcv(2) */
};

修改消息队列的属性信息,或者获得原属性信息
一般来说,修改属性应该先执行获得属性操作,然后再修改需要修改的部分。

1
2
3
4
5
6
int msgsnd(int msqid, const void *msgp, size_t msgz, int msgflg);

struct mymesg{
long mtype; /*positive message type*/
char mtext[512]; /*message data, of length nbytes */
};

msgsnd 用来发送消息到消息队列,msgp必须是类似struct mymesg的结构体,它的结构体的
第一个元素必须是long 来指定消息的type值。
msgflg可以指定为IPC_NOWAIT,如果消息队列满了就立刻返回.

1
ssize_t msgrcv(int msqid, void *msgp, size_t msgz, long msgtyp, int msgflg);

msgrcv 用来接受消息,msgp必须是指向mymesg结点的指针。
msgflg可以指定为IPC_NOWAIT 当消息队列为空的时候立刻返回.

Contents
  1. 1. 进程通信的多种方式
    1. 1.1. 管道
    2. 1.2. 共享内存
    3. 1.3. 信号量
    4. 1.4. 消息队列
,