进程间通信

进程间通信目的
1.数据传输:一个进程需要将它的数据发送给另一个进程;
2.资源共享:多个进程之间共享同样的资源;
3.通知事件:一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件;
4.进程控制:有些进程希望完全控制另一个进程的执行(如Debuge进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够既是知道它的状态改变。
进程间通信发展
1.管道
2.System V进程间通信
3.POSIX进程间通信
进程间通信分类
管道:
匿名管道pipe
命名管道
System V IPC:
System V 消息队列
System V 共享内存
System V 信号量
POSIX IPC:
消息队列
共享内存
信号量
互斥量
条件变量
读写锁
管道
管道是Unix中最古老的进程间通信的形式;
把从一个进程连接到另一个进程的一个数据流称为一个“管道”。
这里写图片描述
匿名管道:
#incldue

 #include<stdio.h>                                                                                                                                   
 #include<stdlib.h>
 #include<string.h>
 #include<unistd.h>
 int main (void){
    int fds[2];
    char buf[100];
    int len;
    if(pipe(fds) == -1){
        perror("make pipe"),exit(1);
    }   
    //read from stdin
    while(fgets(buf,100,stdin)){
        len = strlen(buf);
        //write into pipe
        if(write(fds[1],buf,len) != len){
            perror("write to pipe");
            break;
        }   
        memset(buf,0x00,sizeof(buf));
        //read from pipe
              if((len = read(fds[0],buf,100)) == -1){
            perror("read from pipe");
            break;
        }
        //write from pipe
        if(write(1,buf,len) != len){
            perror("write to stdout");
            break;
        }
    }
 }

运行结果:
这里写图片描述
管道特点
1、只能用于具有共同祖先地今进程(具有亲缘关系的进程)之间进行通信;通常一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可以应用该管道;
2、管道提供流式服务;
3、进程退出,管道释放,所以管道的生命周期随进程;
4、内核会对管道操作进行同步与互斥;
5、管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立两个管道。
这里写图片描述
命名管道
管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信;
如果想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,这经常被称之为命名管道;
命名管道是一种特殊类型的文件。
创建一个命名管道:
$ mkfifo filename
命名管道也可以从程序里创建,相关函数有:
int mkfifo(const char *filename,mode_t mode);
创建命名管道:

int main (int argc,char argv[]){
        mkfifo("p2",0644);
        return 0;
}

消息队列
1、消息队列提供了一个从一个进程向另一个进程发送一块数据的方法;
2、每个数据块都被认为是一个类型,接受者进程接收的数据块可以有不同的类型值;
3、消息队列也有管道一样的不足,就是每个消息的最大长度是有上限的(MSGMAX),每个消息队列的总字节数是有上限的(MSGMNB),系统上消息队列的总数也有一个上限(MSGMNB)。
IPC对象数据结构/usr/include/linux/ipc.h
内核为每一个IPC对象维护一个数据结构

struct ipc_perm{
    key_t         __key;       /* Key suppplied to xxxget(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 */
    unsigned short __seq;        /*Sequence number */
} 

消息队列结构/usr/include/linux/ipc.h

struct msqid_ds {
    struct ipc_perm msg_perm;
    struct msg *msg_first;      /* first message on queue,unused  */
    struct msg *msg_last;       /* last message in queue,unused */
    __kernel_time_t msg_stime;  /* last msgsnd time */
    __kernel_time_t msg_rtime;  /* last msgrcv time */
    __kernel_time_t msg_ctime;  /* last change time */
    unsigned long  msg_lcbytes; /* Reuse junk fields for 32 bit */
    unsigned long  msg_lqbytes; /* ditto */
    unsigned short msg_cbytes;  /* current number of bytes on queue */
    unsigned short msg_qnum;    /* number of messages in queue */
    unsigned short msg_qbytes;  /* max number of bytes on queue */
    __kernel_ipc_pid_t msg_lspid;   /* pid of last msgsnd */
    __kernel_ipc_pid_t msg_lrpid;   /* last receive pid */
};

消息队列函数
msgget函数
功能:⽤来创建和访问⼀个消息队列
原型
int msgget(key_t key, int msgflg);
参数
key: 某个消息队列的名字
msgflg:由九个权限标志构成,它们的⽤法和创建⽂件时使⽤的mode模式标志是⼀样的
返回值:成功返回⼀个⾮负整数,即该消息队列的标识码;失败返回-1
msgctl函数
功能:消息队列的控制函数
原型
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数
msqid: 由msgget函数返回的消息队列标识码
cmd:是将要采取的动作,(有三个可取值)
返回值:成功返回0,失败返回-1
cmd:将要采取的动作(有三个可取值),分别如下:
这里写图片描述
msgsnd函数
功能:把⼀条消息添加到消息队列中
原型
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
参数
msgid: 由msgget函数返回的消息队列标识码
msgp:是⼀个指针,指针指向准备发送的消息,
msgsz:是msgp指向的消息⻓度,这个⻓度不含保存消息类型的那个long int⻓整型
msgflg:控制着当前消息队列满或到达系统上限时将要发⽣的事情
msgflg=IPC_NOWAIT表⽰队列满不等待,返回EAGAIN错误。
返回值:成功返回0;失败返回-1
说明:
1.消息结构在两⽅⾯受到制约:
⾸先,它必须⼩于系统规定的上限值;
其次,它必须以⼀个long int⻓整数开始,接收者函数将利⽤这个⻓整数确定消息的类型
2.消息结构参考形式如下:
struct msgbuf {
long mtype;
char mtext[1];
}
msgrcv函数
功能:是从⼀个消息队列接收消息
原型
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
参数
msgid: 由msgget函数返回的消息队列标识码
msgp:是⼀个指针,指针指向准备接收的消息,
msgsz:是msgp指向的消息⻓度,这个⻓度不含保存消息类型的那个long int⻓整型
msgtype:它可以实现接收优先级的简单形式
msgflg:控制着队列中没有相应类型的消息可供接收时将要发⽣的事
返回值:成功返回实际放到接收缓冲区⾥去的字符个数,失败返回-1
说明:
msgtype=0返回队列第⼀条信息
msgtype>0返回队列第⼀条类型等于msgtype的消息 
msgtype<0返回队列第⼀条类型⼩于等于msgtype绝对值的消息,并且是满⾜条件的消息类型最⼩的消息
msgflg=IPC_NOWAIT,队列没有可读消息不等待,返回ENOMSG错误。
msgflg=MSG_NOERROR,消息⼤⼩超过msgsz时被截断
msgtype>0且msgflg=MSG_EXCEPT,接收类型不等于msgtype的第⼀条消息。
代码模拟实现:
comm.h

 #pragma once
 #include <stdio.h>
 #include <sys/types.h>
 #include <sys/ipc.h>
 #include <sys/msg.h>
 #include <string.h>
 #define PATHNAME "."
 #define PROJ_ID 0x6666
 #define SERVER_TYPE 1
 #define CLIENT_TYPE 2
struct msgbuf{
    long mtype;
    char mtext[1024];
};
int createMsgQueue();
int getMsgQueue();
int destroyMsgQueue(int msgid);
int sendMsg(int msgid, int who, char *msg);
int recvMsg(int msgid, int recvType, char out[]);

comm.c

#include "comm.h"
//success > 0 failed == -1
static int commMsgQueue(int flags)
{
    key_t _key = ftok(PATHNAME, PROJ_ID);
    if(_key < 0){
        perror("ftok");
        return -1;
    }
    //int msgid = msgget(_key, IPC_CREAT|IPC_EXCL);
    int msgid = msgget(_key, flags);
    if(msgid < 0){
        perror("msgget");
    }
    return msgid;
    }
int createMsgQueue()
{
    return commMsgQueue(IPC_CREAT|IPC_EXCL|0666);
}
int getMsgQueue()
{
    return commMsgQueue(IPC_CREAT);
}
int destroyMsgQueue(int msgid)
{
    if(msgctl(msgid, IPC_RMID, NULL)<0){
        perror("msgctl");
        return -1;
    }
    return 0;
}
int sendMsg(int msgid, int who, char *msg)
{
    struct msgbuf buf;
    buf.mtype = who;
    strcpy(buf.mtext, msg);
    if(msgsnd(msgid, (void*)&buf, sizeof(buf.mtext), 0) < 0){
        perror("msgsnd");
        return -1;
    }
    return 0;
}
int recvMsg(int msgid, int recvType, char out[])
{
    struct msgbuf buf;
    if(msgrcv(msgid, (void*)&buf, sizeof(buf.mtext), recvType,0) < 0){
        perror("msgrcv");
        return -1;
    }
    strcpy(out, buf.mtext);
    return 0;
}

server.c

#include "comm.h"
int main()
{
    int msgid = createMsgQueue();
    char buf[1024];
    while(1){
        buf[0] = 0;
        recvMsg(msgid, CLIENT_TYPE, buf);
        printf("client# %s\n", buf);
        printf("Please Enter# ");
        fflush(stdout);
        ssize_t s = read(0, buf, sizeof(buf));
        if(s>0){
            buf[s-1] = 0;
            sendMsg(msgid, SERVER_TYPE, buf);
            printf("send done, wait recv...\n");
        }
    }
    destroyMsgQueue(msgid);
    return 0;
}

cilent.c

#include "comm.h"
int main()
{
    int msgid = getMsgQueue();
    char buf[1024];
    while(1){
        buf[0] = 0;
        printf("Please Enter# ");
        fflush(stdout);
        ssize_t s = read(0, buf, sizeof(buf));
        if(s>0){
            buf[s-1] = 0;
            sendMsg(msgid, CLIENT_TYPE, buf);
            printf("send done, wait recv...\n");
        }
        recvMsg(msgid, SERVER_TYPE, buf);
        printf("server# %s\n", buf);
    }
    return 0;
}

运行结果:
这里写图片描述

这里写图片描述

ipcs & ipcrm命令
ipcs:显⽰IPC资源
ipcrm:⼿动删除IPC资源
共享内存
共享内存区是最快的IPC形式。⼀旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执⾏进⼊内核的系统调⽤来传递彼此的数据。
共享内存数据结构

struct shmid_ds {
    struct ipc_perm     shm_perm;   /* operation perms */
    int         shm_segsz;  /* size of segment (bytes) */
    __kernel_time_t     shm_atime;  /* last attach time */
    __kernel_time_t     shm_dtime;  /* last detach time */
    __kernel_time_t     shm_ctime;  /* last change time */
    __kernel_ipc_pid_t  shm_cpid;   /* pid of creator */
    __kernel_ipc_pid_t  shm_lpid;   /* pid of last operator */
    unsigned short      shm_nattch; /* no. of current attaches */
    unsigned short      shm_unused; /* compatibility */
    void            *shm_unused2;   /* ditto - used by DIPC */
    void            *shm_unused3;   /* unused */
};

共享内存函数
shmget函数:
功能:⽤来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
key:这个共享内存段名字
size:共享内存⼤⼩
shmflg:由九个权限标志构成,它们的⽤法和创建⽂件时使⽤的mode模式标志是⼀样的
返回值:成功返回⼀个⾮负整数,即该共享内存段的标识码;失败返回-1。
shmat函数:
功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回⼀个指针,指向共享内存第⼀个节;失败返回-1
shmdt函数:
功能:将共享内存段与当前进程脱离
原型
int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段。
shmctl函数:
功能:⽤于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向⼀个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1。

这里写图片描述
信号量
信号量和P、V原语
信号量:
互斥:P、V在同一个进程中
同步:P、V在不同的进程中
信号量值含义:
S>0:S表示可用资源的个数
S=0:表示无可用资源,无等待进程
S<0:|S|表示等待队列中进程的个数
信号量结构体伪代码:
信号量本质上是⼀个计数器。

struct semaphore
{
    int value;
    pointer_PCB queue;
}

P原语
P(s)
{
s.value = s.value–;
if (s.value < 0)
{
该进程状态置为等待状状态
将该进程的PCB插⼊相应的等待队列s.queue末尾
}
V原语
V(s)
{
s.value = s.value++;
if (s.value < =0)
{
唤醒相应等待队列s.queue中等待的⼀个进程
改变其状态为就绪态
并将其插⼊就绪队列
}
}
信号量集结构

struct semid_ds {
    struct ipc_perm sem_perm;  /* Ownership and permissions */
    time_t       sem_otime; /* Last semop time */
    time_t       sem_ctime; /* Last change time */
    unsigned short  sem_nsems; /* No. of semaphores in set */
};

信号量集函数
semget函数
功能:⽤来创建和访问⼀个信号量集
原型
int semget(key_t key, int nsems, int semflg);
参数
key: 信号集的名字
nsems:信号集中信号量的个数
semflg: 由九个权限标志构成,它们的⽤法和创建⽂件时使⽤的mode模式标志是⼀样的
返回值:成功返回⼀个⾮负整数,即该信号集的标识码;失败返回-1。
shmget函数
功能:⽤来创建和访问⼀个信号量集
原型
int semget(key_t key, int nsems, int semflg);
参数
key: 信号集的名字
nsems:信号集中信号量的个数
semflg: 由九个权限标志构成,它们的⽤法和创建⽂件时使⽤的mode模式标志是⼀样的
返回值:成功返回⼀个⾮负整数,即该信号集的标识码;失败返回-1。
shmctl函数
功能:⽤于控制信号量集
原型
int semctl(int semid, int semnum, int cmd, …);
参数
semid:由semget返回的信号集标识码
semnum:信号集中信号量的序号
cmd:将要采取的动作(有三个可取值)
最后⼀个参数根据命令不同⽽不同
返回值:成功返回0;失败返回-1。

这里写图片描述

semop函数
功能:⽤来创建和访问⼀个信号量集
原型
int semop(int semid, struct sembuf *sops, unsigned nsops);
参数
semid:是该信号量的标识码,也就是semget函数的返回值
sops:是个指向⼀个结构数值的指针
nsops:信号量的个数
返回值:成功返回0;失败返回-1。
说明:
sembuf结构体:
struct sembuf {
short sem_num;
short sem_op;
short sem_flg;
};
sem_num是信号量的编号。
sem_op是信号量⼀次PV操作时加减的数值,⼀般只会⽤到两个值:
⼀个是“-1”,也就是P操作,等待信号量变得可⽤;
另⼀个是“+1”,也就是我们的V操作,发出信号量已经变得可⽤
sem_flag的两个取值是IPC_NOWAIT或SEM_UNDO
实例代码:
comm.h

 #pargma once
 #include <stdio.h>
 #include <sys/types.h>
 #include <sys/ipc.h>
 #include <sys/sem.h>
 #define PATHNAME "."
 #define PROJ_ID 0X6666
union semun {
    int              val;    /* Value for SETVAL */
    struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
    unsigned short  *array;  /* Array for GETALL, SETALL */
    struct seminfo  *__buf;  /* Buffer for IPC_INFO */
};
int createSemSet(int nums);
int initSem(int semid, int nums, int initVal);
int getSemSet(int nums);
int P(int semid, int who);
int V(int semid, int who);
int destroySemSet(int semid);

comm.c

 #include "comm.h"
static int commSemSet(int nums, int flags)
{
    key_t _key = ftok(PATHNAME, PROJ_ID);
    if(_key < 0){
        perror("ftok");
        return -1;
    }
    int semid = semget(_key, nums, flags);
    if(semid < 0){
        perror("semget");
        return -2;
    }
    return semid;
}
int createSemSet(int nums)
{
    return commSemSet(nums, IPC_CREAT|IPC_EXCL|0666);
}
int getSemSet(int nums)
{
    return commSemSet(nums, IPC_CREAT);
}
int initSem(int semid, int nums, int initVal)
{
    union semun _un;
    _un.val = initVal;
    if(semctl(semid, nums, SETVAL, _un)<0){
        perror("semctl");
        return -1;
    }
    return 0;
}
static int commPV(int semid, int who, int op)
{
    struct sembuf _sf;
    _sf.sem_num = who;
    _sf.sem_op = op;
    _sf.sem_flg = 0;
    if(semop(semid, &_sf, 1) < 0){
        perror("semop");
        return -1;
    }
    return 0;
}
int P(int semid, int who)
{
    return commPV(semid, who, -1);
}
int V(int semid, int who)
{
    return commPV(semid, who, 1);
}
int destroySemSet(int semid)
{
    if(semctl(semid, 0, IPC_RMID) < 0){
perror("semctl");
        return -1;
    }   
}

test_sem.c

#include "comm.h"
int main()
{
    int semid = createSemSet(1);
    initSem(semid, 0, 1);
    pid_t id = fork();
    if(id == 0){//child
        int _semid = getSemSet(0);
        while(1){
            P(_semid, 0);
            printf("A");
            fflush(stdout);
            usleep(123456);
            printf("A ");
            fflush(stdout);
            usleep(321456);
            V(_semid, 0);
    }
    }else{ //father
        while(1){
            P(semid, 0);
            printf("B");
            fflush(stdout);
            usleep(223456);
            printf("B ");
            fflush(stdout);
            usleep(121456);
            V(semid, 0);
        }
        wait(NULL);
    }
    destroySemSet(semid);
    return 0;
}

运行结果:
这里写图片描述

阅读更多
上一篇静态数组顺序表
下一篇进程信号
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭