Linux进程间通信

目录

一.无名管道

二.命名管道

三.消息队列

四.共享内存

五.信号

六.信号量


进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。

IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。

一.无名管道

管道,通常指无名管道,是 UNIX 系统IPC最古老的形式。

特点:

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

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

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

原型:

#include <unistd.h>
int pipe(int fd[2]);    // 返回值:若成功返回0,失败返回-1

当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。如下图:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main()
{
     int fd[2];

     int pid;

     char readbuf[128];

     if(pipe(fd) == -1){
          printf("creat pipe 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]);
          write(fd[1],"hellow this father",strlen("hellow this father"));
          wait();
     }else{
          printf("this is child\n");
          close(fd[1]);
          read(fd[0],readbuf,128);
          printf("this is from father:%s\n",readbuf);
          exit(0);
     }
     return 0;
}

二.命名管道

FIFO,也称为命名管道,它是一种文件类型。

特点

FIFO可以在无关的进程之间交换数据,与无名管道不同。

FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。

原型

#include <sys/stat.h> // 返回值:成功返回0,出错返回-1
int mkfifo(const char *pathname, mode_t mode);
/*其中的 mode 参数与open函数中的 mode 相同。
一旦创建了一个 FIFO,就可以用一般的文件I/O函数操作它。*/

当 open 一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别:

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

若指定了O_NONBLOCK,则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO,其c置ENXIO。

demo

//read.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
     char readbuf[30] = {0};

     if(mkfifo("./file",0600) == -1){
        printf("mkfifo fail\n");     //创建fifo
     }else{
        printf("creat fifo success\n");
     }

     int fd = open("./file",O_RDONLY); //只读打开
     printf("open file success\n");

     while(1){
        int n_read = read(fd,readbuf,30);  //读取数据
        printf("read %d bytes,readbuf is %s\n",n_read,readbuf);
     }

     close(fd);
     return 0;
}
//write.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
     char *str = "message";

     int fd = open("./file",O_WRONLY);  //只写打开
     printf("write open file success\n");

     while(1){
        int n_write = write(fd,str,strlen(str)); //写入数据
        sleep(1);
     }

     close(fd);
     return 0;
}

三.消息队列

消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。

1.特点

(1)消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。

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

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

2.消息队列相关API

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);// 创建或打开消息队列:成功返回队列ID,失败返回-1
创建或打开消息队列
key和消息队列关联的key值(通过这个key在内核中找到某个队列)
msgflg是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。
返回值成功返回队列ID,失败返回-1

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
// 添加消息:成功返回0,失败返回-1
添加消息
msgid消息队列的ID
*msgp指向消息的指针
msgsz发送的消息正文你的字节数
msgflg

IPC_NOWAIT:消息没有发送完成函数也会立即返回

0:知道发送完成函数才返回


ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
// 读取消息:成功返回消息数据的长度,失败返回-1
读取消息
msgid消息队列的ID
msgp要接收消息的缓冲区
msgsz要接收的消息的字节数
msgtyp

0:接收消息队列中第一个消息

大于0:接收消息队列中第一个类型为msgtyp的消息

小于0:接收消息队列中类型值不大于msgtyp的绝对值且类型值又最小的消息。

flag

0:若无消息函数一直阻塞

IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG。


int msgctl(int msqid, int cmd, struct msqid_ds *buf);
//控制消息队列,成功返回0,失败返回‐1
/*一般情况配合IPC_RMID使用,最后一个参数设为NULL,用于删除消息队列*/

demo 

//msgget.c
#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 readbuf;
     struct msgbuf sendbuf = {888,"this is from quen"};

     int msgid = msgget(0x1235, IPC_CREAT|0777);
     if(msgid == -1){
        printf("IPC creat fail\n");
    }

     msgsnd(msgid,&sendbuf,strlen(sendbuf.mtext),0);
     msgrcv(msgid,&readbuf,sizeof(readbuf.mtext),988,0);

     printf("return from send:%s\n",readbuf.mtext);
     return 0;
}


//msgsend.c
#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 readbuf;
     struct msgbuf sendbuf = {888,"this is from quen"};

     int msgid = msgget(0x1235, IPC_CREAT|0777);
     if(msgid == -1){
        printf("IPC creat fail\n");
    }

     msgsnd(msgid,&sendbuf,strlen(sendbuf.mtext),0);
     msgrcv(msgid,&readbuf,sizeof(readbuf.mtext),988,0);

     printf("return from send:%s\n",readbuf.mtext);
     return 0;
}

ftok函数

key_t ftok( char * fname, int id )
//系统建立IPC通讯(如消息队列、共享内存时)必须指定一个ID值。
//通常情况下,该id值通过ftok函数得到。

参数: fname就时你指定的文件名(该文件必须是存在而且可以访问的)。 id是子序号, 虽然为int,但是只有8个比特被使用(0‐255)。

返回值: 当成功执行的时候,一个key_t值将会被返回,否则 ‐1 被返回。

四.共享内存

概念

共享内存(Shared Memory)就是允许多个进程访问同一个内存空间,是在多个进程之间共享和传递数据最高效的方式。操作系统将不同进程之间共享内存安排为同一段物理内存,进程可以将共享内存连接到它们自己的地址空间中,如果某个进程修改了共享内存中的数据,其它的进程读到的数据也将会改变。

特点

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

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

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

相关函数

int shmget(key_t key, size_t size, int shmflg);
//用来获取或创建共享内存
参数:
key:IPC_PRIVATE 或 ftok的返回值
size:共享内存区大小
shmflg:同open函数的权限位,也可以用8进制表示法
返回值:
成功:共享内存段标识符‐‐‐ID‐‐‐文件描述符
出错:‐1
void *shmat(int shm_id, const void *shm_addr, int shmflg);
//把共享内存连接映射到当前进程的地址空间
参数:
shm_id:ID号
shm_addr:映射到的地址,NULL为系统自动完成的映射
shmflg:SHM_RDONLY共享内存只读
默认是0,表示共享内存可读写
返回值:
成功:映射后的地址
失败:NULL
int shmdt(const void *shmaddr);
//将进程里的地址映射删除
参数:
shmid:要操作的共享内存标识符
返回值:
成功:0
出错:‐1
int shmctl(int shm_id, int command, struct shmid_ds *buf);
//删除共享内存对象
参数:
shmid:要操作的共享内存标识符
cmd :
IPC_STAT (获取对象属性)‐‐‐ 实现了命令ipcs ‐m
IPC_SET (设置对象属性)
IPC_RMID (删除对象) ‐‐‐实现了命令ipcrm ‐m
buf :指定IPC_STAT/IPC_SET时用以保存/设置属性
返回值:
成功:0
出错:‐1

demo

//shmw.c
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

//       int shmget(key_t key, size_t size, int shmflg);
//       void *shmat(int shmid, const void *shmaddr, int shmflg);
//       shmctl(int shmid, int cmd, struct shmid_ds *buf);

int main()
{
        key_t key;

        key = ftok(".",1);
        int shmid = shmget(key,1024*4,IPC_CREAT|0666); //创建共享内存
        if(shmid == -1){
                printf("shmget fail\n");
                exit(-1);
        }
        char *shmaddr = shmat(shmid,0,0); //连接共享内存到当前进程的地址空间
        printf("shmat ok\n");
        strcpy(shmaddr,"this is msg");
        sleep(5);
        shmdt(shmaddr);  //断开与共享内存的连接
        shmctl(shmid,IPC_RMID,0);  //删除共享内存对象
        printf("quit\n");
        return 0;
}

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

//       int shmget(key_t key, size_t size, int shmflg);
//       void *shmat(int shmid, const void *shmaddr, int shmflg);
//       shmctl(int shmid, int cmd, struct shmid_ds *buf);

int main()
{
        key_t key;

        key = ftok(".",1);
        int shmid = shmget(key,1024*4,0); //获取共享内存
        if(shmid == -1){
                printf("shmget fail\n");
                perror("why");
                exit(-1);
        }
        char *shmaddr = shmat(shmid,0,0); //连接共享内存到当前进程的地址空间
        printf("shmat ok1\n");
        printf("data:%s\n",shmaddr);
        shmdt(shmaddr);  //断开与共享内存的连接

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

ipcs -m:查看系统中有哪些共享内存


 ipcrm -m 共享内存id号:删除该共享内存 

五.信号

对Linux来说信号相当于是软中断,许多程序都需要去处理信号。

1.信号的名字和编号:
每个信号都有一个名字和编号,这些名字都以“SIG”开头,例如“SIGIO ”、“SIGCHLD”等等。
信号定义在signal.h头文件中,信号名都定义为正整数。
具体的信号名称可以使用kill -l来查看信号的名字以及序号,信号是从1开始编号的,不存在0号信号。kill对于信号0又特殊的应用。 

ps -aux|grep  查找特定进程的信息

2.信号的处理:
信号的处理有三种方法,分别是:忽略捕捉默认动作

  • 忽略信号,大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是 SIGKILL和SIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景
  • 捕捉信号,需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
  • 系统默认动作,对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。
    具体的信号默认动作可以使用man 7 signal来查看系统的具体定义。

相关函数:

//信号函数的注册
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
参数:
signum:所接收到的信号
sighandler_t handler:接收到信号所执行的函数
//信号的发送
#include<signal.h>
#include<sys/types.h>
int kill(pid_t pid, int sig);
参数:
函数传入值:pid
正数:要接收信号的进程的进程号
 0:信号被发送到所有和pid进程在同一个进程组的进程
‐1:信号发给所有的进程表中的进程(除了进程号最大的进程外)
sig:信号
函数返回值:成功 0 出错 ‐1

demo

//signaldemo1.c
#include <signal.h>
#include <stdio.h>

void handler(int signum)
{
        printf("get signal = %d\n",signum);
        printf("never quit\n");
}

int main()
{
        signal(SIGINT,handler); //注册信号
        while(1);

        return 0;
}
//signaldemo2.c
#include <signal.h>
#include <stdio.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 siganl ok\n");
        while(1);

        return 0;
}

六.信号量

信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。

特点:

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

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

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

4.支持信号量组。

相关函数:

//获取/创建信号量
#include <sys/sem.h>
int semget(key_t key, int num_sems, int sem_flags);
// 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
参数:
第一个参数:key        
第二个参数:信号量个数      
第三个参数:是否创建信号量
//控制信号量的相关信息
int semctl(int semid, int sem_num, int cmd, ...); 

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) */
           };
参数:
第一个参数:操作对象的ID       
第二个参数:操作第几个信号量     
第三个参数:SETVAL 设置信号量初值(宏)
第四个参数:初始化信号量
int semop(int semid, struct sembuf semoparray[], size_t numops);  
// 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1

           unsigned short sem_num;  /* semaphore number */
           short          sem_op;   /* semaphore operation */
           short          sem_flg;  /* operation flags */

参数:
semid:ID
struct sembuf semoparray[]:对信号量的操作
numops:信号量个数

demo

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

// int semget(key_t key, int nsems, int semflg);
// int semctl(int semid, int semnum, int cmd, ...);

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) */
};

void P_operation (int id){  //P操作
        struct sembuf set;
        set.sem_num = 0;
        set.sem_op = -1;
        set.sem_flg = SEM_UNDO;
        semop(id,&set,1);
        printf("get key success\n");
}

void V_operation (int id){  //V操作
        struct sembuf set;
        set.sem_num = 0;
        set.sem_op = 1;
        set.sem_flg = SEM_UNDO;
        semop(id,&set,1);
        printf("put key success\n");
}

int main()
{
        key_t key;
        int semid;

        key = ftok(".",3);
        semid = semget(key,1,IPC_CREAT|0666); //注册信号量

        union semun initsem;
        initsem.val = 0;
        semctl(semid,0,SETVAL,initsem);  //SETVAL设置信号量的值,设置为initsem

        int pid = fork();
        if(pid > 0){
                P_operation (semid);  //拿锁
                printf("this is father\n");
        }
        else if(pid == 0){
                printf("this is child\n");
                V_operation (semid); //放锁
        }
        return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LJX

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值