1. 管道
fd[0]用来读,fd[1]用来写。fd[1]的输出是fd[0]的输入;成功返回0,失败-1
特点:
半双工:同一时间数据只能在一个方向上流动
只能在有共同祖先的两进程之间使用
是进程的资源,不在文件系统
父子进程间用匿名管道:
写一个读端已经关闭的管道,会产生信号 SIGPIPE,如果忽略该信号或者捕捉该信号并从处理程序返回,则write返回 -1,errno设为 EPIPE
写管道或FIFO时,常量 PIPE_BUF 规定了内核的管道缓冲区大小。确定这个量的值:
1.1
一个从父进程到子进程的管道:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <sys/types.h> 5 #define PIPE_BUF 40 6 int main(int argc,char* argv[]) 7 { 8 int fd[2]; 9 char buf[PIPE_BUF]; 10 pid_t pid; 11 int len; 12 if(pipe(fd)<0) { 13 perror("failed to pipe"); 14 exit(1); 15 } 16 if((pid=fork())<0) { 17 perror("failed to fork"); 18 exit(1); 19 } 20 else if(pid>0) { 21 close(fd[0]); //关闭父进程的读端 22 write(fd[1],"a message from parent process\n",30); 23 exit(0); 24 } 25 else { 26 close(fd[1]); //关闭子进程的写端 27 len = read(fd[0],buf,PIPE_BUF); 28 if(len<0) { 29 perror("process failed when read a pipe"); 30 exit(1); 31 } 32 else { 33 write(STDOUT_FILENO,buf,len); 34 } 35 exit(0); 36 } 37 return 0; 38 }
注意情况:
(1)读管道
①管道中有数据,read返回实际读到的字节数
②管道中无数据: 写端全部关闭,read返回0
写端未全部关闭,read阻塞等待
(2)写管道
①读端全部关闭,进程异常终止或捕捉SIGPIPE信号处理
②读端未全部关闭: 管道满,write阻塞
管道未满,写入数据返回实际写入字节数
1.2 已经封装好的库函数
popen先执行fork再调用exec执行command,返回一个标准I/O文件指针
type:r,文件指针连接到command的标准输出 w,标准输入
2. FIFO命名管道
是一种文件,可使不同进程间通信
命令:mknod、mkfifo也可创建管道
open时没有O_NONBLOCK标志,只读open会阻塞到某个其他进程为写而打开FIFO为止,只写open会阻塞到某个进程为读而打开这个FIFO为止
若指定了O_NONBLOCK标志,则只读open立即返回,若没有进程为读打开FIFO,则只写open会返回-1,设置errno为ENXIO
FIFO先入先出,通过网络完成进程通信,依赖底层网络接口(DNS服务、TCP/IP协议等),
------------------------------------------------------------------------IPC对象---------------------------------------------------------------
IPC对象:
IPC对象相关结构:<sys/ipc.h>
1 struct ipc_perm {
2 key_t key;
3 uid_t uid; //所有者有效UID
4 gid_t gid; //所有者有效GID
5 uid_t cuid; //创建者有效UID
6 gid_t cgid; //创建者有效GID
7 unsigned short mode; //权限
8 unsigned short seq; //sequence number
9 };
IPC对象的问题:
①IPC不适用通用的文件系统,不能用标准I/O操作;不使用文件描述符,不能使用多路I/O监控函数select、poll操作
②缺少资源回收机制,IPC对象使用过程中不保存引用计数,当进程创建了IPC对象后退出时,则只有 某一个进程读出消息或IPC所有者或超级用户删除了这个对象,IPC对象才会被释放或删除
查看IPC对象的命令:ipcs -a
IPC对象生成键值:
3. 消息队列 SYSTEM-V
在内核中的一个消息链表,每个消息队列由内部的消息队列标识符标识
IPC对象还有一个外部键值,key_t类型,键值到标识符的转换由内核转换
查看消息队列的命令:ipcs -q
相关的结构:<linux/msg.h>
1 struct msqid_ds 2 { 3 struct_ipc_perm msg_perm; //是一个ipc_perm的结构,保存了消息队列的存取权限,以及队列的用户ID、组ID等信息 4 struct_msg *msg_first; //指向队列中的第一条消息 5 struct_msg *msg_last; //指向队列中的最后一条消息 6 __kernel_t time_t msg_stime; //向消息队列发送最后一条信息的时间 7 __kernel_t time_t msg_rtime; //从消息队列取最后一条信息的时间 8 __kernel_t time_t msg_ctime; //最后一次变更消息队列的时间 9 unsigned long msg_lcbytes; 10 unsigned long msg_lqbytes; 11 unsigned short msg_cbytes; //消息队列中所有消息占的字节数 12 unsigned short msg_qnum; //消息队列中消息的数目 13 unsigned short msg_qbytes; //消息队列的最大字节数 14 __kernel_ipc_pid_t msg_lspid; //向消息队列发送最后一条消息的进程ID 15 __kernel_ipc_pid_t msg_lrpid; //从消息队列读取最后一条信息的进程ID 16 };
创建新的消息队列或访问一个已存在的消息队列需要先获得key值
(1)打开消息队列或创建一个新队列
返回非负队列ID
msgflg参数:IPC_CREAT,如果内核中不存在键值与key相等的消息队列,则新建消息队列;若存在,则返回该消息队列的描述符
IPC_EXCL,和IPC_CREAT一起使用,如果对应键值的消息队列已经存在则出错,返回-1
可或上权限,如 IPC_CREAT | 0666
(2)消息队列操作
cmd指定对msgid指定的队列要执行的命令:
(3)将数据写到消息队列
msgp指向msgbuf结构:
struct msgbuf { long mtype; /消息类型 char mbuf[];//不能以NULL结尾 };
(4)从消息队列取数据
msgtyp: 0,返回队列中第一个消息
大于0,返回类型为msgtyp的第一个消息
小于0,返回队列中消息类型值小于等于msgtyp绝对值的消息,取类型值最小的
(5)简单实例
发送消息进程:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <sys/types.h> 5 #include <sys/msg.h> 6 #include <sys/ipc.h> 7 #include <string.h> 8 struct msg { 9 long msg_types; 10 char msg_buf[511]; 11 }; 12 int main(int argc,char* argv[]) 13 { 14 int qid; 15 int pid; 16 int len; 17 struct msg pmsg; 18 pmsg.msg_types = getpid(); //消息类型为当前进程的PID 19 sprintf(pmsg.msg_buf,"this is massage from:%d\n\0",getpid()); 20 len = strlen(pmsg.msg_buf); 21 22 if((qid=msgget(IPC_PRIVATE,IPC_CREAT|0666))<0) { 23 //创建一个消息队列 24 perror("msgget error"); 25 exit(1); 26 } 27 if((msgsnd(qid,&pmsg,len,0))<0) { 28 //向消息队列中发送消息 29 perror("msgsnd error"); 30 exit(1); 31 } 32 printf("successfully send a message to queue:%d\n",qid); 33 return 0; 34 }
收取消息进程:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <sys/types.h> 5 #include <sys/msg.h> 6 #include <sys/ipc.h> 7 struct msg { 8 long msg_types; 9 char msg_buf[511]; 10 }; 11 int main(int argc,char* argv[]) 12 { 13 struct msg pmsg; 14 int qid; 15 int len; 16 qid = atoi(argv[1]); 17 len = msgrcv(qid,&pmsg,1024,0,0); //获得qid对应消息队列的第一条消息 18 if(len>0) { 19 pmsg.msg_buf[len] = ‘\0‘; 20 printf("reading queue id %d\n",qid); 21 printf("message type:%ld\n",pmsg.msg_types); 22 printf("message length:%d\n",len); 23 printf("message text:%s\n",pmsg.msg_buf); 24 } 25 else if(len==0) { 26 printf("no message from queue %d\n",qid); 27 } 28 else { 29 perror("msgrcv error"); 30 exit(1); 31 } 32 system("ipcs -q"); 33 return 0; 34 }
收取消息进程需要指导发送消息所在消息队列的qid
4. 信号量 SYSTEM-V
4.1
信号量是一个计数器,通过控制其他通信资源实现进程间通信。
当进程不再使用一个信号量控制的共享资源时,信号量值增1(增减为原子操作,信号量的作用是维护资源的互斥或多进程的同步访问),在信号量的创建以及初始化时,不能保证操作是原子
4.2
信号量的操作:P(sv)、V(sv)
P(sv):若sv值大于0,则减1;若值为0,则挂起进程。P操作就是试图获得信号量。
S(sv):如果有其他进程因等待sv被挂起,则让他恢复运行;如果没有,则给sv加1。V操作就是释放信号量。
4.3
信号量集相关的结构:
1 struct semid_ds { 2 struct ipc_perm sem_perm; //ipc都有的结构 3 unsigned long sem_nsems; //信号量集中信号量数 4 time_t sem_otime; //最后一次执行semop的时间 5 time_t sem_ctime; //最后一次修改时间 6 ... 7 };
1 struct { 2 unsigned short semval; //GETALL返回,同时可访问的进程数 3 pid_t sempid; 4 unsigned short semncnt; //GETNCNT 5 unsigned short semzcnt; 6 ... 7 };
(1)获取信号量集合
成功返回信号量集标识符,失败-1
参数:
nsems,指定有要创建的信号量集包含的信号量个数。如果只是打开信号量,把nsems设为0
semflg:①0:取信号量集标识符,若不存在则报错
②IPC_CREAT:semflg & IPC_CREAT为真,若内核中不存在key相关的信号量集,则新建;若存在,则返回此信号量集的标识符
③IPC_CREAT | IPC_EXCL:若不存在key相关信号量集则新建;若存在,则报错
semflg低9位是权限位,可或上权限来设置信号量集的存取权限(IPC_CREAT | 0666)
错误码:errno
EACCES:已有key关联的信号量集,但进程没有权限,且没有 CAP_IPC_OWNER capability
EEXIST:semflg为IPC_CREATE、IPC_EXCL,但已有一个key关联的信号量集
EINVAL:nsems小于0或大于一个信号量集中信号量个数限制 SEMMSL
已有key关联的信号量集存在,但nsems大于该信号量集中信号量数目
ENOENT:key关联的信号量集不存在,且semflg没有IPC_CREAT
ENOMEM:没有足够内存创建新的信号量集
ENOSPC:系统已达最大信号量集数SEMMNI、或已达系统最大信号量数SEMMNS
创建新信号量集时会初始化 semid_ds 结构:
struct ipc_perm sem_perm.cuid、sem_perm.uid设置为进程有效ID
struct ipc_perm sem_perm.cgid、sem_perm.gid设置为进程有效组ID
semflg的低9位设置到sem_perm.mode的低9位
nsems设置到sem_nsems
sem_otime设为0
sem_ctime设为当前时间
(2)信号量集控制、设置
作用:根据cmd操作创建成功的semid
参数:
semid:信号量集标识符
semnum:信号量集中第几个信号量/信号灯,SETALL时无意义
cmd参数:
IPC_STAT:取semid关联的semid_ds结构存到arg.buf里,忽略semnum,进程需有信号量集的读权限
IPC_SET:写arg.buf指定的semid_ds结构里的数据到此信号量集在内核里的semid_ds结构里去。更新sem_ctime、sem_perm.uid、sem_perm.gid、sem_perm.mode低9位,调用此函数的进程的有效UID需等于此信号量集的sem_perm.uid、sem_perm.cuid,或者是有超级权限的进程,忽略semnum参数
IPC_RMID:删除信号量集,唤醒因semop阻塞的进程
IPC_INFO:返回sem_info结构数据到arg.buf linux专有
SEM_STAT:返回semid_ds结构(类似IPC_STAT),linux专有,入参semid不是信号量集标识符,而是一个包含了所有信号量集信息的内核内部数组下标
GETALL:有读信号量集权限进程,返回 信号量集的所有信号量的semval到arg.array
GETNCNT:返回信号量集中第 semnum个信号量 semncnt的值()
GETZCNT:返回第semnum个信号量的semzcnt值
GETPID:第semnum个信号量sempid值(最后一个对这个信号量调用semop的进程PID)
GETVAL:返回第semnum个信号量的 semval 值
SETALL:通过arg.array设置该信号量集中所有信号量的semval值,更新sem_ctime,
SETVAL:
第四个参数:union semun arg
1 union semun { 2 int val; //搭配 SETVAL 3 struct semid_ds *buf; //搭配 IPC_STAT、IPC_SET 4 unsigned shrot *array; //搭配 GET_ALL、SETALL
5 struct seminfo *__buf; //搭配IPC_INFO(linux有)
6 };
错误码:errno
EACCES:无权限
EFAULT:arg.bug或arg.array所指地址不可访问
EIDRM:信号量已被删除
EINVAL:参数无效
EPERM:cmd是 IPC_SET、IPC_RMID,但进程UID不是信号量集的sem_perm.cuid、sem_perm.uid,且无 CAP_SYS_ADMIN权限
ERANGE:
(3)信号量集操作(P、V操作)
nsops:sops指向数组的元素个数,要操作的信号量个数 sops:结构体数组首地址
struct sembuf结构:
1 struct sembuf { 2 unsigned short sem_num; //信号量集中的信号量编号 3 short sem_op; //正值,对应进程释放的占用资源数,这个值会加到信号量的semval上 4 short sem_flg; //IPC_NOWAIT、SEM_UNDO(进程崩溃时根据UNDO记录恢复信号量的计数值) 5 };
(4)
semget创建一个有16个信号量的信号量集,semctl设置每个信号量的值为1,用semop试图占用资源:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <sys/types.h> 6 #include <sys/ipc.h> 7 #include <sys/sem.h> 8 9 #define NSEMS 16 10 int main(int argc,char* argv[]) 11 { 12 int semid; 13 unsigned short semun_array[NSEMS]; 14 int i = 0; 15 16 semid = semget(IPC_PRIVATE,NSEMS,0600); 17 if(semid<0) { 18 perror("semget error"); 19 exit(1); 20 } 21 for(i=0;i<NSEMS;++i) { 22 semun_array[i] = 1; 23 } 24 if(semctl(semid,0,SETALL,&semun_array) != 0) { 25 perror("semctl error"); 26 exit(1); 27 } 28 printf("semid:%d\n",semid); 29 struct sembuf sb; 30 sb.sem_num = 0; //操作第0个信号量 31 sb.sem_op = -1; //占用一个资源 32 sb.sem_flg = SEM_UNDO; 33 if((semop(semid,&sb,1))<0) { 34 perror("semop error"); 35 exit(1); 36 } 37 system("ipcs -s"); 38 return 0; 39 }
5. 共享内存 SYSTEM-V
在多个进程之间共享一定的存储空间,不需要在客户进程和服务器进程之间复制,速度快。
共享内存只提供数据传送,实现同步、互斥还需要其他工具:记录锁、互斥量等
共享内存相关结构体:<sys/shm.h>
1 struct shmid_ds { 2 struct ipc_perm shm_perm; 3 size_t shm_segsz; //段大小,单位字节 4 pid_t shm_lpid; //最后执行shmat、shmdt的进程PID 5 pid_t shm_cpid; //创建者PID 6 shmatt_t shm_nattch; //number of current attaches,连接到此内存的进程数 7 time_t shm_atime; //last-attach time,建立连接时间 8 time_t shm_dtime; //last-detach time,断开连接时间 9 time_t shm_ctime; //最后修改时间 10 11 };
(1)获取/创建共享内存
shmflg:
IPC_CREAT:
IPC_EXCL:
SHM_HUGETLB:(linux2.6)
SHM_HUGE_2MB,SHM_HUGE_1GB:(linux3.8)
SHM_NORESERVE:(linux2.6.15)
错误码:
(2)
cmd:
IPC_STAT:取shmid指向内存共享段的shmid_ds结构
IPC_SET:更新shm_perm.uid、shm_perm.gid、shm_perm.mode
IPC_RMID:标记删除该段(当最后一个进程detach后,shm_nattch变成0才真正删除)。被标记删除时,shm_perm.mode的相应位SHM_DEST设置
SHM_LOCK:锁定段,shm_perm.mode 的SHM_LOCKED被设置。超级管理员请求
SHM_UNLOCK:解锁,超级管理员请求
(3)将共享内存连接到本进程空间、断开连接
成功,返回实际地址,shmid_Ds.shm_nattch加1;失败-1
shmaddr:
0,内核找一个可用的位置
非0,shmflg没指定SHM_RND:段连接到shmaddr指定的地址上
非0,shmflg指定了SHM_RND:地址取整
shmflg:
SHM_RDONLY:只读连接此段;不指定,则读写连接
shmdt:进程与该共享内存分离,shmid_ds.nattch减1
6. UNIX域套接字
本机多个进程间通信,仅复制数据而不执行协议处理(无需添加、删除报头、计算校验和、确认报文等),提供流和数据报两种接口
-----------------------------------------POSIX IPC----------------------------------------------------
原文:https://www.cnblogs.com/taoXiang/p/12453257.html