进程间的通信

  • 匿名管道
      
int pipe(int fds[]); 
//失败的返回值是1
//fds[0]从管道里读
//fds[1]从管道里写

举一个例子:(子进程往管道里写,父进程从管道里拿)

#include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 #include<error.h>
  6 
  7 int main()
  8 {
  9     int fds[2];
 10     if(pipe(fds) == -1)
 11     {
 12 
 13         perror("pipe");
 14         exit(1);
 15     }
 16 
 17    pid_t pid = fork();
 18 
 19    
 20    if(pid == 0)//让子进程写
 21    {
 22         //子进程
 23         close(fds[0]);//关闭读端
 24         write(fds[1],"你好\n",5);
 25         close(fds[1]);
26         exit(0);
 27 
 28    }
 29    else
 30        if(pid > 0)//父进程读
 31        {
 32            //父进程
 33            close(fds[1]);
 34            char buf[100] = {};
 35            int r = read(fds[0],buf,100);
 36            if(r == 0)
 37            {
 38                printf("read EOF\n");
 39             }
 40             else
 41                 if(r == -1)  //读失败
 42             {
 43                 perror("read");
 44                 exit(1);
 45             }
 46             if(r > 0)
 47             {
 48                 printf("buf = %s",buf);
 49 
 50             }
 51             close(fds[0]);
 52             exit(0);
 53         }
 54        else
 55            if(pid < 0)
 56                {
 57                     perror("fork");
 58                     exit(1);
 59                }
 60 
 61 
 62 }                                            
  • 命名管道
      由于匿名管道只能是有血缘关系的进程使用这个管道。为了能让没有血缘关系的进程使用同一个管道,所以就要用到命名管道。
创建管道的命令:mkfifo name.p   //name 是要创建管道的名字
在程序中如何创建管道:
int mkfifo(const char *name,mode_t mode);
//name 是管道的名字
//mode 是管道文件的权限,如0666,0644,等
返回值:失败返回-1
打开管道文件:
读:  int fd = open(name,O_RDONLY);
写:  int fd = open(name,O_WRONLY);

管道内核的缓存大小是65536
往管道每次写不能超过4096

  • 消息队列

创建消息队列:

创建消息队列: msgget(key_t key,int flag)
//key_t key  相当于文件名 
//int flag   打开:0,创建:IPC_CREAT | 0644  
返回值:消息队列的id,相当于文件描述符     

往消息队列里发送消息:

int msgsnd(int id,const viod *msgp,size_t len,int flag);
//int id      mesget 返回的值
 /void *msgp  要发送的消息在哪里
//size_t len  发送消息的大小
//int flag    0
返回值:成功返回0,失败返回-1
写消息队列的时候,这个结构体要自己定义:
struct msgbuf{
    long channel; //消息类型(通道号),必须 >=1
    char mtext[100]; //自己的消息的结构,啥样都可以
}
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<sys/ipc.h>
  4 #include<sys/msg.h>
  5 #include<string.h>
  6 
  7 struct msgbuf
  8 {
  9     long mtype;    //消息类型(通道号),必须 >=1
 10     char mtext[1000];  //自己的消息的结构,啥样都可以
 11 };
 12 
 13 int main(int argc,char *argv[])
 14 {
 15     // ./msgsnd  type    要输入两个参数,这个文件的执行程序 ,第二个是消息队列的通道号
 16     if(argc != 2)
 17     {
 18         fprintf(stderr,"usage:%s type\n",argv[0]);
 19         exit(1);
 20     }
 21 
 22     int id = msgget(1234,0); //获得要发送到的那个消息队列的id
 23     if(id == -1)
 24     {
 25         perror("msgget");
 26         exit(1);
 27     }
 28     struct msgbuf mb;
 29     memset(&mb,strlen(mb.mtext),0); 
 30 
 31     mb.mtype = atoi(argv[1]);    //把字符型的消息队列通道号转换为整型
 32     printf("msg:");
 33     fgets(mb.mtext,999,stdin);   //从标准输入里获取要写入的内容
 34 
 35     int r = msgsnd(id,&mb,strlen(mb.mtext),0);   //往消息队列里发内容
 36     if( r == -1)
 37     {
 38         perror("msgsnd");
 39         exit(1);
 40     }
 41 
 42 }

从消息队列里取消息:

ssize_t msgrcv(int id,void *mssgp,size_t len,long mtype,int flag);
//int id  消息队列的id
//void *mssgp  取出来的消息放在那里
//size_t len   装消息地方的大小,不包括类型
//long mtype  //取哪个消息的类型(通道号)
//int flag  0(有消息我就读,没消息我就等)
 1 
  2 #include<stdio.h>
  3 #include<stdlib.h>
  4 #include<sys/ipc.h>
  5 #include<sys/msg.h>
  6 #include<string.h>
  7 
  8 struct msgbuf
  9 {
 10     long mtype;
 11     char mtext[1000];
 12 };
 13 
 14 int main(int argc,char *argv[])
 15 {
 16     // ./msgsnd  type  //第一个取消息这个执行程序,第二个消息通道
 17     if(argc != 2)
 18     {
 19         fprintf(stderr,"usage:%s type\n",argv[0]);
 20         exit(1);
 21     }
 22 
 23     int id = msgget(1234,0);   //获取这个消息队列的id
 24     if(id == -1)
 25     {
 26         perror("msgget");
 27         exit(1);
 28     }
 29     struct msgbuf mb;
 30     memset(&mb,0x00,sizeof(mb)); 
 31 
 32     if(msgrcv(id,&mb,1000,atoi(argv[1]),0) == -1)
 33     {
 34         perror("msgrcv");
 35         exit(1);
 36     }
 37 
 38     printf("%s\n",mb.mtext);
 39 }
  • 共享内存
    特点:
    1.双向通信
    2.用于随意进程
    1. 不存在“面向字节流”或者“面向数据快”的概念,其实只是一块内存,可以随意的在上面读写数据
    2. 没有同步互斥
    3. 生命周期随内核

创建或打开共享内存:

shmget(key_t key,size_t size,int flag);
//key_t key  相当于文件名 
//size_t size 共享内存段的大小
//int flag  创建:IPC_CREAT | 0644 打开:0 
 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<sys/ipc.h>
  4 #include<sys/shm.h>
  5 
  6 struct stu
  7 {
  8     int id;
  9     char name[20];
 10 };
 11 
 12 int main(void)
 13 {
 14     int shmid = shmget(1234,sizeof(struct stu),IPC_CREAT | 0644);
 15 
 16     if(shmid == -1)
 17     {
 18         perror("shmget");
 19         exit(1);
 20     }
 21 
 22     printf("creat shm ok\n");
 23 
 24 }

让共享内存和本进程建立关系(挂载:attach):

shmad(int id,const char *shmadder,int flag);
//int id    共享内存的id
//const char *shmaddr   想让操作系统挂载到这个地址空间,若写NULL ,则让操作系统自己选择
//int flag)   0
 返回值: 实际挂载到的虚拟地址的起始位置

卸载掉共享内存:

int shmdt(void *shmadder);
//只是和本进程没有关了,并没有删除

删除共享内存:

int shmctl(int id,int cmd,NULL);
//int cmd  ------IPC_RMID  删除共享内存
  • 信号量
    信号量是计数器
    信号量是用于信号的同步和互斥
    PV操作:申请资源、释放资源
    原语
    PV操作
    上锁– :P
    解锁++ :V
    创建或打开信号量:
int semget(key_t key,int names,int flags);
// int nsems  ----------信号量集中信号量的个数
//int flags -----打开0,创建IPC_CREAT | 0644

信号量的销毁:

semctl(snmid,0,IPC_EMID);

设置信号量初值:

semctl(int semid,int semnum,int cmd,su);
//int semnu  信号量集中的第几个信号量
//int cmd    ---SETVAL
//su         ---信号量初值
union semun
{
    int val;  //value for SETVAL
}

查看信号量的值:

int semctl(semid,int semnum,int cmd,0)
//int semnum -----信号量集中的第几个信号量
//int cmd,   -----GETVAL

PV操作:

semop(int semid,struct sembuf sb[],int len);
struct sembuf{
    short sem_num,   //信号量的下标
    short sem_op,     //1 V操作, -1 P操作
    short sem_flg,     //0,若取SEM_UNDO--------进程结束后把申请的信号量给你还原,IPC_NOWAIT -------- 进程不会挂起,P操作也不会成功
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<sys/ipc.h>
  4 #include<sys/sem.h>
  5 #include<unistd.h>
  6 
  7 
  8 union semun
  9 {
 10     int val;
 11 };
 12 
 13 void P(int semid)  //P操作
 14 {
 15     struct sembuf buf[1];
 16     buf[0].sem_num = 0;
 17     buf[0].sem_op = -1;
 18     buf[0].sem_flg = 0;
 19 
 20    int ret =  semop(semid,buf,1);
 21    if(ret<0)
 22    {
 23        perror("P 失败"),
 24        exit(1);
 25    }
 26 }
 27 
 28 void V(int semid)   //V操作
 29 {
 30     struct sembuf buf[1];
 31     buf[0].sem_num = 0;
 32     buf[0].sem_op = 1;
 33     buf[0].sem_flg = 0;
 34 
 35     int ret = semop(semid,buf,1);
 36     if(ret < 0) perror("V 失败"),exit(1);
 37 }
 38 
 39 int main()
 40 {
 41     int semid = semget(1234,1,IPC_CREAT|0666);  //创建信号量
 42     if(semid < 0) perror("semget fail"),exit(1);
 43 
 44     union semun su;
 45     su.val = 1;     //给信号量赋值为1
 46     semctl(semid,0,SETVAL,su);
 47 
 48     pid_t pid = fork();
 49     if(pid > 0)
 50     {
 51         //父进程
 52         while(1)
 53         {
 54 
 55           P(semid);  //-1
 56           printf("A");
 57           fflush(stdout);
 58           usleep(12324);
 59           printf("A  ");
 60           fflush(stdout);
 61           usleep(323423);
 62           V(semid);  //+1
 63         }
 64 
 65 
 66     }
 67     else
 68         if(pid == 0)
 69         {
 70             //子进程
 71             while(1)
 72             {
 73 
 74                 P(semid);  //-1
 75                 printf("B");
 76                 fflush(stdout);
 77                 usleep(12324);
 78                 printf("B  ");
 79                 fflush(stdout);
 80                 usleep(323423);
 81                 V(semid);  //+1
 82             }
 83         }
 84         else
 85         {
 86             perror("fork");
 87             exit(1);
 88         }
 89 }
  • 进程互斥

由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥。系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源。在进程中涉及到互斥资源的程序段叫临界区。互斥是一种独占关系,如任一时刻,进程1和进程2只能有一个写文件C。

  • 进程同步

是指多个进程需要相互配合共同完成一项任务。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源,如“第一类读写者模型”。同步是一种时序关系,如规定了进程1处理完事情A后,进程2才能处理事情B。

如果A和B互相包含,那A只能等于B,而同步和互斥的是不对等的。但从广义上来看,互斥是一种特殊的同步,同步是一种更为复杂的互斥关系。

  • 临界资源

系统中同时存在有许多进程,他们共享各种资源,然而有许多资源在某一时刻只能允许一个进程使用。例如打印机,磁带机等硬件设备和变量,队列等数据结构,如果有多个进程同时去使用这些资源就会造成混乱。因此必须保护这些资源,避免两个进程同时访问这类资源。我们把某段时间只能允许一个进程使用的资源成为临界资源。

  • 生产者与消费者

有两个进程分别为消费者进程和生产者进程,对同一个临界资源进行访问,生产者不断的将生产的产品加入缓冲区,而消费者不断的消费缓冲区中的资源,利用信号量实现两个进程的同步与互斥。

在生产者往缓冲区加入产品时,需要两个信号量,一个是互斥信号量,使得在生产者往缓存里放产品的时候其他进程不能对临界资源进行操作,另一个信号量是指示缓存区是否已满的信号量,从而判断生产者能否往缓冲区加入产品;而消费者从缓冲区中消费资源的时候,也需要两个信号量,一个信号量是互斥信号量,使得消费者在从缓冲区中取产品的时候其他进程不能对临界资源进行操作,另外一个信号量是指示缓冲区是否为空,从而判断消费者能否对缓冲区进行操作。

由以上分析,可知在该问题中共需要三个信号量,一个是用于互斥的信号量mutux=1; 一个是用于指示缓冲区空位置数目的empty=N;另外一个是指示缓冲区填满位置的信号量full = 0;

用于同步的信号量一定是位于不同进程中,用于互斥的信号量一定是位于同一个进程中。

实验题目: 生产者与消费者(综合性实验) 实验环境: C语言编译器 实验内容: ① 由用户指定要产生的进程及其类别,存入进入就绪队列。    ② 调度程序从就绪队列中提取一个就绪进程运行。如果申请的资源被阻塞则进入相应的等待队列,调度程序调度就绪队列中的下一个进程。进程运行结束时,会检查对应的等待队列,激活队列中的进程进入就绪队列。运行结束的进程进入over链表。重复这一过程直至就绪队列为空。    ③ 程序询问是否要继续?如果要转直①开始执行,否则退出程序。 实验目的: 通过实验模拟生产者与消费者之间的关系,了解并掌握他们之间的关系及其原理。由此增加对进程同步的问题的了解。 实验要求: 每个进程有一个进程控制块(PCB)表示。进程控制块可以包含如下信息:进程类型标号、进程系统号、进程状态、进程产品(字符)、进程链指针等等。 系统开辟了一个缓冲区,大小由buffersize指定。 程序中有三个链队列,一个链表。一个就绪队列(ready),两个等待队列:生产者等待队列(producer);消费者队列(consumer)。一个链表(over),用于收集已经运行结束的进程 本程序通过函数模拟信号量的操作。 参考书目: 1)徐甲同等编,计算机操作系统教程,西安电子科技大学出版社 2)Andrew S. Tanenbaum著,陈向群,马红兵译. 现代操作系统(第2版). 机械工业出版社 3)Abranham Silberschatz, Peter Baer Galvin, Greg Gagne著. 郑扣根译. 操作系统概念(第2版). 高等教育出版社 4)张尧学编著. 计算机操作系统教程(第2版)习题解答与实验指导. 清华大学出版社 实验报告要求: (1) 每位同学交一份电子版本的实验报告,上传到202.204.125.21服务器中。 (2) 文件名格式为班级、学号加上个人姓名,例如: 电子04-1-040824101**.doc   表示电子04-1班学号为040824101号的**同学的实验报告。 (3) 实验报告内容的开始处要列出实验的目的,实验环境、实验内容等的说明,报告中要附上程序代码,并对实验过程进行说明。 基本数据结构: PCB* readyhead=NULL, * readytail=NULL; // 就绪队列 PCB* consumerhead=NULL, * consumertail=NULL; // 消费者队列 PCB* producerhead=NULL, * producertail=NULL; // 生产者队列 over=(PCB*)malloc(sizeof(PCB)); // over链表 int productnum=0; //产品数量 int full=0, empty=buffersize; // semaphore char buffer[buffersize]; // 缓冲区 int bufferpoint=0; // 缓冲区指针 struct pcb { /* 定义进程控制块PCB */ int flag; // flag=1 denote producer; flag=2 denote consumer; int numlabel; char product; char state; struct pcb * processlink; …… }; processproc( )--- 给PCB分配内存。产生相应的的进程:输入1为生产者进程;输入2为消费者进程,并把这些进程放入就绪队列中。 waitempty( )--- 如果缓冲区满,该进程进入生产者等待队列;linkqueue(exe,&producertail); // 把就绪队列里的进程放入生产者队列的尾部 void signalempty() bool waitfull() void signalfull() void producerrun() void comsuerrun() void main() { processproc(); element=hasElement(readyhead); while(element){ exe=getq(readyhead,&readytail); printf("进程%d申请运行,它是一个",exe->numlabel); exe->flag==1? printf("生产者\n"):printf("消费者\n"); if(exe->flag==1) producerrun();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值