• 进程通信——管道、消息队列、共享内存、信号量


    一、进程间通信(IPC)
      简单的进程间通信:
      命令行:父进程通过exec函数创建子进程时可以附加一些数据。
      环境变量:父进程通过exec函数创建子进程顺便传递一张环境变量表。
      信号:父子进程之间可以根据进程号相互发送信号,进程简单通信。
      文件:一个进程向文件中写入数据,另一个进程从文件中读取出来。
      命令行、环境变量只能单身传递,信号太过于简单,文件通信不能实时

      XSI通信方式:X/open 计算机制造商组织。共享内存、消息队列、信号量
      网络进程间通信方式:网络通信就是不同机器的进程间通信方式。

      传统的进程间通信方式:管道

    二、管道
    1、管道是一种古老的通信的方式(基本上不再使用)
    2、早期的管道是一种半双工,现在大多数是全双工。
    3、有名管道(这种管道是以文件方式存在的)。

    mkfifo ()会依参数pathname建立特殊的FIFO文件,该文件必须不存在,而参数mode为该文件的权限(mode%~umask),因此 umask值也会影响到FIFO文件的权限。Mkfifo()建立的FIFO文件其他进程都可以用读写一般文件的方式存取。

    管道通信的编程模式:
    进程A               进程B
    创建管道mkfifo  
    打开管道open            打开管道
    写/读数据read/write          读/写数据
    关闭管道close           关闭管道

    4、无名管道:由内核帮助创建,只返回管道的文件描述符,看不到管道文件,这种管道只能用在fork创建的父子进程之间。

    pipefd[0] 用来读数据
    pipefd[1] 用来写数据

    #include <stdio.h>
    #include <unistd.h>
    
    int main()
    {
        int pipefd[2] = {};
        if(pipe(pipefd) < 0 )
        {
            perror("pipe");
            return -1;
        }
        
        if(0 == fork())
        {
            int num = 10;
            write(pipefd[1],&num,4);
            close(pipefd[1]);
            return 0;
        }
        int num2;
        read(pipefd[0],&num2,4);
        printf("%d
    ",num2);
        close(pipefd[0]);
    }


    三、XSI IPC进程间通信
      XSI通信是靠内核的IPC对象进程通信。每一个IPC对象都有一个IPC标识(类似文件描述符),IPC标识它是一个非的整数。
      创建IPC对象必须要提供一个键值(key_t),键值是创建、获取IPC对象的依据。

    产生键值的方法:
    固定的字面值:1980014
    使用函数计算:键值=ftok(项目路径,项目id)

    使用宏让操作系统随机分配:IPC_PRIVTE,但其必须把获取到IPC对象标识符记录下来,告诉其它进程

    XSI可以创建的IPC对象有:共享内存,消息队列,信号量

    四、共享内存
    1、由内存维护一个共享的内存区域,其它进程把自己的虚拟地址映射到这块内存,然后多个进程之间就可以共享这块内存了。
    2、这种进程间通信的好处是不需要信息复制,是进程间通信最快的一种方式。
    3、但这种通信方式会面临同步的问题,需要与其它通信方式配合,最合适的就是信号。

    共享内存的编程模式:
      进程之间要约定一个键值
      进程A            进程B
      创建共享内存     获取共享内存
      加载共享内存     加载共享内存
      卸载共享内存     卸载共享内存
      销毁共享内存

    编程相关函数:

    功能:创建共享内存

    key:键值key,通常由ftok函数获取
    size:共享的大小,尽量是4096的倍数(一页=4096byte)
    shmflg:设置IPC_CREAT标志,则共享内存存在就打开,不存在则创建。设置IPC_CREAT | IPC_EXCL则不存在创建,存在则返回一个错误。
    返回值:IPC对象标识符(类似文件描述符)

    功能:加载共享内存(进程的虚拟地址与共享的内存映射)
    shmid:shmget的返回值
    shmaddr:进程提供的虚拟地址,如果为NULL,操作系统会自动选择一块地址映射。
    shmflg:
    SHM_RDONLY:限制内存的权限为只读
    SHM_REMAP:映射已经存的共享内存。
    SHM_RND:当shmaddr为空时自动分配
    SHMLBA:shmaddr的值不能为空,否则出错
    返回值:映射后的虚拟内存地址


    功能:卸载共享内存(进程的虚拟地址与共享的内存取消映射关系)

    addr参数是以前调用shmat时的返回值


    功能:控制/销毁共享内存
    cmd:
    IPC_STAT:获取共享内存的属性
    IPC_SET:设置共享内存的属性
    IPC_RMID:删除共享内存
    buf:记录共享内存属性的对象

    示例:shm_a,shm_b

    #include <stdio.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <signal.h>
    #include <unistd.h>
    
    char* buf = NULL;
    void sigint(int num)
    {
        printf("
    接收到数据:%s
    ",buf);
        printf(">");
        fflush(stdout);
    }
    
    int main()
    {
        signal(SIGINT,sigint);
        key_t key = ftok("shm",6);
        
        int pid = 0;
        printf("我是进程%d
    ",getpid());
        printf("与我通信的进程是:");
        scanf("%d",&pid);
        getchar();
    
        // 创建共享内存
        int shmid = shmget(key,4096,IPC_CREAT|0744);
        if(0 > shmid)
        {
            perror("shmget");
            return -1;
        }
        
        // 加载共享内存
        buf = shmat(shmid,NULL,SHM_RND);
    
        while(1)
        {
            printf(">");
            gets(buf);
            kill(pid,SIGINT);
        }
    
        if(shmdt(buf))
        {
            perror("shmdt");
        }
    }

    #include <stdio.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <signal.h>
    #include <unistd.h>
    
    char* buf = NULL;
    void sigint(int num)
    {
        printf("
    接收到数据:%s
    ",buf);
        printf(">");
        fflush(stdout);
    }
    
    int main()
    {
        signal(SIGINT,sigint);
        key_t key = ftok("shm",6);    
        
        int pid = 0;
        printf("我是进程%d
    ",getpid());
        printf("与我通信的进程是:");
        scanf("%d",&pid);
        getchar();
    
        // 创建共享内存
        int shmid = shmget(key,4096,0);
        if(0 > shmid)
        {
            perror("shmget");
            return -1;
        }
        
        // 加载共享内存
        buf = shmat(shmid,NULL,SHM_RND);
    
        while(1)
        {
            printf(">");
            gets(buf);
            kill(pid,SIGINT);
        }
    
        if(shmdt(buf))
        {
            perror("shmdt");
        }
    }

    五、消息队列

    1、消息队列是一个由系统内核负责存储和管理、并通过IPC对象标识符获取的数据链表。

    2、消息队列可以设定接收特定的消息类型,由此可以处理不同的消息类型的消息

    3、消息队列类似于队列,先进先出的排队机制

    功能:创建或获取消息队列
    msgflg:IPC_CREAT|IPC_EXEC,为0则表示获取消息队列

    功能:向消息队列发送消息
    msqid:msgget的返回值
    msgp:消息(消息类型+消息内容)的首地址,通常为结构体地址

    struct msgbuf {
    long mtype; /* 消息类型,必须 > 0 */
    char mtext[1]; /* 消息文本 */
    };

    msgsz:消息内存的长度(不包括消息类型)
    msgflg:

    0:当消息队列满时,msgsnd将会阻塞,直到消息能写进消息队列

    IPC_NOWAIT:当消息队列已满的时候,msgsnd函数不等待立即返回
    MSG_NOERROR:当发送消息的实际长比msgsz还要长的话,则按照msgsz长度截取再发送,且不通知发送进程。


    功能:从消息队列接收消息

    msqid:msgget的返回值
    msgp:存储消息的缓冲区
    msgsz:要接收的消息长度
    msgtyp:消息的的类型(它包含消息的前4个字节)
    msgflg:

    0: 阻塞式接收消息,没有该类型的消息msgrcv函数一直阻塞等待

    IPC_NOWAIT:如果没有返回条件的消息调用立即返回,此时错误码为ENOMSG

    IPC_EXCEPT:与msgtype配合使用返回队列中第一个类型不为msgtype的消息
    IPC_NOERROR:如果队列中满足条件的消息内容大于所请求的size字节,则把该消息截断,截断部分将被丢弃

    功能:控制/销毁消息队列
    cmd:
    IPC_STAT:获取消息队的属性
    IPC_SET:设置消息队列的属性
    IPC_RMID:删除消息队列

    buf:通常填0

    示例:

    msg_a.c

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    
    // 定义消息
    typedef struct Msg
    {
        long type;
        char buf[255];
    }Msg;
    
    int main()
    {
        // 获取键值
        key_t key = ftok(".",6);
    
        // 创建消息队列
        int msgid = msgget(key,0777|IPC_CREAT);
        if(0 > msgid)
        {
            perror("msgget");
            return -1;
        }
    
        while(1)
        {
            Msg msg = {};
            msg.type = 1;
            printf(">");
            gets(msg.buf);
            msgsnd(msgid,&msg,sizeof(msg.buf),0);
            if('q' == msg.buf[0]) break;
            msgrcv(msgid,&msg,sizeof(msg.buf),2,0);
            perror("msgrcv");
            if('q' == msg.buf[0]) break;
            printf("接收到:%s
    ",msg.buf);
        }
        
        msgctl(msgid,IPC_RMID,NULL);
    }

    msg_b.c

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    
    // 定义消息
    typedef struct Msg
    {
        long type;
        char buf[255];
    }Msg;
    
    int main()
    {
        key_t key = ftok(".",6);
        
        int msgid = msgget(key,0);
        if(0 > msgid)
        {
            perror("msgid");
            return -1;
        }
    
        while(1)
        {
            Msg msg = {};
            msgrcv(msgid,&msg,sizeof(msg.buf),1,0);
            printf("接收到:%s
    ",msg.buf);
            if('q' == msg.buf[0]) break;
            printf(">");
            gets(msg.buf);
            msg.type = 2;
            msgsnd(msgid,&msg,sizeof(msg.buf),0);
            if('q' == msg.buf[0]) break;
        }
    }

    六、信号量

    1、信号量是一种特殊的变量,访问具有原子性。

    2、我们使用信号量,来解决进程或线程间共享资源引发的同步问题。

    3、可以将信号量理解为两个进程共享的一个变量

    功能:创建一个新信号量或取得一个已有信号量

    key:键值,通常由ftok函数获取

    nsems:指定需要的信号量数目,它的值几乎总是1

    semflg:IPC_CREAT | IPC_EXCL,为0则表示获取一个已有的信号量

    功能:对信号量指定的变量进行操作,是选择发送(+1)还是等待(-1);

    sem_id:是由semget返回的信号量标识符; 
    sops:表示要对信号量进行什么操作;

    struct sembuf{ 
    short sem_num;//除非使用一组信号量,否则它为0 ,从0开始,表示要操作的信号量是第几个; 
    short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作, 对第一个变量选择的信号量进行操作;一个是+1,即V(发送信号)操作。 
    short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号, 并在进程没有释放该信号量而终止时,操作系统释放信号量 
    }; 
    nsops:是表示操作的信号量个数。

    功能:用来控制或销毁信号量

    semid:信号量的标志码(ID),也就是semget()函数的返回值; 
    semnum:操作信号在信号集中的编号,从0开始; 
    cmd:命令,表示要进行的操作。

    参数cmd中可以使用的命令如下: 
    IPC_STAT读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。 
    IPC_SET设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。 
    IPC_RMID将信号量集从内存中删除。 
    GETALL用于读取信号量集中的所有信号量的值。 
    GETNCNT返回正在等待资源的进程数目。 
    GETPID返回最后一个执行semop操作的进程的PID。 
    GETVAL返回信号量集中的一个单个的信号量的值。 
    GETZCNT返回这在等待完全空闲的资源的进程数目。 
    SETALL设置信号量集中的所有的信号量的值。 
    SETVAL设置信号量集中的一个单独的信号量的值。

    示例:图书馆借阅书籍小程序

    lib.c

    #include <stdio.h>
    #include <sys/ipc.h>
    #include <sys/types.h>
    #include <sys/sem.h>
    #include <signal.h>
    #include <unistd.h>
    
    int semid = 0;
    void sigint(int signum)
    {
        
            //删除信号量
        if(!semctl(semid,0,IPC_RMID))
        {
            printf("图书馆关闭了...
    ");
        }
    }
    
    int main()
    {
        signal(SIGINT,sigint);
        key_t key = ftok(".",6);
    
        semid = semget(key,1,0644|IPC_CREAT);
        if(0 > semid)
        {
            perror("semget");
            return -1;
        }
            //设置信号量初始值为10
        semctl(semid,0,SETVAL,10);
        printf("图书馆开放了...
    ");
    
        pause();    
        
    }        

    customer.c

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>
    
    int main()
    {
        key_t key = ftok(".",6);
    
        int semid = semget(key,0,0);
        if(semid < 0 )
        {
            perror("semget");
            return -1;
        }
    
        struct sembuf buf = {0,-2,0};
        if(!semop(semid,&buf,1))
        {
            printf("借到两本书...
    ");
            printf("还剩%d
    ",semctl(semid,0,GETVAL));
        }
    
        getchar();
    
        buf.sem_op = 2;
        if(!semop(semid,&buf,1))
        {
            printf("还回去两本书...
    ");
            printf("还剩%d
    ",semctl(semid,0,GETVAL));
        }
    }
  • 相关阅读:
    TSQL循环打印一年中所有的日期(WHILE循环)
    给Table加字段的SQL
    [正则表达式]前台JS得到控件ID (该控件被其它控件包住了)
    1.SQL Server中批量更新Object的Owner 2.附加数据库
    转:动态LINQ的几种方法
    转:查看LINQ生成SQL语句的几种方法
    TrimZero方法
    Oracle关联更新语法(TSQL中的update...from)
    Table之间的空隙或Table与父控件之间的空隙怎么去掉?
    自动完成带来的麻烦
  • 原文地址:https://www.cnblogs.com/xiehuan-blog/p/9397836.html
Copyright © 2020-2023  润新知