首页 > 系统服务 > 详细

linux---进程间通信

时间:2020-03-14 13:04:27      阅读:61      评论:0      收藏:0      [点我收藏+]

 

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----------------------------------------------------

 

 

 

 

 

  

 

linux---进程间通信

原文:https://www.cnblogs.com/taoXiang/p/12453257.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!