Linux环境:C编程进程通信
管道通信
标准流管道popen
标准流管道函数:
FILE* popen(const char* command, const char* open_mode);//打开管道流文件
int pclose(FILE* fp);//关闭管道流文件
popen
函数:通过command
参数拉起一个新的进程,并创建一个管道文件,如果open_mode
是"r",则调用程序就可以通过该管道文件读取被调用程序的标准输出;反之如果open_mode
是“w”,则被调用程序可以通过标准输入从管道文件读取数据,调用程序通过向管道文件写数据,实现通信。
pclose
函数:用 popen 启动的进程结束时,我们可以用 pclose 函数关闭与之关联的文
件流
无名管道pipe
函数原型:
int pipe(int *fd[2]);
函数机制:
创建一个无名管道,如果成功,fds[0]
存放可读的文件描述符,fds[1]
存放
可写文件描述符,并且函数返回 0,否则返回-1。
注意:
- 由于返回了一对文件描述符,所以想要用无名管道实现进程通信,两个进程必须拥有相同的进程空间,即存在继承关系。
- 生成的无名管道是特殊文件,可以用 read、write 等,只能在内存存在
样例:
int main(int args,char *argv[])
{
int fd[2];
int ret = pipe(fd);
RET_CHECK(ret,-1,"pipe")
char buf[128]={0};
if(fork()>0)
{
read(fd[0],buf,sizeof(buf));
printf("%s\n",buf);
wait(NULL);
close(fd[0]);
return 0;
}
else
{
write(fd[1],"hello",6);
close(fd[1]);
exit(0);
}
}
命名管道’fifo’
创建FIFO文件函数原型:
int mkfifo(const char *pathname, mode_t mode);
- 参数 pathname 为要创建的 FIFO 文件的全路径名;
- 参数 mode 为文件访问权限
- 如果创建成功,则返回 0,否则-1。
删除FIFO文件的函数原型为:
int unlink(const char *pathname)
如何使用管道见《linux环境:C编程文件操作》
共享内存通信
共享内存原理:
ystem V机制下的共享内存本质是一段特殊的内存区域,进程间需要共享的数据被放在该共享内存区域中,所有需要访问该共享区域的进程都要把该共享区域映射到本进程的地址空间中
共享内存允许一个或多个进程通过同时出现在它们的虚拟地址空间的内存进行通信,而这块虚拟内存的页面被每个共享进程的页表条目所引用,同时并不需要在所有进程的虚拟内存都有相同的地址。进程对象对于共享内存的访问通过 key(键)来控制,同时通过 key 进行访问权限的检查
进程对象对于共享内存的访问通过 key(键)来控制,同时通过 key 进行访问权限的检查
使用共享内存步骤:
① 开辟一块共享内存shmget
② 允许本进程使用共某块共享内存shmat
③ 写入/读取
删除共享内存步骤
①禁止本进程使用这块共享内存shmdt
②删除这块共享内存shmctl或者命令行下ipcrm
内存共享不提供同步机制,需要自行实现,一般通过信号量实现同步。
ftok
函数
函数 ftok
用于创建一个关键字,可以用该关键字关联一个共享内存段,相当于命名。
key_t ftok(const char *pathname, int proj_id);
- 参数
pathname
为一个全路径文件名,并且该文件必须可访问。 - 参数
proj_id
通常传入一非 0 字符 - 通过
pathname
和proj_id
组合可以创建唯一的key
如果调用成功,返回该关键字,否则返回-1。
shmget
函数
int shmget(key_t key, int size, int shmflg);
函数解析:
用于创建或打开一共享内存段
- 参数
key
内存标识,如果事先已经存在一个与指定关键字关联的共享内存段,则直接返回该内存段的标识,表示打开,如果不存在,则创建一个新的共享内存段。key
的值既可以用ftok
函数产生,也可以是IPC_PRIVATE
(用于创建一个只属于创建进程的共享内存,主要用于父子通信),表示总是创建新的共享内存段; - 参数
size
指定共享内存段的大小,以字节为单位; - 参数
shmflg
是掩码合成值,可以是访问权限码与(IPC_CREAT 或 IPC_EXCL)的合成。IPC_CREAT 表示如果不存在该内存段,则创建它。IPC_EXCL 表示如果该内存段存在,则函数返回失败结果(-1)。如果调用成功,返回内存段标识,否则返回-1
shmat
函数
void *shmat(int shmid, const void *shmaddr, int shmflg);
函数解析:
将共享内存段映射到进程空间的某一地址。
- 参数
shmid
是共享内存段的标识 通常应该是 shmget的成功返回值 - 参数
shmaddr
指定的是共享内存连接到当前进程中的地址位置。通常是 NULL,表示让系统来选择共享内存出现的地址。 - 参数
shmflg
是一组位标识,通常为 0 即可。 - 调用成功,返回映射后的进程空间的首地址,否则返回(char *)-1。
shmdt
函数
int shmdt(const void *shmaddr);
函数解析:
用于将共享内存段与进程空间分离
- 参数 shmaddr 通常为 shmat 的成功返回值。
- 函数成功返回 0,失败时返回-1.注意,将共享内存分离并没删除它,只是使得该共享内存对当前进程不在可用。
shmctl
函数
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
函数解析:
用于控制共享内存段:
- 参数
shmid
是共享内存段标识 通常应该是 shmget 的成功返回值 - 参数
cmd
是对共享内存段的操作方式,可选为IPC_STAT
,IPC_SET
,IPC_RMID
。通常为IPC_RMID
,表示删除共享内存段。 - 参数
buf
是表示共享内存段的信息结构体数据,通常为NULL。 - 有进程连接,执行返回 0,标记删除成功,最后一个进程解除连接后,共享内存真正被删除
信号量通信
一般通过信号量实现进程同步
semget
函数
int semget(key_t key,int nsems,int flag);
用于创建一个信号量集合,返回一个标识id,失败返回-1
- 参数
key
是唯一标识一个信号量的关键字,如果为IPC_PRIVATE
(值为 0,创建一个只有创建者进程才可以访问的信号量,通常用于父子进程之间;非 0 值的 key(可以通过 ftok 函数获得)表示创建一个可以被多个进程共享的信号量; - 参数
nsems
指定需要使用的信号量数目。如果是创建新集合,则必须指定nsems
。如果引用一个现存的集合,则将nsems
指定为 0 - 参数
flag
是权限标志。还可以与键值 IPC_CREAT按位或操作,打开现有或创建一个新的信号量。可以通过 IPC_CREAT 和 IPC_EXCL标志的联合使用确保自己将创建出一个新的独一无二的信号量来,如果该信号量已经存在,就会返回一个错误。
semop
函数
int semop(int semid,struct sembuf *sops,size_t num_sops);
用于改变信号量对象中各个信号量的状态。返回值:成功时,返回 0;失败时,返回-1.
- 参数
semid
是由semget
返回的信号量标识符。 - 参数
sops
是指向一个描述信号量的结构体数组指针。
该结构体包含:- 当前信号量的序号sem_num,
- 对信号量执行的操作sem_op,
- 信号量操作的属性标志sem_flag:如果为0,表示正常操作,如果为IPC_WAIT,使对信号量的操作是非阻塞的。即指定了该标志,调用线程在信号量的值不满足条件的情况下不会被阻塞,而是直接返回-1,并将errno设置为EAGAIN。如果为SEM_UNDO,那么将维护进程对信号量的调整值,以便进程结束时恢复信号量的状态。
struct sembuf { unsigned short int sem_num; /* 信号量的序号从0~num_sops-1 */ short int sem_op; /* 对信号量的操作,>0, 0, <0 */ short int sem_flg; /* 操作标识:0, IPC_WAIT, SEM_UNDO */ };
- 参数
num_sops
表示信号量数组的大小
semctl
函数
int semctl(int semid, int semnum, int cmd, …);
信号量控制函数:
在 semid
标识的信号量集上,或者该集合的第semnum
个信号量上执行 cmd
指定的控制命令
-
cmd类型如下:
- IPC_RMID(立即删除信号集,唤醒所有被阻塞的进程)
- GETVAL(根据 semun 返回信号量的值,从 0 开始,第一个信号量编号为 0)
- SETVAL(根据 semun 设定信号的值,从 0 开始,第一个信号量编号为 0)
- GETALL(获取所有信号量的值,第二个参数为 0,将所有信号的值存入semun.array中)
- SETALL(将所有 semun.array 的值设定到信号集中,第二个参数为 0)
-
根据 cmd 不同,该函数有三个或四个参数。当有四个参数时,第四个参数的类型是
union semun
。调用程序 必须按照下面方式定义这个联合体:union semun { int val; // SETVAL使用的值 struct semid_ds *buf; // IPC_STAT、IPC_SET 使用缓存区 unsigned short *array; // GETALL,、SETALL 使用的数组 struct seminfo *__buf; // IPC_INFO(Linux特有) 使用缓存区 };
其中
semid_ds
结构体定义如下:struct semid_ds { struct ipc_perm sem_perm; // 所有者和权限 time_t sem_otime; // 上次执行 semop 的时间 time_t sem_ctime; // 上次更新时间 unsigned short sem_nsems; // 在信号量集合里的索引 };
ipc_perm
定义如下:struct ipc_perm { key_t __key; // 提供给 semget()的键 uid_t uid; // 所有者有效 UID gid_t gid; // 所有者有效 GID uid_t cuid; // 创建者有效 UID gid_t cgid; // 创建者有效 GID unsigned short mode; // 权限 unsigned short __seq; // 序列号 };
信号量应用之生产者——消费者模型
#include <fun.h>
int main(int args,char *argv[])
{
//定义2个信号量,一个统计产品数量,一个统计仓库容量
int sid = semget(IPC_PRIVATE,2,0600|IPC_CREAT);
RET_CHECK(sid,-1,"semget");
//定义两组信号量pv操作结构体
struct sembuf p1,v1,p2,v2;
p1.sem_op = -1;
v1.sem_op = 1;
p1.sem_num = 0;
v1.sem_num = 0;
p1.sem_flg = SEM_UNDO;
v1.sem_flg = SEM_UNDO;
p2.sem_op = -1;
p2.sem_num = 1;
p2.sem_flg =SEM_UNDO;
v2.sem_num = 1;
v2.sem_op =1;
v2.sem_flg =SEM_UNDO;
short arr[2]={0,10};
//初始化信号量。0为初始库存,10为仓库初始容量。
int ret=semctl(sid,1,SETALL,arr);
RET_CHECK(ret,-1,"semctl");
printf("现有产品数量:%d,尚可存储产品数量:%d\n",semctl(sid,0,GETVAL),semctl(sid,1,GETVAL));
//父进程负责生产
if(fork()>0)
{
printf("I am a producer!\n");
while(1)
{
semop(sid,&p2,1);
printf("一个产品已生产!\n");
semop(sid,&v1,1);
if(semctl(sid,0,GETVAL)>1)
{
printf("there are now %d products.\n",semctl(sid,0,GETVAL));
}
else
{
printf("there is now %d product.\n",semctl(sid,0,GETVAL));
}
sleep(1);
}
wait(NULL);
return 0;
}
//子进程负责消费
else
{
printf("I am a consumer!\n");
while(1)
{
semop(sid,&p1,1);
printf("一个产品已消费\n");
semop(sid,&v2,1);
sleep(2);
}
}
return 0;
}
消息队列
消息队列与 FIFO 很相似,都是一个队列结构,都可以有多个进程往队列里面写信息,
多个进程从队列中读取信息。但 FIFO 需要读、写的两端事先都打开,才能够开始信息传递工作。而消息队列可以事先往队列中写信息,需要时再打开读取信息。但是,消息队列先打开读,仍然会阻塞,因为此时没有消息可读。
msgget
函数
函数原型:
int msgget(key_t key, int msgflg);
函数原理:
- 函数
msgget
创建和访问一个消息队列 - 参数
key
是唯一标识一个消息队列的关键字,如果为IPC_PRIVATE
(值为 0),用创建一个只有创建者进程才可以访问的消息队列,可以用于父子间通信;非 0 值的 key(可以通过 ftok 函数获得)表示创建一个可以被多个进程共享的消息队列 - 参数
msgflg
指明队列的访问权限和创建标志,创建标志的可选值为IPC_CREAT
和IPC_EXCL
如果单独指定IPC_CREAT
,msgget
要么返回新创建的消息队列 id,要么返回具有相同 key 值的消息队列 id;如果IPC_EXCL
和IPC_CREAT
同时指明,则要么创建新的消息队列,要么当队列存在时,调用失败并返回-1。
msgsnd
函数和msgrcv
函数
函数原型:
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);//消息发送函数
- 该函数向一个队列中发送消息
- 参数
msqid
指明消息队列的id标识 - 参数
msgp
消息结构体指针struct msgbuf*
,结构体定义如下:struct msgbuf { long mtype; /* 消息类型,正整数,由用户指定*/ char mtext[1]; /* 消息内容,可以通过重写结构体来自定义 */ };
- 参数
msgsz
自定义消息结构体中的消息体大小 - 参数
msgflg
可以为 0(通常为 0)或IPC_NOWAIT
,如果设置IPC_NOWAIT
,则msgsnd
和msgrcv
都不会阻塞,此时如果队列满并调用msgsnd
或队列空时调用msgrcv
将返回错误;
函数原型:
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);//消息接收函数
- 参数
msqid
为消息队列标识id - 参数
msgp
为消息结构体struct msgbuf指针 - 参数
msgz
标识消息的长度 - 参数
msgtyp
为接收消息类型:msgtyp
=0,接收第一个消息msgtyp
>0,接收第一个类型为msgtyp的消息msgtyp
<0, 接收类型小于或等于 msgtyp 绝对值的第 1 个最低类型消息
- 参数
msgflg
可以为 0(通常为 0)或IPC_NOWAIT
,如果设置IPC_NOWAIT
,则msgsnd
和msgrcv
都不会阻塞,此时如果队列满并调用msgsnd
或队列空时调用msgrcv
将返回错误;
msgctl
函数
函数原型:
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
- 函数 msgctl 是消息队列的控制函数,常用来删除消息队列。
- 参数 msqid 是由 msgget 返回的消息队列标识符。
- 参数 cmd 通常为 IPC_RMID 表示删除消息队列。
- 参数 buf 通常为 NULL 即可