【linux开发】进程间通信命名管道-共享内存-内存映射-消息队列-信号量

进程间通信命名管道-共享内存-内存映射-消息队列-信号量
 在Unix平台上,建立命名管道是创建了一个fifo文件,和在shell下面用mkfifo命令的效果是一样的。看起来这个管道文件就是一个普通的文件系统瓜挂载点,但是它只不过是作为一个名称存在,实际的内容是一块系统管理的共享内存。这个fifo文件读写端同时处于open状态,才能进行通信。否则,一端open的话,会处于阻塞状态。下面两个例子程序,实现的内容是一样的。父子进程间通过命名管道来通信。当然,如果为了保证读写的顺序,那么就用wait()函数。
1. 最简单的情况:程序输出hello!
> cat r.C
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
int main(void){
  char FIFO[]=".myfifo";
  char msg[8]="hello!\n";
  char buf[8]={0};
  unlink(FIFO);
  mkfifo(FIFO,0666);
  if(fork()>0){
    int fd=open(FIFO,O_WRONLY);
    write(fd,msg,sizeof(msg));
    close(fd);
  }else{//child
    int fd=open(FIFO,O_RDONLY);
    read(fd,buf,sizeof(buf));
    write(STDOUT_FILENO,buf,sizeof(buf));
    close(fd);
  }
  return 0;
}

 
2. 稍微复杂点的情况,用wait()函数保证调用顺序:
注意,wait之前,必须保证两方的open都已经调用成功了。
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/wait.h>
int main(void){
  char FIFO[]=".myfifo";
  char msg[8]="hello!\n";
  char buf[8]={0};
  unlink(FIFO);
  mkfifo(FIFO,0666);
  pid_t pid=fork();
  int status;
  if(pid>0){//father
    int fd=open(FIFO,O_RDONLY);
    wait(&status);
    read(fd,buf,sizeof(buf));
    write(STDOUT_FILENO,buf,sizeof(buf));
    close(fd);
  }else{//child
    int fd=open(FIFO,O_WRONLY);
    write(fd,msg,sizeof(msg));
    close(fd);
  }
  return 0;
}
 
3. 更复杂的情况: 在两个独立的进程之间共享这个命名管道。注意s.C作为子进程r.C作为主进程。
>cat s.C(这个要被编译成./s)


#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/wait.h>
int main(void){
  char FIFO[]=".myfifo";
  char msg[8]="hello!\n";
  int fd=open(FIFO,O_WRONLY);
  write(fd,msg,sizeof(msg));
  close(fd);
  return 0;
}
> cat r.C
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/wait.h>
int main(void){
  char FIFO[]=".myfifo";
  char buf[8]={0};
  unlink(FIFO);
  mkfifo(FIFO,0666);
  pid_t pid=fork();
  int status;
  if(pid>0){//father
    int fd=open(FIFO,O_RDONLY);
    wait(&status);
    read(fd,buf,sizeof(buf));
    write(STDOUT_FILENO,buf,sizeof(buf));
    close(fd);
  }else{//child
    execl("./s",NULL);
  }
  return 0;
}

 
输出结果仍然是hello!

4. 如果不用命名管道,用共享内存或者内存映射的方式,效果是一样的,因为管道本身就是一种共享内存。共享内存是系统管理的不需要经过文件,内存映射则必须创建一个中间文件。相比较而言,Windows平台上面共享内存和内存映射是一个函数CreateFileMapping的不同参数而已。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<sys/shm.h>
#include<sys/ipc.h>
#include<sys/mman.h>
struct p{
  int age;
  int height;
};
int main(void){
  key_t key=1234;
  int status;
  int shmfd=shmget(key,sizeof(p),0666|IPC_CREAT);
  if(shmfd==-1)exit(1);
  p* pt=(p*)shmat(shmfd,(void*)0,0);
  if(pt==(void*)-1)exit(2);
  int fd=open("my.txt",O_CREAT|O_RDWR|O_TRUNC,00777);
  p* pm=(p*)mmap(NULL,sizeof(p),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
  pid_t pid=fork();
  if(pid>0){//father
    wait(&status);
    printf("%d,%d\n",pt->age,pt->height);
    shmdt((char*)pt);
    shmctl(key,IPC_RMID,0);
    printf("%d,%d\n",pm->age,pm->height);
    msync((char*)pm,sizeof(p),MS_SYNC);
    munmap((char*)pm,sizeof(p));
    close(fd);
  }else{//child
    pt->age=20;
    pt->height=180;
    shmdt((char*)pt);
    pm->age=30;
    pm->height=170;
  }
  return 0;
}

 
程序运行的结果是,父进程读取了子进程对共享内存的写入内容,输出:
20 180
30 170

5. 消息队列的用法也基本一样。


#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<sys/ipc.h>
#include<sys/msg.h>
struct p{
  int age;
  int height;
};
int main(void){
  int msgfd=msgget(IPC_PRIVATE,0700);
  if(msgfd<0)exit(1);
  p pbuf={20,180};
  int status;
  pid_t pid=fork();
  if(pid>0){//father

    wait(&status);
    p rev;
    msgrcv(msgfd,&rev,sizeof(p),0,IPC_NOWAIT);
    printf("%d %d\n",rev.age,rev.height);
    msgctl(msgfd,IPC_RMID,NULL);
  }else{//child

    msgsnd(msgfd,&pbuf,sizeof(p),IPC_NOWAIT);
  }
  return 0;
}

 
程序输出
20 180
6、信号量
这里介绍的信号量函数要比前面介绍的用于线程的信号量函数要更加通用,要想编写通用的代码以确保程序对某个特定的资源具有独占式的访问权是非常困难的,我们前面见过的一种可能的解决方法是使用带O_EXCL标志的open函数来创建锁文件,他提供了原子化的文件创建办法。荷兰科学家提出的信号量概念是在并发变成领域迈出的重要一步,如前面所讨论的,信号量是一个特殊的变量,他只取正整数值,并且程序对其访问都是原子操作。
信号量的一个更正式的定义:一个特殊变量,他值允许对他进行等待和发送信号这两个操作,
P(信号量变量):用于等待
V (信号量变量) :用于发送信号
假设有一个信号量sv,则
P(sv)如果SV的值大于0,就减去一,如果sv等于0就挂起这个进程的执行
V(sv)如果有其他进程因等待sc而被挂起,就让他恢复运行,如果没有进程因等待sv而被挂起就给他加1
linux的信号量机制:
#include <sys/sem.h>
int semctl(int sem_id, int sem_num, int command, ...);
int semget(key_t key, int num_sems, int sem_flags);
int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);
semget函数的作用是创建一个新信号量或获得一个已有信号量的键,第一个参数key是整数值,不相关的进程可以通过它访问同一个信号量,程序对所以信号量的访问都是间接的,他提供一个键再有系统生成一个相应的信号量标示符,只有semget函数才直接使用key,所有其他信号量函数都是使用由semget函数返回的信号量标示符
There is a special semaphore key value, IPC_PRIVATE, that is intended to create a semaphore that only the creating process could access, but this rarely has any useful purpose. You should provide a unique,non-zero integer value for key when you want to create a new semaphore
num_sems参数指定需要的信号量数目,他几乎总是取值为1.
sem_flags是一组标志,他与open函数的标志非常相似,低端的9个bit是信号量的权限其作用类似于文件访问权限,还可以和值IPC_CREAT做按位或操作来创建一个新信号量。
semget函数在成功时返回一个正数,他就是其他信号量函数将用到的信号量标示符,失败返回-1
semop函数用于改变信号量的值,第一个参数sem_id是由semget返回的信号量标示符,第二个参数sem_ops是指向一个结构体数组的指针,每个数组元素至少包含以下几个成员:
struct sembuf {
short sem_num;
short sem_op;
short sem_flg;
}

第一个成员sem_num是信号量编号,除非你需要使用一组信号量,否则他一般取值0.sem_op的值是一次操作中需要改变的数值,通常只会用到两个值一个是-1他等待信号量可用,也就是p操作,一个是+1也就是v操作他发送信号量表示信号现在可用,最后一个成员sem_flg通常被设置为SEM_UNDO,他将使得操作系统跟踪当前进程对这个信号量的修改情况,如果这个进程在没有释放信号量的情况下终止,操作系统将自动释放该进程持有的信号量。
semop函数的调用时的一切动作时一次性完成的,这是为了避免出现因使用多个信号量而可能发生的竞争现象。
semctl函数用来直接控制信号量信息,第一个参数sem_id是由semget返回的信号量标示符。sem_num参数是信号量编号,当需要用到成组的信号量时,就要用到这个参数,他一般取值为0,表示这是第一个也是唯一的一个信号量。command参数将要采取的动作,如果还有第四个参数,如果还有第四个参数它将会使一个union semun结构
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
}
semctl函数中的command参数可以设置许多不同的值,但只有下面介绍的两个值最常用。
SETVAL: Used for initializing a semaphore to a known value. The value required is passed as the
val member of the union semun. This is required to set the semaphore up before it’s used for
the first time.
IPC_RMID: Used for deleting a semaphore identifier when it’s no longer required.

semctl函数将根据command参数的不同而返回不同的值,对于SETVAL和IPC_RMID成功时返回0,失败时返回-1
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/sem.h>
#include "semun.h"
static int set_semvalue(void);
static void del_semvalue(void);
static int semaphore_p(void);
static int semaphore_v(void);
static int sem_id;
int main(int argc, char *argv[])
{
int i;
int pause_time;
char op_char = '0';
srand((unsigned int)getpid());
sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);
if (argc > 1) {

if (!set_semvalue()) {
fprintf(stderr, "Failed to initialize semaphore\n");
exit(EXIT_FAILURE);
}
op_char = 'X';
sleep(2);
}

for(i = 0; i < 10; i++) {
if (!semaphore_p()) exit(EXIT_FAILURE);
printf("%c", op_char);fflush(stdout);
pause_time = rand() % 3;
sleep(pause_time);
printf("%c", op_char);fflush(stdout);

if (!semaphore_v()) exit(EXIT_FAILURE);
pause_time = rand() % 2;
sleep(pause_time);
}
printf("\n%d - finished\n", getpid());
if (argc > 1) {
sleep(10);
del_semvalue();
}
exit(EXIT_SUCCESS);
}

static int set_semvalue(void)
{
union semun sem_union;
sem_union.val = 1;
if (semctl(sem_id, 0, SETVAL, sem_union) == -1) return(0);
return(1);
}

static void del_semvalue(void)
{
union semun sem_union;
if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
fprintf(stderr, "Failed to delete semaphore\n");
}

static int semaphore_p(void)
{
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = -1;
sem_b.sem_flg = SEM_UNDO;
if (semop(sem_id, &sem_b, 1) == -1) {
fprintf(stderr, "semaphore_p failed\n");
return(0);
}
return(1);
}

static int semaphore_v(void)
{
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = 1;
sem_b.sem_flg = SEM_UNDO;
if (semop(sem_id, &sem_b, 1) == -1) {
fprintf(stderr, "semaphore_v failed\n");
return(0);
}
return(1);
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值