Linux--进程间通信(IPC)

进程间通信(IPC)

何为IPC?

其实就是进程间的通信,指得是在不同进程之间传播或交换信息。

.IPC的方式通常有管道、消息队列、信号量、共享内存等。

一、管道

管道又包括无名管道命名管道

无名管道

在不指明的情况下说管道,通常指无名管道,它是 UNIX 系统中IPC最古老的形式。

1、特点

它是半双工的,具有固定的读端和写端。
它只能用于具有亲缘关系的进程之间的通信(父子进程或者兄弟进程)
它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。

创建匿名管道
#include<unistd.h>
int pipe(int fds[2]);  //成功返回0,失败返回-1
//当一个管道建立时,它会创建两个文件标识符
fds[0]    //读端      
fds[1]    //写端
//要关闭管道只需将这两个文件描述符关闭即可。

2、原理
这里写图片描述
3、实现代码

//从键盘读取数据写入管道,再从管道读取数据,写到屏幕

#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("pipe"),exit(1);

    while( fgets(buf, 100, stdin)){
        len = strlen(buf);
        if( write(fds[1], buf, len) != len){
            perror("write");
            break;
        }
        memset(buf, 0*00, sizeof(buf));

        if((len = read(fda[0], buf, 100)) == -1){
            perror("read from pipe");
            break;
        }

        if( write(1, buf, len) != len){
            perror("write to stdout");//标准输出
            break;
        }       
    }
}

4、用法

单进程中的管道用处不大。通常调用 fork函数和 pipe 一起,这样就可以用fork来共享管道,如下图所示:
这里写图片描述
上图是父进程读,子进程写,需要关闭父进程的写端(fd[1])和子进程的读端(fd[0])。

实现代码如下:

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

int main( void )
{
    int fds[2];
    if( pipe(fds) == -1)
        perror("pipe"), exit(1);

    pid_t pid = fork();
    if(pid == -1)
        perror("fork"), exit(1);

    if(pid == 0){
        //子进程写入管道
        close(fds[0]);//关闭读端
        sleep(1);
        write(fds[1],"abc",3);
        close(fds[1]);//写完后关闭写端
        exit(0);
    } else{
        close(fds[1]);//关闭父进程的写端
        char buf[100]={};
        int r = read(fds[0], buf, 100);
        if(r == 0)
             printf("read EOF\n");         
        else if(r == -1)
             perror("read"), exit(1);
        else if(r > 0)
             printf("buf=[%s]\n", buf);

        close(fds[0]);
        exit(0);
    }
} 
命名管道(FIFO)

1、特点

它是半双工的,具有固定的读端和写端。
它可以在不相关的进程之间交换数据,这点与无名管道不同。
它可以看成是一种特殊的文件类型。

2、 创建管道文件

1.从命令行创建
mkfifo name    
2.从程序中创建
#include <sys/stat.h>
int mkfifo( const char *name, mode_t mode );
//返回值:成功返回0,出错返回-1
3.打开管道文件
int fd = open(name, O_RDONLY); //只读方式打开
int fd = open(name, O_WRONLY); //只写方式打开
4.读或者写
read / write    用法和匿名管道一样

在数据读出时,FIFO管道中同时清除数据,并且“先进先出”。

2、代码

创建管道文件

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

int main ( void )
{
    int n = mkfifo("1234", 0777);
    if ( n < 0)
        perror("mkfifo"), exit(1);

    printf("create OK\n");
    return 0; 
}
从一个端口输入,另一个端口输出

//write.c 
#include<stdio.h>
#include<stdlib.h>   
#include<fcntl.h>    
#include<sys/stat.h>

int main( )
{
    int fd;
    int  i;
    char buf[1024];
    int n = sizeof( buf );

    if( (fd = open("1234", O_WRONLY)) < 0) 
    {
        perror("Open FIFO Failed");
        exit(1);
    }

    while(1){
        printf("请输入:");
        fgets(buf,1024,stdin);
        if( write(fd, buf, n) < 0) 
        {
            perror("Write FIFO Failed");
            close(fd);
            exit(1);
        }
    }    

    close(fd);  // 关闭FIFO文件
    return 0;
}

//read.c
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<fcntl.h>
#include<sys/stat.h>

int main( )
{
    int fd;
    int len;
    char buf[1024];

    if( (fd = open("1234", O_RDONLY)) < 0 )
    {
        perror("Open FIFO Failed"), exit(1);
    }

    while( (len = read(fd, buf, 1024)) > 0) 
        printf("请输出: %s", buf);

    close(fd);  // 关闭FIFO文件
    return 0;
}

注意:FIFO(命名管道)与pipe(匿名管道)之间唯⼀的区别在它们创建与打开的方式不同,一旦这些⼯工作完成之后,它们具有相同的语义

二、消息队列

消息队列提供了⼀个从⼀个进程向另外⼀个进程发送⼀块数据的⽅法。
每个数据块都被认为是有⼀个类型,接收进程接收的数据块可以有不同的类型值。
消息队列也有管道⼀样的不足,就是每个消息的最大长度是有上限的,每个消息队列的总的字节数是有上限的,系统上消息队列的总数也有一个上限。

1、特点

消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

2、创建

创建消息队列?

int msgget(
          key_t key, //相当于文件名,为一个整数 
          int flag   //IPC_CREAT | 0644 创建 // 0 打开
          );           
返回值:成功返回队列id(当于文件描述符),失败返回-1

//代码
#include<sys/msg.h>
#include<sys/ipc.h>

int main ( void )
{
    int i = msgget(1234 , IPC_CREAT | 0644); 
    if( i == -1 ) 
        perror("msgget"), exit(1);

    return 0; 
}

系统中最多能创建多少个消息队列?

cat /proc/sys/kernel/msgmni

一条消息最多能够装多少个字节?

cat /proc/sys/kernel/msgmax

一个消息队列中所有消息的总字节数?

cat /proc/sys/kernel/msgmnb

往消息队列中发送数据

int  msgsnd(
      int id,           // msgget 的返回值
      const void *msgp, //要发送的消息在哪里 
      size_t len, //消息的字节数,不包括channel的大小
      int flag          // 0
      );
//返回值失败为-1,成功为0

消息存放的结构体:
struct msgbuf{
        long channel;//消息类型,必须>=1,必须long类型
        随便          //写上自己的消息
        };

从消息队列中取数据

ssize_t msgrcv{
            int id,
            void *mssgp,//取出来的消息放在这里
            size_t len, // 装消息地方的大小,不包括类型
            long mtype, //取哪个类型的消息
            int flag    //0
            };

代码:


//往消息队列中发送数据
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/msg.h> 
#include<sys/ipc.h>

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

int main( int argc, char *argv[] )
{
    if( argc != 2){
        fprintf( stderr, "usage:%s type\n", argv[0]);
        exit(1);
}

   int id = msgget(1234, 0);//打开消息队列
   if( id == -1) perror("msgget"), exit(1);

   struct msgbuf mb;//消息队列数据存放的结构体
   memset( &mb, 0*00, sizeof(mb));

   mb.mtype = atoi(argv[1]);//输入数据和消息类型到结构体中
   printf("msg: ");
   fgets(mb.mtext, 999 ,stdin);

  int r = msgsnd(id , &mb, strlen(mb.mtext), 0);//发送消息
  if(r ==-1 ) perror("msgsnd"), exit(1);
}


//从消息队列中取数据     
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/msg.h> 
#include<sys/ipc.h>

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

int main( int argc, char *argv[])
{
    if( argc != 2){
        fprintf( stderr, "usage:%s type\n", argv[0]);
        exit(1);
    }

   int id = msgget(1234, 0);
   if( id == -1) perror("msgget"), exit(1);

   struct msgbuf mb;
   memset( &mb, 0*00, sizeof(mb));

   if( msgrcv(id, &mb, 1000, atoi(argv[1]), 0) == -1)//取数据
         perror("msgrcv"), exit(1);

   printf("%s\n", mb.mtext);
}

测试上述代码时需要同时打开两个终端,在一个输入
这里写图片描述
就可以在另一个得到
这里写图片描述

三、共享内存

不同的地址IP映射了同一块物理内存。

1、特点

共享内存是最快的一种 IPC,因为进程是直接对内存进行操作。
共享内存通常和信号量结合在一起使用,用来对共享内存进行同步的访问。

2、创建

如何创建一个共享内存?

#include <sys/shm.h>
// 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
int shmget(key_t key, 
           size_t size,//共享内存的大小
           int flag  //创建 IPC_CREAT | 0644,打开 0                  
           );

代码:
#include<stdio.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/shm.h>

struct stu{
     int id;
     char name[20];
};

int main( void )
{
int shmid = shmget(1234, sizeof(struct stu), IPC_CREAT | 0644);
if( shmid == -1) perror("shmget"), exit(1);

printf("creat ok\n");  
}

如何将共享内存映射到地址空间?

 void *shmat(id, NULL, 0);
 void *shmat(
    int id,             //shmget的返回值
    const char *shmaddr,// 想让操作系统挂到这个地址空间
                        // NULL 让操作系统自己选择
    int flag            //0
    );

返回值:实际挂载到的虚拟地址的起始地址

卸载共享内存(只是删除映射)

int shmdt(void *shmadr);

代码:

//写数据
#include<stdio.h>
#include<stdlib.h>
#include<sys/msg.h>
#include<sys/ipc.h>
#include<string.h>
#include<assert.h>
#include<unistd.h>

struct stu{
     int id;
     char name[20];
};

int main(void)
{
     int shmid = shmget(1234,sizeof(struct stu),0);//打开共享内存
     if( shmid == -1 ) perror("shmget"), exit(1);

     struct stu *p = (struct stu *)shmat(shmid, NULL,0);//映射
     assert (p!=NULL);

     p->id =1;//赋值
     strcpy(p->name , "余淮");

     sleep(30);
     shmdt(p);//删除映射
}

//读数据
int main(void)
{
    int shmid = shmget(1234,sizeof(struct stu),0);
    if( shmid == -1 ) perror("shmget"), exit(1);

    struct stu *p = (struct stu *)shmat(shmid, NULL,0);
    assert (p!=NULL);

    printf("p->id=%d,p->name=%s\n",p->id,p->name);
    shmdt(p);     
}

删除共享内存

int shmctl(int id,
           int cmd, //IPC_RMID  删除
           NULL);

//代码
#include<stdio.h>
#include<sys/msg.h>
#include<sys/ipc.h>

int main( void )
{
    int id = shmget(1234,0,0);
    shmctl(id, IPC_RMID, 0);
}  

四、信号量

信号量用于实现进程间的互斥与同步。

什么是互斥与同步?
互斥:是指当运行多个任务时且这多个任务都要运行同一个程序片段时,当其中某一个任务A运行到这个程序片段时,其它任务就不能运行这个程序片段,只能等到任务A运行完这个程序片段后才可以运行。
同步:是指当运行多个任务时,它们的运行必须按照规定的次序来运行。
注意:同步并不代表同时运行

临界资源与临界区
临界资源:
各进程采取互斥的方式,实现共享的资源称作临界资源。
临界资源是一次仅允许一个进程使用的共享资源。
临界区:
每个进程中访问临界资源的那段代码称为临界区。
每次只允许一个进程进入临界区,进入后,不允许其他进程进入。

1、特点

信号量的使用需要结合共享内存。
信号量基于操作系统的 PV 操作。

2、使用

创建或打开信号量

int semget(
            key_t key,
            int nsems,  //信号量集中信号量的个数,一般只有一个                        
            int flags,  //打开,0;创建,IPC_CREAT | 0644
          );

设置信号量初值

semctl( id,          //semget的返回值
        int semnum,  //信号量集中的第几个信号量
        int cmd,     // SETVAL
        su           //信号量初值 
      );

union semun{
             int val; //初值
           };

查看信号量的值

semctl( id,
        int semnum,//信号量集中的第几个信号量
        int cmd,   // GETVAL
        0 );
返回值当前信号量的值。

p v 操作

P操作
①将信号量的值减1;
②如果信号量 >= 0,则该进程继续执行;否则该进程置为等待状态。
V操作
①将信号量的值加1;
②该进程继续执行;如果该信号的等待队列中有等待进程就唤醒一等待进程。
这里写图片描述

//调用semop实现P V操作
semop(int semid,          //semget的返回值
      struct sembuf sb[],
      int len);

struct sembuf{
            short sem_num, //信号量的下标
            short sem_op,  //1 v, -1 p
            short sem_flag // 0
            };

//代码:P.c
#include<stdio.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<string.h>

void p(int id)
{
    struct sembuf sb[1];
    sb[0].sem_num = 0;
    sb[0].sem_op = -1;
    sb[0].sem_flg = 0;

    semop(id, sb, 1);
}

int main( void )
{
    int id = semget(1234,0,0);
    if( id == -1) perror("semget"),exit(1);

    p(id);
}

//代码:V.c
#include<stdio.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<string.h>

void v(int id)
{
    struct sembuf sb[1];
    sb[0].sem_num = 0;
    sb[0].sem_op = 1;
    sb[0].sem_flg = 0;

    semop(id, sb, 1);
}

int main( void )
{
    int id = semget(1234,0,0);
    if( id == -1) perror("semget"),exit(1);

    v(id);
}

3、将信号量与共享内存一起使用

//放数据put.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/sem.h>
#include<unistd.h>

void v (int id)
{
    struct sembuf sb1[1] = {0,1,0};
    semop(id, sb1, 1);
}

void p (int id)
{
    struct sembuf sb2[1] = {0,-1,0};
    semop(id, sb2,1);
}

union senum { int val;};

int main ( void )
{
    int shmid = shmget(1234, sizeof(int), IPC_CREAT | 0644);  //创建一个共享内存
    if( shmid == -1) perror("shmget"), exit(1);

    int semid = semget(1234, 1, IPC_CREAT | 0644);//创建型号量1234
    if (semid == -1) perror("semget"), exit(1);

    int semid2 = semget(123, 1, IPC_CREAT | 0644);//创建信号量123
    if (semid2 == -1) perror("semget2"), exit(1);

    //  初始化
    union senum su = {1};
    semctl(semid, 0, SETVAL, su);//设置信号量1234初值

    union senum su2 = {0};
    semctl(semid2, 0, SETVAL, su2);

    int *pv = shmat(shmid, NULL, 0);//将共享内存映射到地址上
    int num = 0;
    while(1) {
        sleep(1);
        *pv = num++;
        p(semid2);
        printf("put: %d\n", *pv);
        v(semid);
    }
}

//取数据 get.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/sem.h>
#include<unistd.h>

void p (int id)
{
    struct sembuf sb1[1] = {0,-1,0};
    semop(id, sb1, 1);
}

void v (int id)
{
    struct sembuf sb2[1] = {0, 1,0};
    semop(id, sb2, 1);
}

int main ( void )
{
    int shmid = shmget(1234,0,0);
    if( shmid == -1) perror("shmget"), exit(1);

    int semid = semget(1234,0,0);//打开信号量1234
    if (semid == -1) perror("semget"), exit(1);

    int semid2 = semget(123, 0,0);//打开型号量123
    if( semid2 == -1) perror("semget2"), exit(1);

    int *pv = shmat(shmid, NULL, 0);//将共享内存映射到地址上
    while(1){
        p(semid);
        printf("get: %d\n", *pv);
        v(semid2);
    }
}

小贴士:

查看IPC对象

ipcs 
ipcs -q   //只查看消息队列
ipcs -m   //共享内存
ipcs -s   //信号量

删除IPC对象

ipcrm -Q key   //删除消息队列
ipcrm -M key 
ipcrm -S key

猜你喜欢

转载自blog.csdn.net/it_is_me_a/article/details/81063277