读进程和写进程同步设计_Linux笔记(16)| 进程同步机制——管道和IPC

今天要分享的是Linux进程的同步机制,包括管道和IPC。之前学习的信号也有控制进程同步的作用,但是信号仅仅传输很少的信息,而且系统开销大,所以这里再介绍几种其他的进程同步机制。在之前的一篇文章中有提到相关内容,但是当时没有详细展开,可以回顾一下:Linux笔记(10)| 进程概述。 一、管道(无名管道) 一般说管道都是指无名 管道(也称匿名管道),有名管道(也称命名管道)不会直接说管道,肯定会加上前缀。管道是Linux下最常见的进程间的通信方式之一,它是在两个进程之间实现一个数据流通的通道。它有以下特点:1、管道一般是半双工的,数据只能向一个方向流动。2、通常只能在父子进程或者兄弟进程之间使用。3、管道是一种特殊的文件,并且只存在于内存当中……接下来说一下管道的使用:创建管道
int pipe(int pipefd[2]);
输入参数是一个数组,实际上这个输入参数是当做输出来用的,调用这个函数,成功的话数组里就会保存两个文件描述符,并且返回0,失败返回-1.其中文件描述符fd[0]是读端,fd[1]是写端,这是固定不变的。刚刚我们也说了,创建管道肯定是为了父子进程或者兄弟进程之间通信的,单独在一个进程里面使用管道毫无意义。所以父进程创建好管道之后,再调用fork函数创建子进程,子进程就会继承那个管道,于是父子间可以约定谁来读,谁来写。因为是半双工通信,所以只能一个读,一个写,如果想要两端都能读写,那就要创建两个管道。比如父进程读,子进程写,那么父进程可以先close(fd[1]),然后read(fd[0]),子进程先close(fd[0]),然后write(fd[1]).读写的时候只要把他当做普通的文件就行了,和普通的文件描述符的读写一样,但是有一点不一样的是普通文件读完了数据还在,而管道读完之后数据就没了。 二、有名管道管道只能在有亲缘关系的进程之间实现通信,但是有名管道可以在任何两个进程之间实现通信。有名管道严格遵循先进先出的规则,不支持lseek函数等文件定位操作。首先创建一个有名管道:
int mkfifo(const char*pathname,mode_t mode);
pathname参数是一个普通的路径名,也就是创建后有名管道文件的名字,mode参数是文件的操作权限,调用成功返回0,调用失败返回-1.接下来就可以使用open或者fopen函数打开刚刚创建的有名管道文件,对其进行读写操作了。 三、System V IPC机制IPC机制由消息队列、信号量以及共享内存三种具体实现方法组成。首先要了解两个概念, 标识符关键字。每一个IPC结构(消息队列或者信号量或者共享内存)都有一个标识符,这是一个非负整数,每创建一个IPC结构,相应的标识符就会加1,这个标识符在相同的结构中是唯一的,也就是说,如果“666”是某个消息队列的标识符,那么肯定不会有第二个消息队列的标识符也是“666”。但是需要注意的是,可能某个共享内存的标识符也是“666”。 于是,还得用一个东西来区分,那就是关键字了 。所以,根据关键字和标识符可以唯一确定一个IPC结构。 IPC的关键字一般可以使用IPC_PRIVATE,也可以使用ftok函数获得,他们有一些区别,后面会提到。 ftok函数的使用:
key_t ftok(const char* pathname,int proj_id);
ftok函数是用于将一个路径和项目ID转换为关键字,第一个参数必须是一个存在的、可以访问的文件路径名,第二个参数 项目ID只有低八位有效。 在创建一个IPC对象的时候,他们有一些共同的特点: 我们先来看一下IPC对象创建函数: 1、创建消息队列
int msgget(key_t key,int msgflg);
2、创建信号量
int semget(key_t key,int nsems,int semflg);
3、创建共享内存
int shmget(key_t ,size_t size,int shmflg);
可以发现,他们都有一个关键字key,都有一个flag参数。在使用上,也有一些共同的特点:当key使用IPC_PRIVATE时,操作系统保证创建一个唯一的IPC对象,此时flag参数仅决定对象的存取权限。当key使用ftok函数得到的关键字时,flag参数不仅决定对象的存取权限,还和创建方式有关,具体就是:设置flag参数的IPC_CREAT位,但不设置IPC_EXCL位,如果不存在指定key的IPC对象,就创建,如果存在,就返回该对象。同时设置 IPC_C REAT位和 IPC_ EXCL位,如果对象不存在就创建,如果已经存在,则返回错误。这和文件操作函数open是类似的。接下来介绍一下各个IPC对象涉及到的API函数。由于具体的细节比较多,这里就不一一罗列了,可以使用man手册来查看具体细节。1、消息队列创建:用来创建或者打开一个消息队列
int msgget(key_t key,int msgflg);
控制:其中cmd命令参数可以获取或者设置buf里的内容,也可以删除这个消息队列(看具体的参数设置)
int msgctl(int msqid,int cmd,struct msqid_ds *buf);
发送:向消息队列里发送消息
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);
2、信号量创建: 用来创建或者打开一个信号量
int semget(key_t key, int nsems, int semflg);
操作:可以申请或者释放信号量。
int semop(int semid, struct sembuf *sops, size_t nsops);
控制:可以设置或返回信号量的值,可以删除信号量
int semctl(int semid, int semnum, int cmd, ...);
3、共享内存创建:用来创建或者打开一个共享内存。
int shmget(key_t key, size_t size, int shmflg);
连接:将共享内存附加到进程里的地址空间(有点像映射)
void *shmat(int shmid, const void *shmaddr, int shmflg);
脱离:将共享内存和进程地址空间脱离,但不删除共享内存本身
void *shmat(int shmid, const void *shmaddr, int shmflg);
设置属性:可以设置或者返回buf里的内容,也可以删除共享内存。
 int shmctl(int shmid, int cmd, struct shmid_ds *buf);
接下来写一段代码,需求是:创建一个共享内存,子进程写入数据,父进程读出数据,同时使用信号量来对读写进行保护
#include #include #include #include #include #include #include #include #define  SHMSIZE  128int main(int argc,char* argv[]){  if(argc !=2){    printf("please input param\n");    exit(0);  }  key_t semkey,shmkey;  int semid,shmid,pid;  char* shmp;  struct sembuf locksembuf={0,-1,SEM_UNDO};  struct sembuf unlocksembuf={0,1,SEM_UNDO|IPC_NOWAIT};  struct shmid_ds shmdsbuff;  semkey=ftok(argv[1],0);  if(semkey==-1){    printf("semkey ftok failed\n");    exit(0);  }  shmkey=ftok(argv[1],1);  if(semkey==-1){    printf("shmkey ftok failed\n");    exit(0);  }  semid=semget(semkey,1,IPC_CREAT | 0666);        //创建信号量  if(semid==-1){    perror("semget");    exit(0);  }  shmid=shmget(shmkey,SHMSIZE,0666 | IPC_CREAT );      //创建共享内存  if(shmid==-1)      {    perror("shmget");    exit(0);  }  printf("creat share memory success\n");  shmp=shmat(shmid,NULL,0);                             //连接共享内存(映射地址)  if((long)shmp==-1)  {     perror("shmat failed\n");    exit(0);  }  pid=fork();  if(pid<0)  {    perror("fork failed\n");    exit(0);  }    else if(pid==0)                              //子进程  {    printf("creat child process success\n");    if(semctl(semid,0,SETVAL,1)<0)             //初始化信号量为1    {      perror("semctl");      exit(0);    }    if(semop(semid,&locksembuf,1)==-1)          //申请资源    {      perror("semop");      exit(0);    }    sleep(4);    strcpy(shmp,"hello world\n");    if(shmdt((void*)shmp)<0)        //使共享内存脱离进程地址空间    {      perror("shmdt");      exit(0);    }    if(semop(semid,&unlocksembuf,1)<0)      //解锁临界资源    {      perror("child process unlock semop");      exit(0);    }  }  else                                      //父进程  {    printf("This is parent process\n");    sleep(1);    int ret=semop(semid,&locksembuf,1);    if(ret<0)    {      perror("parent process semop lock");      exit(0);    }    if(shmctl(shmid,IPC_STAT,&shmdsbuff)<0)      //父进程获取共享内存的信息    {      perror("shmctl");      exit(0);    }        else{      printf("get shm success\n");      printf("Shared Memory Infomation:\n");      printf("\tCreator PID:%d\n",shmdsbuff.shm_cpid);      printf("\tSize(bytes):%ld\n",shmdsbuff.shm_segsz);      printf("\tLast Operator PID :%d\n",shmdsbuff.shm_lpid);      printf("Receive message:%s\n",(char*)shmp);    }    if(shmdt((void*)shmp)<0)    {      perror("parent process shmdt");      exit(0);    }    ret=semop(semid,&unlocksembuf,1);    if(ret<0)    {      perror("parent process semop");      exit(0);    }    if(shmctl(shmid,IPC_RMID,NULL)<0)    {      perror("shmctl RMID");      exit(0);    }    if(semctl(semid,0,IPC_RMID,NULL)<0)    {      perror("semctl RMID");      exit(0);    }  }  return 0;}
接下来编译执行
gcc sem.c./a.out a.txt      //注意,当前目录下需要有一个a.txt,其他文件也行
输出:

b265363ea1392bac7081326e94dafd62.png

以上就是今天的内容。

4f88edeadbf9977d329dc34b0917cb58.gif

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值