• Linux Day12 ---进程间通信


    一、管道

    1.1 有名管道

    有名管道可以在任意两个进程之间通信

    1.1.1 有名管道的创建:

    命令创建: mkfifo +管道名
    系统调用创建

    1.1.2 与普通文件区别

    打开管道文件,在内存分配一块空间,往管道文件里面写数据,实际是写入内存,读取也是,这样比较高效,所以管道文件的大小永远为0.

    1.1.3 使用有名管道

    管道文件只有两种打开方式:只读 O_RDONLY 只写 O_WRONLY

    一个进程往管道里面写入数据

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. int main()
    8. {
    9. int fdw=open("./FIFO",O_WRONLY);
    10. assert(fdw!=-1);
    11. printf("fdw=%d\n",fdw);
    12. while(1)
    13. {
    14. printf("input:\n");
    15. char buff[128]={0};
    16. fgets(buff,128,stdin);
    17. if(strncmp(buff,"end",3)==0)
    18. {
    19. break;
    20. }
    21. write(fdw,buff,strlen(buff));
    22. }
    23. close(fdw);
    24. }

    另外一个进程从管道里面读出数据

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. int main()
    8. {
    9. int fdw=open("./FIFO",O_RDONLY);
    10. assert(fdw!=-1);
    11. printf("fdw=%d\n",fdw);
    12. while(1)
    13. {
    14. char buf[128]={0};
    15. if(read(fdw,buf,127)==0)
    16. {
    17. break;
    18. }
    19. printf("read=%s\n",buf);
    20. }
    21. close(fdw);
    22. }

    结果

    注意

    需要两个进程同时进行,至少一个读一个写,保证读和写都得有。

    当写端关闭,读端自动返回0。

    如果读端关闭,系统会给写端发送信号--13,引起异常。

    加上这段代码,当读端结束后,写端会打印系统返回的信号。

    1.2 无名管道

    无名管道主要应用于父子进程间的通信

    1.2.1 创建无名管道

    int pipe(int fds[2]);
    pipe()成功返回 0,失败返回-1
    fds[0]是管道读端的描述符
    fds[1]是管道写端的描述符
    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main()
    7. {
    8. int fd[2];
    9. assert(pipe(fd)!=-1);
    10. pid_t pid =fork();
    11. assert(pid!=-1);
    12. if(pid ==0)
    13. {
    14. close(fd[1]);
    15. char buff[128]={0};
    16. read(fd[0],buff,127);
    17. printf("child read:%s\n",buff);
    18. close(fd[0]);
    19. }
    20. else
    21. {
    22. close(fd[0]);
    23. write(fd[1],"hello",5);
    24. close(fd[1]);
    25. }
    26. }
    结果

    半双工,单工,全双工

    单工:方向固定

    半双工:一次只允许一方到另一方传递

    全双工:允许同时进行双方传递

    1.3 管道实现

    二、信号量

    信号量是一个特殊的变量,一般取正数值。它的值代表允许访问的资源数目,获取资源时,需要对信号量的值进行原子减一,该操作被称为 P 操作。当信号量值为 0 时,代表没有 资源可用,P 操作会阻塞。释放资源时,需要对信号量的值进行原子加一,该操作被称为 V 操作。信号量主要用来同步进程。信号量的值如果只取 0,1,将其称为二值信号量。如果信 号量的值大于 1,则称之为计数信号量。
    当信号量取0和1:二值信号量
    当信号量取3和5:计数信号量
    临界资源:同一时刻,只允许被一个进程或线程访问的资源
    临界区:访问临界资源的代码段

    2.1 没有信号量控制

    A和B去打印,需求是A,B同时打印次数为偶数

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main()
    7. {
    8. for(int i=0;i<5;i++)
    9. {
    10. printf("A");
    11. fflush(stdout);
    12. int n=rand()%3;
    13. sleep(n);
    14. printf("A");
    15. fflush(stdout);
    16. n=rand()%3;
    17. sleep(n);
    18. }
    19. }
    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main()
    7. {
    8. for(int i=0;i<5;i++)
    9. {
    10. printf("B");
    11. fflush(stdout);
    12. int n=rand()%3;
    13. sleep(n);
    14. printf("B");
    15. fflush(stdout);
    16. n=rand()%3;
    17. sleep(n);
    18. }
    19. }

    结果

    在B未结束时A已经出现了,所以表示一个进程未结束另一个进程已经开始了。

    2.2 信号量的封装

    为解决2.1的问题,我们增加一个信号量,值为1,当执行A时p操作-1,值为0,这时表明没有资源可以使用,进程B无法运行,当A结束后v操作+1,这时进程B就可以有资源去使用,执行B资源然后同上,这样就解决了两个进程依次执行的

    2.2.1 介绍一些函数

    2.2.1.1 semget()
    int semget(key_t key, int nsems, int semflg);
    semget()创建或者获取已存在的信号量
    semget()成功返回信号量的 ID, 失败返回-1
    key:两个进程使用相同的 key 值,就可以使用同一个信号量
    nsems:内核维护的是一个信号量集,在新建信号量时,其指定信号量集中信号
    量的个数
    semflg 可选: IPC_CREAT IPC_EXCL
    2.2.1.2 semop()
    int semop( int semid, struct sembuf *sops, unsigned nsops);
    semop()对信号量进行改变,做 P 操作或者 V 操作
    semop()成功返回 0,失败返回-1
    struct sembuf
    {
          unsigned short sem_num; //指定信号量集中的信号量下标
          short sem_op; //其值为-1,代表 P 操作,其值为 1,代表 V 操作
          short sem_flg; //SEM_UNDO
    }

    2.2.1.3 semget()

    int semctl( int semid, int semnum, int cmd, ...);
    semctl()控制信号量
    semctl()成功返回 0,失败返回-1 
    cmd 选项: SETVAL IPC_RMID
     
      union semun
        {
          int val;
          struct semid_ds *buf;
          unsigned short *array;
          struct seminfo *_buf;
        };

    2.2 实现创建一个信号量

    1. #include "sem.h"
    2. static int semid = -1;
    3. void sem_init()
    4. {
    5. semid = semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);//全新创建信号量,如果存在就失败
    6. if ( semid == -1 )//失败,表示已存在
    7. {
    8. semid = semget((key_t)1234,1,0600);//获取已存在的信号量id
    9. if ( semid == -1)
    10. {
    11. printf("semget err\n");
    12. }
    13. }
    14. else//全新创建成功,那么要进行初始化
    15. {
    16. union semun a;
    17. a.val = 1;//信号量的初始值
    18. if ( semctl(semid,0,SETVAL,a) == -1)//设置初始值
    19. {
    20. printf("semctl err\n");
    21. }
    22. }
    23. }
    24. void sem_p()
    25. {
    26. struct sembuf buf;
    27. buf.sem_num = 0;
    28. buf.sem_op = -1;//p
    29. buf.sem_flg = SEM_UNDO;
    30. if ( semop(semid,&buf,1) == -1)
    31. {
    32. printf("semop p err\n");
    33. }
    34. }
    35. void sem_v()
    36. {
    37. struct sembuf buf;
    38. buf.sem_num = 0;
    39. buf.sem_op = 1;//v
    40. buf.sem_flg = SEM_UNDO;
    41. if ( semop(semid,&buf,1) == -1)
    42. {
    43. printf("semop v err\n");
    44. }
    45. }
    46. void sem_destroy()
    47. {
    48. if ( semctl(semid,0,IPC_RMID) == -1)
    49. {
    50. printf("semctl destroy err\n");
    51. }
    52. }

    给2.1中的a.c b.c 添加信号量就会输出下图

    三、共享内存

    两个进程在进行通讯时,共同使用同一块内存。

    3.1 介绍一些函数

    3.1.1 shemget()创建共享内存

    int shmget(key_t key, size_t size, int shmflg);
    shmget()用于创建或者获取共享内存
    shmget()成功返回共享内存的 ID, 失败返回-1
    key: 不同的进程使用相同的 key 值可以获取到同一个共享内存
    size: 创建共享内存时,指定要申请的共享内存空间大小
    shmflg: IPC_CREAT IPC_EXCL

    3.1.2 shmat() 用来创建映射

    void * shmat( int shmid, const void *shmaddr, int shmflg);
    shmat()将申请的共享内存的物理内存映射到当前进程的虚拟地址空间上
    shmat()成功返回返回共享内存的首地址,失败返回 NULL
    shmaddr:一般给 NULL,由系统自动选择映射的虚拟地址空间
    shmflg: 一般给 0, 可以给 SHM_RDONLY 为只读模式,其他的为读写

    3.1.3  shmdt()用来断开映射

    int shmdt( const void *shmaddr);
    shmdt()断开当前进程的 shmaddr 指向的共享内存映射
    shmdt()成功返回 0, 失败返回-1

    3.1.4 shmctl()用来控制共享内存

    int shmctl( int shmid, int cmd, struct shmid_ds *buf);
    shmctl()控制共享内存
    shmctl()成功返回 0,失败返回-1
    cmd: IPC_RMID 32. *

    3.2 实现共享内存

    这里的共享内存的实现方式个人感觉和无名管道很像,都是使用两个信号量来控制输入输出进程的执行,只不过无名管道的信号量是来源于fork()复制来的。

    这个是进程1的实现,主要就是往共享内存里面传输数据

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include "sem.h"
    7. int main()
    8. {
    9. int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);
    10. if ( shmid == -1 )
    11. {
    12. printf("shmget err\n");
    13. exit(1);
    14. }
    15. char* s = (char*)shmat(shmid,NULL,0);
    16. if ( s == (char*)-1)
    17. {
    18. printf("shmat err\n");
    19. exit(1);
    20. }
    21. sem_init();
    22. while( 1 )
    23. {
    24. printf("input\n");
    25. char buff[128] = {0};
    26. fgets(buff,128,stdin);
    27. sem_p(SEM1);
    28. strcpy(s,buff);
    29. sem_v(SEM2);
    30. if ( strncmp(buff,"end",3) == 0)
    31. {
    32. break;
    33. }
    34. }
    35. shmdt(s);
    36. }

    在进程1结束后我们只用断开进程1和共享内存之间的映射就行,不需要结束共享内存,因为后续还会有其他进程使用这个共享内存。

    这个是进程2的实现方式,主要就是从共享内存中读出数据

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include "sem.h"
    7. int main()
    8. {
    9. int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);
    10. if ( shmid == -1 )
    11. {
    12. printf("shmget err\n");
    13. exit(1);
    14. }
    15. char * s = (char*)shmat(shmid,NULL,0);
    16. if ( s == (char*)-1)
    17. {
    18. printf("shmat err\n");
    19. exit(1);
    20. }
    21. sem_init();
    22. while( 1 )
    23. {
    24. sem_p(SEM2);
    25. if ( strncmp(s,"end",3) == 0 )
    26. {
    27. break;
    28. }
    29. printf("read:%s\n",s);
    30. sem_v(SEM1);
    31. }
    32. shmdt(s);
    33. shmctl(shmid,IPC_RMID,NULL);
    34. sem_destroy();
    35. }

    这里如果直接去实现就会无限迅速读入读出,所以需要我们引入信号量加以管制,和2的代码很像,只需要大家看得懂我前面画的图,实现这个功能并不复杂。

    四、信息队列

    根据前面的数字类型进行分类。

    4.1 认识一些函数接口

    4.1.1 msgget() 获取消息队列

    int msgget(key_t key, int msqflg);
    msgget()创建或者获取一个消息队列
    msgget()成功返回消息队列 ID,失败返回-1
    msqflg: IPC_CREAT

     4.1.2 msgsnd()发送信息

    int msgsnd( int msqid, const void *msqp, size_t msqsz, int msqflg);
    msgsnd()发送一条消息,消息结构为:
    struct msgbuf
    {
        long mtype; // 消息类型, 必须大于 0  必须有
        char mtext[1]; // 消息数据
    };
     msgsnd()成功返回 0, 失败返回-1
     msqsz: 指定 mtext 中有效数据的长度
     msqflg:一般设置为 0 可以设置 IPC_NOWAIT

     4.1.3 msgrcv()接收消息

    ssize_t msgrcv( int msqid, void *msgp, size_t msqsz, long msqtyp, int msqflg); 
    msgrcv()接收一条消息 
    msgrcv()成功返回 mtext 中接收到的数据长度, 失败返回-1
    msqtyp: 指定接收的消息类型,类型可以为 0
    msqflg: 一般设置为 0 可以设置 IPC_NOWAIT

     4.1.4 msgctl()控制消息队列

    int msgctl( int msqid, int cmd, struct msqid_ds *buf);
    msgctl()控制消息队列
    msgctl()成功返回 0,失败返回-1
    cmd: IPC_RMID
     

     4.2 示例

    进程 a 发送一条消息,进程 b 读取消息。
    进程a
    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. struct message
    8. {
    9. long int type;//固定
    10. char buff[32];
    11. };
    12. int main()
    13. {
    14. int msgid=msgget((key_t)1234,IPC_CREAT|0600);
    15. if(msgid==-1)
    16. {
    17. printf("msgget err\n");
    18. exit(1);
    19. }
    20. struct message dt;
    21. dt.type=1;
    22. strcpy(dt.buff,"hello1");
    23. msgsnd(msgid,&dt,32,0);
    24. }

     进程b

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. struct message
    8. {
    9. long int type;//固定
    10. char buff[32];
    11. };
    12. int main()
    13. {
    14. int msgid=msgget((key_t)1234,IPC_CREAT|0600);
    15. if(msgid==-1)
    16. {
    17. printf("msgget err\n");
    18. exit(1);
    19. }
    20. struct message dt;
    21. msgrcv(msgid,&dt,32,1,0);//0代表不区分类型
    22. printf("read message:%s\n",dt.buff);
    23. }

  • 相关阅读:
    Oracle连接和使用
    浅谈对属性描述符__get__、__set__、__delete__的理解
    1.3 Linux目录操作
    汇编语言入门(一)
    定语从句全面介绍
    Gof23设计模式之命令模式
    QFile 类【官翻】
    PMI-ACP练习题(22)
    群晖7.2版本通过Container Manager安装xiaoya-alist
    C#winform门诊医生系统+sqlserver
  • 原文地址:https://blog.csdn.net/hello_world_juns/article/details/132650056