Unix_Linux操作系统-笔记Day8(进程间通信)


Day8 进程间通信

基本概念

进程间的通信(IPC)InterProcess Communication

两个或多个进程之间的交换数据的过程

当多个进程协同工作高效率完成任务时,因为每个进程都是一个独立的个体(资源单位),进程之间就需要通信

进程之间通信方式

  • 简单进程通信:命令行参数,环境变量表,信号,文件
  • 传统进程通信:管道
  • XSI进程间通信:共享内存,消息队列,信号量
  • 网络进程通信:socket

传统的进程间通信-管道

管道是UNIX系统最古老的进程间通信方式(基本不再使用),历史上的管道通常是半双工的(只允许单向的数据流动),现在的大部分系统都可以全双工,数据可以双向流动

  1. 有名管道(创建实体文件)
    • 命令:mkfifo

    • 函数:int mkfifo(const char *pathname, mode_t mode);

      • 创建管道文件
      • pathname 文件路径
      • mode 权限
      • 返回值 0成功 -1失败
      • 编程模型
      进程A 进程B
      创建管道 mkfifo
      打开管道 open 打开管道
      读写数据 read/write 读写数据
      关闭管道 close 关闭管道
      删除管道 unlink
a.c
#include 
#include 
#include 
#include 
#include 
#include 

int main(){

    //1.创建管道,一般在/temp目录下创建
    if(mkdifo("/tmp/fifo",0644)){
        perror("mkfifo");
        return -1;
    }
    //2.打开管道
    int fd = open("/tmp/fifo",O_WRONLY);//对方不打开管道就不会返回,程序停在这里
    if(0 > fd){
        perror("open");
        return -1;
    }
    //3.读写数据
    char buf[1024] = {};
    for(;;){
        printf(">");
        gets(buf);
        write(fd,buf,strlen(buf)+1);
        if(0 == strcmp("quit",buf)){
            printf("通信完成!\n");
            break;
        }
    }
    //4.关闭管道
    close(fd);
    //5.删除管道
    unlink("/tmp/fifo");

}

b.c
#include 
#include 
#include 
#include 
#include 
#

int main(){

    //1.打开管道
    int fd = open("/tmp/fifo",O_RDONLY);
    if(0 > fd){
        preror("open");
        return -1;
    }
    //2.读写数据
    char buf[1024] = {};
    for(;;){
        read(fd,buf,sizeof(buf));
        printf("read:%s\n",buf);
        if(0 == strcmp(buf,"quit")){
            printf("通信结束\n");
            break;
        }
    }
    //3.关闭管道
    close(fd);
}
  1. 无名管道(用于通过fork创建的父子进程之间的通信)

int pipe(int pipefd[2]);

  • 创建无名管道
  • pipefd 用来存储内核返回的文件描述符
    • pipefd[0] 用于读操作
    • pipefd[1] 用于写操作
#include 
#include 
#include 

int main(){
    int pipefd[2] = {};
    //1.打开无名管道
    if(pipe(pipefd)){
        perror("pipe");
        return -1;
    }
    //2.创建进程
    pid_t id = fork();
    //3.父进程写
    if(id){
        //关闭读
        close(pipefd[0]);
        char buf[1024] = {};
        printf("我是父进程%u,我要和子进程%u通信了\n",getpid(),id);
        for(;;){
            printf(">");
            gets(buf);
            write(pipefd[1],buf,strlen(buf)+1);
            if(0 == strcmp("quit",buf)){
                printf("父进程通信结束!");
                close(pipefd[1]);
                return 0;
            }
        }
    }else{//子进程读
        //关闭写
        close(pipefd[1]);
        char buf[1024] = {};
        printf("我是子进程%u,我要和父进程%u通信了\n",getpid(),getppid());
        for(;;){
            read(piprfd[0],buf,sizeof(buf));
            printf("read:%s\n",buf);
            if(0 == strcmp("quit",buf)){
                printf("子进程通信完成!\n");
                close(pipefd[0]);
                return 0;
            }
        }
    }
}

练习1 使用有名管道进程通信,管道创建者读,对方写

homework
homework

练习2 使用无名管道进行通信,父进程读,子进程写

homework

XSI进程间通信

X/open组织为UNIX系统设计的一套进程间通信机制,有共享内存,消息队列,信号量

  1. IPC标识

    • 内核会为每个进程间通信对象维护一个IPC对象(XSI对象)
    • 该对象通过一个非负整数来引用(类似于文件描述符)
    • 与文件描述符不同的是,每用一个IPC对象,标识符持续+1,达到最大时再从零开始
    • IPC标识需要程序员自己创建(类似于创建文件)
  2. IPC键值

    • 创建IPC标识的依据(类似创建文件的文件名),也是一个非负整数
    1. 自定义(不建议,可能会冲突)
    2. 自动生成(项目的路径和项目的编号)
      • 注意 项目路径一定要有效路径
      • key_t ftok(const char *pathname, int proj_id);
  3. IPC对象的创建

    • IPC_PRIVATE 创建IPC对象时永远创建成功
    • IPC_CRETA 对象存在则获取,不存在则创建
    • IPC_EXCL 如果对象已经创建,则创建失败
  4. IPC对象销毁/控制用到的宏

    • IPC_STAT 获取IPC对象的属性
    • IPC_SET 设置IPC对象的属性
    • IPC_RMID 删除IPC对象

共享内存

内核中开辟的一块由IPC对象管理的内存,进程A和进程B都可以用自己的虚拟地址与它进程映射,这样进程A与进程B就共享同一块内核中的内存

特点

  1. 不需要复制信息,是最快的一种进程间通信
  2. 需要考虑同步问题(必须借助其他的机制,如信号)

编程模型

进程A 进程B
生成IPC键值 ftok 生成IPC键值 ftok
创建IPC对象(共享内存) shmget 创建IPC对象(共享内存) shmget
映射共享内存 shmat 映射共享内存 shmat
使用共享内存 *ptr 使用共享内存 *ptr
取消映射 shmdt 取消映射 shmdt
删除共享内存 shmctl
  • int shmget(key_t key, size_t size, int shmflg);

    • 创建/获取共享内存
    • key IPC键,由ftok函数生成
    • size 共享内存大小,最好是4096的整数倍,获取共享内存时,此值无效
    • shmflg
      • 0 获取共享内存
      • IPC_CREAT 创建
      • IPC_EXCL 如果存在则创建失败
      • 返回值 成功返回共享内存标识(IPC标识),失败 -1
  • void *shmat(int shmid, const void *shmaddr, int shmflg);

    • 映射共享内存
    • shmid 共享内存标识符,shmget函数的返回值
    • shmaddr 进程提供的虚拟地址,与内核中的内存映射用,也可以是NULL(内核会自动选择一个地址映射)
    • shmflg
      • 0 自动分配
      • SHM_RDONLY 只读权限
      • SHM_RND 当shmaddr为空时shmaddr向下取整页
    • 返回值 映射成功后的虚拟地址
  • int shmdt(const void *shmaddr);

    • 取消虚拟地址与共享内存的映射
    • shmaddr倍映射过的虚拟地址
  • int shmctl(int shmid, int cmd, struct shmid_ds *buf);

    • 删除共享内存,获取/设置共享内存的属性
    • shmid 共享内存标识符shmget的返回值
    • cmd
      • IPC_STAT 获取IPC对象的属性
      • IPC_SET 设置IPC对象的属性
      • IPC_RMID 删除IPC对象
struct shmid_ds {
    struct ipc_perm shm_perm;    /* Ownership and permissions */
    size_t          shm_segsz;   /* Size of segment (bytes) */
    time_t          shm_atime;   /* Last attach time */
    time_t          shm_dtime;   /* Last detach time */
    time_t          shm_ctime;   /* Last change time */
    pid_t           shm_cpid;    /* PID of creator */
    pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
    shmatt_t        shm_nattch;  /* No. of current attaches 映射次数*/
    ...
};
struct ipc_perm {
    key_t          __key;    /* Key supplied to shmget(2) */
    uid_t          uid;      /* Effective UID of owner */
    gid_t          gid;      /* Effective GID of owner */
    uid_t          cuid;     /* Effective UID of creator */
    gid_t          cgid;     /* Effective GID of creator */
    unsigned short mode;     /* Permissions + SHM_DEST and
                                           SHM_LOCKED flags */
    unsigned short __seq;    /* Sequence number */
};
a.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
int main(){

    //创建IPC键
    key_t key = ftok(".",110);
    //创建共享内存
    int shmid = shmget(key,4096,IPC_CREAT);
    if(0 > shmid){
        perror("shmget");
        retunr -1;
    }
    //映射
    char* str = shmat(shmid,NULL,SHM_RND);
    if((void)*-1 == str){
        perror("shmat");
        return -1;
    }
    pid_t pid = 0;
    printf("请输入要通信的进程号:");
    scanf("%u",&pid);
    //使用
    for(;;){
        printf(">");
        gets(str);
        kill(pid,SIGINT);
        if(0 == strcmp("quit",str)){
            printf("通信结束\n");
            break;
        }
    }
    //取消映射
    if(0 > shmdt(str)){
        perror("shmdt");
        return -1;
    }
    //删除
    if( 0 > shmctl(shmid,IPC_RMID,NULL)){
        perror("shmctl");
        return -1;
    }
}

b.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
int main(){
    printf("我是进程%u\n",getpid());
    //创建IPC键
    key_t key = ftok(".",110);
    //获取共享内存
    int shmid = shmget(key,0,0);
    if(0 > shmid){
        perror("shmget");
        return -1;
    }
    //映射
    char* str = shmat(shmid,NULL,SHM_RND);
    if((void*)-1 == str){
        perror("shmat");
        return -1;
    }
    //使用
    void sigint(int sig){
        printf("read:%s\n",str);
        if(0 == strcmp("quit",str)){
            printf("通信结束\n");
            //取消映射
            if(0 > shmdt(str)){
                perror("shmdt");
                exit(0);
            }
        }
    }
    signal(SIGINT,sigint);
    pause();
}

消息队列

由内核管理的管道,可以按顺序发送消息包(消息类型+消息内容),可以全双工工作

  • int msgget(key_t key, int msgflg);

    • 创建/获取消息队列
    • key IPC键值,由ftok函数生成
    • msgflg
      • 0 获取消息队列
      • IPC_CREAT 创建队列
      • IPC_EXCL 如果存在则创建失败
    • 返回值 消息队列标识符
  • int msgsnd(int msqid, const void *msgp, size_t msgs_z, int msgflg);

    • 向消息队列发送消息
    • msqid 消息队列标识符msgget函数的返回值
    • msgp 结构指针
      struct msgbuf {
          long mtype;       /* message type, must be > 0 */
          char mtext[1];    /* message data */
      };
      
    • 消息的长度,不包括消息类型,sizeof(msgbuf)-4
    • msgflg
      • 0 阻塞,当消息队列满时等待
      • IPC_NOWAIT 不阻塞,不等待
    • 成功0 失败-1
  • ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

    • 从消息队列获取消息
    • msqid 消息队列标识符msgget函数的返回值
    • msgp 结构指针
    • msgsz 要接收消息的长度,可以长一些
    • msgtyp 要接收消息的类型
      • 0 接收任意类型的消息(第一个)
      • > 0 只接收msgtyp类型的消息
      • < 0 接收消息队列中小于等于msgtyp绝对值的消息,取最小的哪个
    • msgflg
      • 0 阻塞,消息队列中是否有对应类型的消息,没有则等待
      • 1 不阻塞,消息队列中没有对用类型的消息,则返回
      • 当消息类型正确,而消息的实际长度大于msgsz,则不接收消息且返回-1
      • MSG_NOERROR 把多余的消息截去,成功接收
      • IPC_NOWAIT 如果消息队列没有要接收的数据,则不等待直接返回
      • MSG_EXCEPT 接收消息队列中第一个不是msgtyp的消息,需要编译时加-D_GNU_SUORCE 参数
  • int msgctl(int msqid, int cmd, struct msqid_ds *buf));

    • 删除消息队列,设置或获取消息队列的属性

    • msqid 消息队列标识符msgget函数的返回值

    • cmd

      • IPC_STAT 获取消息队列的属性
      • IPC_SET 设置消息队列的属性
      • IPC_RMID 删除消息队列
    • 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) */
              };
    • 返回值 成功0 失败-1

a.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "struct.h"
int main(){
    //创建消息队列
    int msgid = msgget(ftok(".",119),IPC_CREAT|IPC_EXCL|0644);
    if(0 > msgid){
        perror(msgget);
        return -1;
    }
    Msg msg = {666};
    for(;;){
        printf(">");
        gets(msg.data);
        msgsnd(msgid,&msg,sizeof(Msg)-sizeof(msg.type),0)
        if(0 == strcmp("quit",msg.data)){
            printf("通信结束\n");
            break;
        } 

    }
    if(0 > msgctl(msgid,IPC_RMID,NULL)){
        perror("msgctl");
        return -1;
    }
    Msg msg = {};
    for(;;){
        msgrcv(msgid,&msg,sizeof(Msg),555,0);
        printf("read:%s\n",msg.data);
        if(0 == strcmp("quit",msg.data)){
            printf("通信结束\n");
            break;
        }
    }
}


b.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "struct.h"
int main(){
    //获取消息队列
    int msgid = msgget(ftok(".",119),0);
    if(0 > msgid){
        perror("msgget");
        return -1;
    }
    //


}




struct.h
typedef struct Msg{
    long type;
    char data[256];
}Msg;

  • msqid 消息队列标识符msgget函数的返回值

  • cmd

    • IPC_STAT 获取消息队列的属性
    • IPC_SET 设置消息队列的属性
    • IPC_RMID 删除消息队列
  • 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) */
            };
  • 返回值 成功0 失败-1

a.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "struct.h"
int main(){
    //创建消息队列
    int msgid = msgget(ftok(".",119),IPC_CREAT|IPC_EXCL|0644);
    if(0 > msgid){
        perror(msgget);
        return -1;
    }
    Msg msg = {666};
    for(;;){
        printf(">");
        gets(msg.data);
        msgsnd(msgid,&msg,sizeof(Msg)-sizeof(msg.type),0)
        if(0 == strcmp("quit",msg.data)){
            printf("通信结束\n");
            break;
        } 

    }
    if(0 > msgctl(msgid,IPC_RMID,NULL)){
        perror("msgctl");
        return -1;
    }
    Msg msg = {};
    for(;;){
        msgrcv(msgid,&msg,sizeof(Msg),555,0);
        printf("read:%s\n",msg.data);
        if(0 == strcmp("quit",msg.data)){
            printf("通信结束\n");
            break;
        }
    }
}


b.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "struct.h"
int main(){
    //获取消息队列
    int msgid = msgget(ftok(".",119),0);
    if(0 > msgid){
        perror("msgget");
        return -1;
    }
    //


}




struct.h
typedef struct Msg{
    long type;
    char data[256];
}Msg;

你可能感兴趣的:(笔记)