Linux学习之进程通讯(IPC)—进程之间的通信

前言

本文记录进程之间不同通讯方式的学习过程以及程序案例,学习过程中得益于以下两篇优秀博文:

凉了!张三同学没答好「进程间通信」,被面试官挂了…(图文并茂,言简意骇,有助于概念理解)
进程间通信的六种常见方式(逻辑清晰,干净整洁,有程序源码)

在这里插入图片描述

一、管道

管道使用简单,但效率较低不适用于进程之间的频繁信息交流

1.1匿名管道

匿名管道pipe 是一种半双工的通信方式,只能在具有亲缘关系的进程间使用.进程的亲缘关系一般指的是父子关系。使用写入端口时关闭读取端口,使用读取端口时关闭写入端口,可以使用普通的read、write等函数进行读写操作,操作时遵循先进先出原则。

1.1.1 pipe 原型

   	#include <unistd.h>

    int pipe(int pipefd[2]); 

1.1.2 pipe 实例

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>
//匿名管道的创建与使用
int main()
{
    
    
        int fd[2];
        char buf[1024]={
    
    ""};
        int pid;
        int states;

        if(pipe(fd)==-1)
        {
    
    
                printf("fail!\n");
        }

        pid=fork();

        if(pid<0)
        {
    
    
                printf("creat child fail!\n");
        }
        else if(pid>0)//父进程
        {
    
    
                printf("This is Father\n");
                sleep(3);
                close(fd[0]);//管道0负责读 1负责写
                write(fd[1],"Hello from father",strlen("Hello from father"));
                wait(&states);
        }
        else//子进程
        {
    
    
                printf("This is child\n");
                close(fd[1]);
                read(fd[0],buf,1000);
                printf("read from father:%s\n",buf);
                exit(0);
        }
        return 0;
}

1.2 命名管道

命名管道FIFO,以一种特殊设备文件形式存在于文件系统中,有路径名与之相关联。因此,可以在无关的进程之间交换数据。

1.2.1 fifo 原型

    #include <sys/types.h>

    #include <sys/stat.h>

    int mkfifo(const char *pathname, mode_t mode);

1.2.2 fifo 实例

进程向管道写入

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
//有名管道的写入
int main()
{
    
    
        char *str="message from fife";
        int cnt=0;
        int fd=open("flie",O_WRONLY);
        printf("write open success\n");

        while(1)
        {
    
    

                sleep(1);
                write(fd,str,strlen(str));
                if(cnt>5)
                {
    
    
                        break;
                }
                cnt++;
        }

        close(fd);
        return 0;
}

进程从管道读取

#include <sys/types.h>
#include <sys/stat.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include <unistd.h>
#include<fcntl.h>
#include <errno.h>


//有名管道的读取fifo
int main()
{
    
    
        char buf[30]={
    
    0};
        int nread=0;

        if(mkfifo("flie",0600==-1) && errno!=EEXIST)
        {
    
    
                printf("mkfifo failed\n");
                perror("why");
        }

        int fd=open("flie",O_RDONLY);
        printf("open success\n");
        while(1)
        {
    
    
                nread=read(fd,buf,30);
                printf("read %d byte from fifo,context:%s\n",nread,buf);
        }
        close(fd);

        return 0;
}

二、消息队列

消息队列保存在内核中的消息链表,在发送数据时,会分成一个一个独立的数据单元,也就是消息体(数据块),消息体是用户自定义的数据类型,消息的发送方和接收方要约定好消息体的数据类型,所以每个消息体都是固定大小的存储块,不像管道是无格式的字节流数据。如果进程从消息队列中读取了消息体,内核就会把这个消息体删除,不必遵循先进先出原则,可以跟据数据类型进行读取。

消息队列生命周期随内核,如果没有释放消息队列或者没有关闭操作系统,消息队列会一直存在,而前面提到的匿名管道的生命周期,是随进程的创建而建立,随进程的结束而销毁。
但是,鉴于传输上限的限制消息队列不适合应用于大型数据的传输。同时,本质是用户态与内核态之间的信息拷贝,增大了工作量。

2.1 msg 原型

// 创建或打开消息队列:成功返回队列ID,失败返回-1
int msgget(key_t key, int flag);
// 添加消息:成功返回0,失败返回-1
int msgsnd(int msqid, const void *ptr, size_t size, int flag);
// 读取消息:成功返回消息数据的长度,失败返回-1
int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
// 控制消息队列:成功返回0,失败返回-1
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

2.2 msg 实例

2.2.1 单向通信实例

发送端

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

struct msgbuf
{
    
    
        long mtype;
        char mtext[128];
};

int main()
{
    
    
        struct msgbuf  sendbuf={
    
    888,"This is message from quen"};

        //1. int msgget(key_t key, int msgflg);
        int msgid=msgget(0x1234,IPC_CREAT|0777);

        if(msgid==-1)
        {
    
    
                printf("creat que failed!\n");
        }
        //2. int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

        //3. ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg)

        msgsnd(msgid,&sendbuf,strlen(sendbuf.mtext),0);
        return 0;
}

接收端

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>



struct msgbuf
{
    
    
        long mtype;
        char mtext[128];
};

int main()
{
    
    
        struct msgbuf  readbuf;
        //1. int msgget(key_t key, int msgflg);
        int msgid=msgget(0x1234,IPC_CREAT|0777);
        if(msgid==-1)
        {
    
    
                printf("creat que failed!\n");
        }
        // int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

        // ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg)

        msgrcv(msgid,&readbuf,sizeof(readbuf.mtext),888,0);
        printf("read from que:%s\n",readbuf.mtext);
        return 0;
}

2.2.2 双向通信实例

先发再读

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//       int msgget(key_t key, int msgflg);
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
 
//       ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
struct msgbuf{
    
    
        long mtype;       /* message type, must be > 0 */
        char mtext[128];    /* message data */
};
 
 
int main()
{
    
    
        struct msgbuf sendbuf={
    
    888,"message from send"};
        struct msgbuf readbuf;
 
        key_t key;
 
        if((key = ftok(".",'z')) < 0){
    
    
                printf("ftok error\n");
        }
        int msgId = msgget(key,IPC_CREAT|0777);
 
        if(msgId == -1){
    
    
                printf("get quen failed\n");
        }
 
        msgsnd(msgId,&sendbuf,strlen(sendbuf.mtext),0);
        printf("send over\n");
 
        msgrcv(msgId,&readbuf,sizeof(readbuf.mtext),999,0);
        printf("read from get is:%s\n",readbuf.mtext);
 
        msgctl(msgId,IPC_RMID,NULL);
 
        return 0;
}

先读再发

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//       int msgget(key_t key, int msgflg);
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
 
//       ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
struct msgbuf{
    
    
        long mtype;       /* message type, must be > 0 */
        char mtext[128];    /* message data */
};
 
int main()
{
    
    
        struct msgbuf readbuf;
        memset(readbuf.mtext, '\0', sizeof(readbuf.mtext));
        struct msgbuf sendbuf={
    
    999,"thank for your reach"};
 
        key_t key;
 
        //获取key值
        if((key = ftok(".",'z')) < 0){
    
    
                printf("ftok error\n");
        }
 
        int msgId = msgget(key,IPC_CREAT|0777);
 
        if(msgId == -1){
    
    
                printf("get quen failed\n");
                perror("why");
        }
 
        msgrcv(msgId,&readbuf,sizeof(readbuf.mtext),888,0);
        printf("read from send is:%s\n",readbuf.mtext);
 
        msgsnd(msgId,&sendbuf,strlen(sendbuf.mtext),0);
 
        msgctl(msgId,IPC_RMID,NULL);
 
        return 0;
}

三、共享内存

消息队列的读取和写入的过程,都会有发生用户态与内核态之间的消息拷贝过程。那共享内存的方式,就很好的解决了这一问题。共享内存的机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存中。这样这个进程写入的东西,另外一个进程马上就能看到了,不需要拷贝来拷贝去,传来传去,大大提高了进程间通信的速度

3.1 共享内存的原型

// 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
int shmget(key_t key, size_t size, int flag);
// 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
void *shmat(int shm_id, const void *addr, int flag);
// 断开与共享内存的连接:成功返回0,失败返回-1
int shmdt(void *addr); 
// 控制共享内存的相关信息:成功返回0,失败返回-1
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

3.2 shm 实例

发送数据

#include <string.h>
#include <stdlib.h>
#include <unistd.h>
//共享内存的创建

//int shmget(key_t key, size_t size, int shmflg);

//void *shmat(int shmid, const void *shmaddr, int shmflg);
//int shmdt(const void *shmaddr);

//void *shmat(int shmid, const void *shmaddr, int shmflg);
//int shmdt(const void *shmaddr);

int main()
{
    
    
        key_t key;
        key=ftok(".",1);

        int shmid=shmget(key,1024*4,IPC_CREAT|0777);//创建,内存大小必须得是MB的整数倍
        if(shmid==-1)
        {
    
    
                printf("failed!\n");
                exit(-1);
        }

        char *shmadder=shmat(shmid,0,0);//映射挂载
        printf("shmat ok\n");
        strcpy(shmadder,"message frome send");

        sleep(5);//等待五秒

        shmdt(shmadder);//卸载
        shmctl(shmid,IPC_RMID,0);//删除空间

        printf("quit!!!\n");
        return 0;
}

读取数据

#include <sys/shm.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
//共享内存的创建

//int shmget(key_t key, size_t size, int shmflg);

//void *shmat(int shmid, const void *shmaddr, int shmflg);
//int shmdt(const void *shmaddr);

//void *shmat(int shmid, const void *shmaddr, int shmflg);
//int shmdt(const void *shmaddr);

int main()
{
    
    
        key_t key;
        key=ftok(".",1);

        int shmid=shmget(key,1024*4,0);//获取
        if(shmid==-1)
        {
    
    
                printf("failed!\n");
                exit(-1);
        }

        char *shmadder=shmat(shmid,0,0);//映射挂载
        printf("shmat ok\n");
        printf("data:%s\n",shmadder);


        shmdt(shmadder);//卸载
        shmctl(shmid,IPC_RMID,0);//删除空间

        printf("quit!!!\n");
        return 0;
}

四、信号量

信号量与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥同步,而不是用于存储进程间通信数据。信号量基于操作系统的 P、V原子操作,用于进程间同步,若要在进程间传递数据需要结合共享内存。每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。

4.1 信号量原型

// 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
int semget(key_t key, int num_sems, int sem_flags);
// 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
int semop(int semid, struct sembuf semoparray[], size_t numops);  
// 控制信号量的相关信息
int semctl(int semid, int sem_num, int cmd, ...);

4.2 信号量实例

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
 
//       int semget(key_t key, int nsems, int semflg);
//       int semctl(int semid, int semnum, int cmd, ...);
//       int semop(int semid, struct sembuf *sops, size_t nsops);
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
                                    (Linux-specific) */
};
 
//P操作,拿钥匙
void PGetKey(int semid)
{
    
    
        struct sembuf sops;
        sops.sem_num = 0;// 信号量组中对应的序号,0~sem_nums-1
        sops.sem_op = -1;// 信号量值在一次操作中的改变量
        sops.sem_flg = SEM_UNDO;// IPC_NOWAIT, SEM_UNDO
 
        semop(semid, &sops, 1);
}
 
//V操作,放回钥匙
void VPutBackKey(int semid)
{
    
    
        struct sembuf sops;
        sops.sem_num = 0;
        sops.sem_op = 1;
        sops.sem_flg = SEM_UNDO;
 
        semop(semid, &sops, 1);
}
 
int main()
{
    
    
        key_t key;
        int semid;
        if((key == ftok(".",6)) < 0)
        {
    
    
                printf("ftok error\n");
        }
 
        semid = semget(key , 1,  IPC_CREAT|0666);//创造信号量集合 钥匙,数量为1
 
        union semun sem;
        sem.val = 0;//初始状态为没有钥匙
        semctl(semid, 0, SETVAL, sem);//SETVAL初始化信号量为一个已知的值,这时就需要第四个参数
                     //0表示操作第一把钥匙
        int pid = fork();
 
        if(pid < 0)
        {
    
    
                printf("fork failed\n");
        }else if(pid == 0)//子进程
        {
    
    
                printf("this is child\n");
                VPutBackKey(semid);//子进程运行结束后把钥匙放回     
        }else//父进程
        {
    
    
                PGetKey(semid);//等子进程将钥匙放回后才会进行操作,保证子进程先执行(最开始无锁状态,父进程拿不到锁,进程堵塞)
                printf("this is father\n");
                VPutBackKey(semid);
                semctl(semid, 0, IPC_RMID);//销毁钥匙
        }
 
        return 0;
}

五、信号

信号signal属于软中断,属于异步通讯。终端用户输入了 ctrl+c 来中断程序,会通过信号机制(SIGINT)停止一个程序。
信号处理的三种方法分别是:忽略捕捉
默认动作

5.1 signal

5.1.1信号signal原型

     //接收函数,第二个参数指向信号处理函数
    sighandler_t signal(int signum, sighandler_t handler);

    //发送函数
     int kill(pid_t pid, int sig);

5.1.2信号sig实例

接收

#include <stdio.h>
#include <signal.h>
 
//       typedef void (*sighandler_t)(int);
 
//       sighandler_t signal(int signum, sighandler_t handler);
/*接受到信号后,让信号处理该函数*/
void handler(int signum)
{
    
    
        printf("signum = %d\n",signum);
 
        switch(signum){
    
    
                case 2:
                        printf("SIGINT\n");
                        break;
                case 9:
                        printf("SIGKILL\n");
                        break;
                case 10:
                        printf("SIGUSR1\n");
                        break;
        }
}
 
int main()
{
    
    
        signal(SIGINT,handler);
        signal(SIGKILL,handler);
        signal(SIGUSR1,handler);
 
        while(1);
 
        return 0;
}

发送

#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>
 
//       int kill(pid_t pid, int sig);
 
int main(int argc,char **argv)
{
    
    
        int signum;
        int pid;
        char cmd[128]={
    
    0};
 
        signum = atoi(argv[1]);//将字符型转为整型 //消息号       
        pid = atoi(argv[2]);//进程号
 
        kill(pid,signum);//进程号,信号编号
        
 //		sprintf(cmd,"kill -%d%d",signum,pid);       
 //     system(cmd);
        printf("signum = %d,pid = %d\n",signum,pid);
 
        return 0;
}

5.2 sigaction

5.2.1 sigaction 原型

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
 
struct sigaction {
    
    
   void       (*sa_handler)(int); //信号处理程序,不接受额外数据,SIG_IGN 为忽略,SIG_DFL 为默认动作
   void       (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序,能够接受额外数据和sigqueue配合使用
   sigset_t   sa_mask;//阻塞关键字的信号集,可以再调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值。
   int        sa_flags;//影响信号的行为SA_SIGINFO表示能够接受数据
 };
//回调函数句柄sa_handler、sa_sigaction只能任选其一

5.2.2 sigaction 实例

接收端

#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
//       int sigaction(int signum, const struct sigaction *act,
//                     struct sigaction *oldact);

void handler(int signum ,siginfo_t *info,void *context)
{
    
    
        printf("get signum%d\n",signum);
        if(context!=NULL)
        {
    
    
                printf("get data=%d\n",info->si_int);
                printf("from:%d\n",info->si_pid);
        }
}


int main()
{
    
    

        struct sigaction act;
        printf("pid:%d\n",getpid());

        act.sa_sigaction = handler;
        act.sa_flags = SA_SIGINFO;//be able to get message

        sigaction(SIGUSR1,&act,NULL);//注册信号
        while(1);
        return 0;
}

发送端

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
//  int sigqueue(pid_t pid, int sig, const union sigval value);


int main(int argc,char **argv)
{
    
    
        int signum;

        int pid;

        union sigval value;
        value.sival_int=100;


        signum = atoi(argv[1]);
        pid = atoi(argv[2]);
        sigqueue(pid,signum,value);
        printf("send successful\n");
        printf("pid=%d,done\n",getpid());
        return 0;
}

六、Socket

猜你喜欢

转载自blog.csdn.net/ONERYJHHH/article/details/126534712