Linux进程间通信学习笔记

5 篇文章 0 订阅
4 篇文章 0 订阅

Linux进程间通信

进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。
IPC 的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams 等。其中 Socket 和 Streams 支持不同主机上的两个进程 IPC。

无名管道

它是半双工的(即数据只能在一个方向上流动),具有固定的读端(fd[0])和写端(fd[1])。

它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。

它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。

函数所需头文件及原型pipe

	   #include <unistd.h>

       int pipe(int pipefd[2]);

返回值:创建失败返回-1,创建成功返回0
无名管道例子:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
        int fd[2];
        int pid;
        char buf[128];
        if(pipe(fd) == -1){
                printf("creat pipe failed\n");
        }

        pid = fork();

        if(pid < 0){
                printf("creat child failed\n");
        }
        else if (pid > 0){
                sleep(2);
                printf("this is father\n");
                close(fd[0]);
                write(fd[1],"hello this is a demo",strlen("hello this is a demo"));
        }
        else{
                printf("this is child\n");
                close(fd[1]);
                read(fd[0],buf,128);
                printf("read from father: %s\n",buf);
        }

        return 0;
}

注意: 管道为半双工通信,读和写操作在同一时间内只能进行一个,所以在读的时候要关闭写端,写的时候关闭读端。
运行结果:
在这里插入图片描述

命名管道

和无名管道的主要区别在于,命名管道有一个名字,命名管道的名字对应于一个磁盘索引节点,有了这个文件名,任何进程有相应的权限都可以对它进行访问。

而无名管道却不同,进程只能访问自己或祖先创建的管道,而不能访任意访问已经存在的管道——因为没有名字。

Linux中通过系统调用mknod()或makefifo()来创建一个命名管道。最简单的方式是通过直接使用shell

函数所需头文件及原型fifo

	   #include <sys/types.h>
       #include <sys/stat.h>

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

参数:
pathname:路径名(例如:./file)
mode:权限(例如:0600)
命名管道例子:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>

int main()
{
        int ret;
        ret = mkfifo("./file",0600);

        if(ret == 0){
                printf("mkfifo success!\n");
        }
        if(ret == -1){
                printf("mkfifo failure!\n");
                perror("why");
        }
        return 0;
}

ps:创建命名管道后会生成一个文件(ls查看)
当 open 一个 FIFO 时,是否设置非阻塞标志(O_NONBLOCK)的区别:

==若没有指定 O_NONBLOCK(默认),只读 open 要阻塞到某个其他进程为写而打开此 FIFO。类似的,只写 open 要阻塞到某个其他进程为读而打开它。

==若指定了 O_NONBLOCK,则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO,其 errno 置 ENXIO。
例子:
在这里插入图片描述

在这里插入图片描述
read代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
        int nread;
        mkfifo("./file",0600);
        char buf[1024] = {0};
        int fd = open("./file",O_RDONLY);
        printf("open success\n");
        while(1){
                nread = read(fd,buf,1024);
                printf("read %d byte ,neirong:%s\n",nread,buf);
        }
        return 0;
}

write代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
        mkfifo("./file",0600);
        char *str = "this is a fifo demo!";
        int fd = open("./file",O_WRONLY);
        printf("open success\n");
        while(1){
                write(fd,str,strlen(str));
                sleep(1);
        }
        return 0;
}

运行read会阻塞,一直到运行write后read才会继续往下执行。

消息队列

1.消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列 ID)来标识。
2.消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。

3.消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。

4.消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

函数原型

#include <sys/msg.h>
// 创建或打开消息队列:成功返回队列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

例子:
get代码:

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

struct msgbuf {
        long mtype;       /* message type, must be > 0 */
        char mtext[128];    /* message data */
};

int main()
{
        struct msgbuf readBuf;

        int msgId = msgget(0x1236,IPC_CREAT|0777);//0777为可读可写可执行权限
        //这里0x1236可用ftok函数创建,具体百度百科。
        if(msgId == -1){
                printf("msgget failure!\n");
        }

        msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);
        printf("read form que:%s\n",readBuf.mtext);
        msgctl(msgId,IPC_RMID,NULL);//移除消息队列,防止占用内存
        return 0;
}

send代码:

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

struct msgbuf {
        long mtype;       /* message type, must be > 0 */
        char mtext[128];    /* message data */
};

int main()
{
        struct msgbuf writeBuf = {888,"this is mssage from que!"};
        int msgId = msgget(0x1235,IPC_CREAT|0777);

        if(msgId == -1){
                printf("msgget failure!\n");
        }

        msgsnd(msgId,&writeBuf,strlen(writeBuf.mtext),0);
		msgctl(msgId,IPC_RMID,NULL);//移除消息队列,防止占用内存
        return 0;
}

共享内存

共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。

共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。

因为多个进程可以同时操作,所以需要进行同步。

信号量 + 共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。

函数原型

// 创建或获取一个共享内存:成功返回共享内存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);

例子
shmw代码

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <string.h>
int main()
{
        int shmid;//类似文件描述符
        char *shmaddr;//共享内存的指针
        key_t key;
        key = ftok(".",1);
        shmid = shmget(key,1024*4,IPC_CREAT|0666);//创建获取功效内存
        if(shmid == -1){
                printf("shmget failure!\n");
        }
        shmaddr = shmat(shmid,0,0);//连接到共享内存,返回的为共享内存的指针

        printf("shmat ok\n");
        strcpy(shmaddr,"this is a shm demo");//给共享内存写入数据

        sleep(5);

        shmdt(shmaddr);//断开连接
        shmctl(shmid,IPC_RMID, 0);//清楚共享内存
        printf("quit\n");

        return 0;
}

shmr代码

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <string.h>
#include<stdlib.h>
int main()
{
        int shmid;
        char *shmaddr;
        key_t key;
        key = ftok(".",1);

        shmid = shmget(key,1024*4,0);
        if(shmid == -1){
                printf("shmget failure!\n");
                exit(-1);
        }
        shmaddr = shmat(shmid,0,0);

        printf("shmat ok\n");

        printf("data ;%s\n",shmaddr);
        shmdt(shmaddr);
        printf("quit\n");

        return 0;
}

信号

信号的本质是软件层次上对中断的一种模拟。它是一种异步通信的处理机制,事实上,进程并不知道信号何时到来。
信号的捕捉:

#include <signal.h>
#include <stdio.h>
void handler(int signum)
{
        printf("get signum = %d\n",signum);
        printf("never quit\n");
}
int main()
{
        signal(SIGINT,handler);
        while(1);
        return 0;
}
//运行程序后按下ctrl+c程序不会结束,在另一个终端调用kill 9 pid命令来强制结束次程序
//pid查看方法:ps -aux|grep 程序名

发送信号

#include <signal.h>
#include <stdio.h>
#include <sys/types.h>

int main(int argc,char **argv)
{
        int signum;
        int pid;
        signum = atoi(argv[1]);
        pid = atoi(argv[2]);
        printf("num = %d,pid = %d\n",signum,pid);
        kill(pid,signum);
        printf("send signal ok!");
        return 0;
}
~ 

信号携带信息实例

//高级 
#include <signal.h>
#include<stdio.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("get data = %d\n",info->si_value.sival_int);
        }

}
int main()
{
        struct sigaction act;

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

        sigaction(SIGUSR1,&act,NULL);
        while(1);
        return 0;
}
//send
#include <stdio.h>
#include <signal.h>

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

        signum = atoi(argv[1]);
        pid = atoi(argv[2]);

        union sigval value;
        value.sival_int = 100;
//      int sigqueue(pid_t pid, int sig, const union sigval value);

        sigqueue(pid,signum,value);
        printf("done\n"); 
        return 0;
}

信号量

信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。

信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。

每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。

支持信号量组。
函数原型

#include <sys/sem.h>
// 创建或获取一个信号量组:若成功返回信号量集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, ...);

参数介绍:当 semget 创建新的信号量集合时,必须指定集合中信号量的个数(即 num_sems),通常为 1; 如果是引用一个现有的集合,则将 num_sems 指定为 0 。
在 semop 函数中,sembuf 结构的定义如下:

struct sembuf 
{
    short sem_num; // 信号量组中对应的序号,0~sem_nums-1
    short sem_op;  // 信号量值在一次操作中的改变量
    short sem_flg; // IPC_NOWAIT, SEM_UNDO
}

在 semctl 函数中的命令有多种,这里就说两个常用的:

SETVAL:用于初始化信号量为一个已知的值。所需要的值作为联合 semun 的 val 成员来传递。在信号量第一次使用之前需要设置信号量。
IPC_RMID:删除一个信号量集合。如果不删除信号量,它将继续在系统中存在,即使程序已经退出,它可能在你下次运行此程序时引发问题,而且信号量是一种有限的资源。

参考:网友精彩博文

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值