Linux系统程序设计--7.进程间的通信

进程间通信概述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
总共有7种文件类型

在这里插入图片描述

管道通信

在这里插入图片描述
在这里插入图片描述

管道的分类

在这里插入图片描述

管道的分类

在这里插入图片描述
在这里插入图片描述

案例1

在这里插入图片描述

父进程创建的管道fd[0],fd[1],当fork一个子进程之后,子进程也会有这个fd[0],fd[1]。所以子进程和父进程只有操作一个,另外一个关闭

#include <stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
    int fd[2];
    // 创建管道
    if(pipe(fd)<0)
    {
        perror("pipi error");
        exit(1);
    }
    pid_t pid;
    if((pid=fork())<0)
    {
        perror("fork error");
        exit(1);
    }
    else if(pid>0) 
    {
        close(fd[0]); // 父进程用来写入数据
        int start = 1,end = 100;
        // 往管道中写入数据
        if(write(fd[1],&start,sizeof(int))!=sizeof(int))
        {
            perror("write error");
            exit(1);
        }
        if(write(fd[1],&end,sizeof(int))!=sizeof(int))
        {
            perror("write error");
            exit(1);
        }
        close(fd[1]);
        wait(0); //回收子进程
    }
    else
    {
        close(fd[1]); // 子进程用来读取数据i
        int start,end;
        if(read(fd[0],&start,sizeof(int))!=sizeof(int))
        {
            perror("read error");
            exit(1);
        }
        if(read(fd[0],&end,sizeof(int))!=sizeof(int))
        {
            perror("read error");
            exit(1);
        }
        close(fd[0]);
        printf("child process read start %d,end %d\n",start,end);
    }
    printf("Hello world\n");
    return 0;
}

案例2

在这里插入图片描述

在这里插入图片描述

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

char *cmd1[3] = {"/bin/cat","/etc/passwd",NULL};
char *cmd2[3] = {"/bin/grep","root",NULL};
int main()
{
    int fd[2];
    if(pipe(fd)<0)
    {
        perror("pipe error");
        exit(1);
    }
    pid_t pid;
    for(int i = 0;i<2;i++)
    {
        pid = fork();
        if(pid<0)
        {
            perror("fork error");
            exit(1);
        }
        else if(pid==0) //son process
        {
            if(i==0) // 第一个子进程,负责写入数据
            {
                // 关闭读端
                close(fd[0]);
                /*
                 *将标准输出重定向到管道的写端
                 下面命令执行结果会写入到管道中
                 而不是输出到屏幕
                 * */
                if(dup2(fd[1],STDOUT_FILENO)!=STDOUT_FILENO)
                {
                    perror("dup2 error");
                }                
                // 由于上面已经复制了一份fd[1],所以可以关闭fdp[1]
                close(fd[1]);
                // 调用exec函数执行cat命令
                if(execvp(cmd1[0],cmd1)<0)
                {
                    perror("execvp error");
                    exit(1);
                }
                break; // 注意这里是进程扇
            }
            if(i == 1) // 第二个子进程,负责写入数据
            {
                // 关闭写端
                close(fd[1]);

                /*
                 *将标准输入重定向到管道的读端
                 下面命令grep的执行是从管道的读端开始
                 读取内容,而不是标准输入读取
                 * */
                if(dup2(fd[0],STDIN_FILENO)!=STDIN_FILENO)
                {
                    perror("dup2 error");
                }
                close(fd[0]);
                // 调用exec函数执行grep命令
                if(execvp(cmd2[0],cmd2)<0)
                {
                    perror("execvp error");
                    exit(1);
                }
                break;
            }
        }
        else // parent process
        {
            if(i==1) // 父进程要等到两个子进程全部创建完毕才去回收
            {
                close(fd[0]);
                close(fd[1]);
                wait(0);
                wait(0);
            }

        }
    }
    return 0;
}

在这里插入图片描述

管道的读写特性

在这里插入图片描述

案例1:读一个写端关闭的管道


#include <stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{

    int fd[2];
    if(pipe(fd)<0)
    {
        perror("pipe error");
        exit(1);
    }
    pid_t pid;
    if((pid=fork())<0)
    {
        perror("fork error");
        exit(1);
    }
    else if(pid>0)
    {// 父进程从不完整管道(写端关闭)中读取数据
        sleep(5);// 首先让子进程写入数据并关闭管道的读写
        close(fd[1]);
        while(1)
        {
            char c;
            if(read(fd[0],&c,1)==0)
            {
                printf("\n已经读到管道的末尾\n");
                break;
            }
            else
            {
                printf(" %c ",c);
            }
        }
        close(fd[1]);
        wait(0);
    }
    else
    {// 子进程负责将数据写入管道
        close(fd[0]);
        char *s = "1234";
        write(fd[1],s,sizeof(s));
        // 写入数据后关闭管道的写端
        close(fd[1]);
        
    }
    return 0;
}

在这里插入图片描述

案例2:写一个读端关闭的管道

#include <stdio.h>
#include<unistd.h>
#include<signal.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>


void sig_handler(int signo)
{
   if(signo == SIGPIPE)
   {
       printf("SIGPIPE occred!\n");
   }
}

int main()
{

   int fd[2];
   if(pipe(fd)<0)
   {
       perror("pipe error");
       exit(1);
   }
   pid_t pid;
   if((pid=fork())<0)
   {
       perror("fork error");
       exit(1);
   }
   else if(pid>0)
   {// 父进程从不完整管道(读端关闭)中写入数据
       sleep(5);
       close(fd[0]);
       if(signal(SIGPIPE,sig_handler) == SIG_ERR)
       {
           perror("signal sigpipe error");
           exit(1);
       }
       char *s = "123";
       if(write(fd[1],s,sizeof(s))!=sizeof(s))
       {
           fprintf(stderr,"%s , %s\n",strerror(errno),
                   (errno==EPIPE)?"EPPIPE":"UNKNOW");
       }
       close(fd[1]);
       wait(0);
   }
   else
   {// 子进程关闭管道的读端
       close(fd[0]);
       close(fd[1]);
       
   }
   return 0;
}

在这里插入图片描述

标准库中的管道操作

在这里插入图片描述

在这里插入图片描述

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

int main()
{
    FILE *fp;
    // 命令执行的结果放置到fp指向的结构体缓存中
    fp = popen("cat /etc/passwd" ,"r");
    char buf[512];
    memset(buf,0,sizeof(buf));
    while(fgets(buf,sizeof(buf),fp)!=NULL)
    {
        printf("%s",buf);
    }
    pclose(fp);
    printf("_____________________________\n");
    // 为wc命令提供统计的数据
    fp = popen("wc -l","w");
    fprintf(fp,"1\n2\n3\n");
    pclose(fp);
    return 0;
}


在这里插入图片描述

命名管道

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

创建命名管道使用mkfifo函数,命名管道创建后,使用时必须先调用open将其打开。因为命名管道是一个存在于硬盘上的文件,而管道是存在于内存中的特殊文件。调用open打开命名管道的进程可能会阻塞,若同时用读写方式(O_RDWR)打开,则一定不会导致阻塞;以只读方式(O_RDONLY)打开,则调用open函数的进程将会被阻塞直到有写方打开管道;以写方式(O_WRONLY)打开也会阻塞直到有读方式打开管道。

案例1

在这里插入图片描述

  • 读程序

#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<unistd.h>
#include<memory.h>
int main(int argc,char *argv[])
{
    if(argc < 2)
    {
        printf("usage:%s fifo\n",argv[0]);
        exit(1);
    }
    
    printf("open fifo read....\n");
    // 打开命名管道
    int fd = open(argv[1],O_RDONLY);
    if(fd<0)
    {
        perror("open error");
        exit(1);
    }
    else
    {
        printf("open file success : %d\n",fd);
    }
    
    // 从命名管道中读取数据
    char buf[512];
    memset(buf,0,sizeof(buf));
    while(read(fd,buf,sizeof(buf))<0)
    {
        perror("read error");
    }
    close(fd);
    printf("%s\n",buf);
    return 0;
}

  • 写程序

#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<unistd.h>
#include<memory.h>
int main(int argc,char *argv[])
{
    if(argc < 2)
    {
        printf("usage:%s fifo\n",argv[0]);
        exit(1);
    }
    
    printf("open fifo write....\n");
    // 打开命名管道
    int fd = open(argv[1],O_WRONLY);
    if(fd<0)
    {
        perror("open error");
        exit(1);
    }
    else
    {
        printf("open file success : %d\n",fd);
    }
    
    // 从命名管道中读取数据
    char* s = "123456789";
    size_t size = strlen(s);
    while(write(fd,s,size != size))
    {
        perror("write error");
    }
    close(fd);
    return 0;
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

匿名管道 VS 命名管道的区别

相同点

  • 重点是非阻塞
    在这里插入图片描述

不相同点

在这里插入图片描述
在这里插入图片描述

System V IPC

在这里插入图片描述

  • 管道的创建在内核中,但是其释放由内核控制。而三个IPC对象的释放需要用户控制。
    在这里插入图片描述

IPC对象的权限和所有者结构体

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

消息队列

在这里插入图片描述
在这里插入图片描述

消息队列属性

在这里插入图片描述
在这里插入图片描述

创建消息队列

在这里插入图片描述

消息队列控制

在这里插入图片描述

发送消息

在这里插入图片描述在这里插入图片描述

接受消息

在这里插入图片描述

案例:发送消息

  • 一个发送消息,一个接受消息
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/msg.h>

typedef struct
{
    long type; // 消息类型?
    int  start; // 消息数据本身(包含start和end)
    int end;
}MSG;
/*
 * 往消息队列中发送消息
 * */

int main(int argc,char *argv[])
{
    if(argc<2)
    {
        printf("参数不足\n");
        exit(1);
    }
    key_t key = atoi(argv[1]);
    printf("key: %d\n",key);
    
    // 创建消息队列
    int msq_id;
    if((msq_id=msgget(key,IPC_CREAT|IPC_EXCL|0777)<0))
    {
        perror("msgget error");
    }
    printf("msg_id : %d\n",msq_id);
    // 定义要发送的消息
    MSG m1 = {4,4,400};
    MSG m2 = {2,2,200};
    MSG m3 = {1,1,100};
    MSG m4 = {6,6,600};
    MSG m5 = {6,60,6000};
    // 发送消息到消息队列
    if(msgsnd(msq_id,&m1,sizeof(MSG)-sizeof(long),
              IPC_NOWAIT)<0)
    {
        perror("msgsnd error ");
    }
    if(msgsnd(msq_id,&m2,sizeof(MSG)-sizeof(long),
              IPC_NOWAIT)<0)
    {
        perror("msgsnd error ");
    }
    if(msgsnd(msq_id,&m3,sizeof(MSG)-sizeof(long),
              IPC_NOWAIT)<0)
    {
        perror("msgsnd error ");
    }
    if(msgsnd(msq_id,&m4,sizeof(MSG)-sizeof(long),
              IPC_NOWAIT)<0)
    {
        perror("msgsnd error ");
    }
    if(msgsnd(msq_id,&m1,sizeof(MSG)-sizeof(long),
              IPC_NOWAIT)<0)
    {
        perror("msgsnd error ");
    }
    
    // 获取消息队列的总数
    struct msqid_ds ds;
    if(msgctl(msq_id,IPC_STAT,&ds)<0)
    {
        perror("msgctl error");
    }
    printf("msg 的消息总数为: %ld\n",ds.msg_qnum);
    return 0;
}


在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  • 通过命令删除
    在这里插入图片描述

在这里插入图片描述

案例:接受消息


#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/msg.h>

typedef struct
{
    long type; // 消息类型·
    int  start; // 消息数据本身(包含start和end)
    int end;
}MSG;
int main(int argc,char *argv[])
{
    if(argc<3)
    {
        printf("参数不足\n");
        exit(1);
    }
    key_t key = atoi(argv[1]);
    long type = atoi(argv[2]);
    printf("key: %d\n",key);
    
    // 获得指定的消息队列
    int msq_id;
    if((msq_id=msgget(key,0777)<0))
    {
        perror("msgget error");
    }
    printf("msg_id : %d\n",msq_id);
    
    
    // 从消息队列中接受指定类型的消息
    MSG m1;
    if(msgrcv(msq_id,&m1,sizeof(MSG)-sizeof(long),
              type,IPC_NOWAIT)<0)
    {
        perror("msgrcv error ");
    }
    else
    {
        printf("type %ld start: %d end %d\n",
               m1.type,m1.start,m1.end);
    }
    return 0;
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

共享内存

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

共享内存属性

在这里插入图片描述
在这里插入图片描述

共享内存使用步骤

在这里插入图片描述

创建共享内存

在这里插入图片描述
在这里插入图片描述在这里插入图片描述在这里插入图片描述

共享内存控制

在这里插入图片描述

共享内存映射和解除映射

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

案例(单向同步)

  • 二个进程(可以有关系也可以没关系),一个进程操作的时候另外一个进程阻塞。利用管道达到进程间的同步。

  • tell.h


#ifndef __TELL_H__
#define __TELL_H__
// 管道初始化
extern void init();

// 利用管道进行等待
extern void wait_pipi();

// 利用管道进行通知
extern void notify_pipe();

// 销毁管道
extern void destory_pipe();
#endif

  • tell.c


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

static int fd[2];
// 管道初始化
void init()
{
    if(pipe(fd)<0)
    {
        perror("pipe error");
    }
}

// 利用管道进行等待
void wait_pipi()
{
    // 管道:一端没有写数据,而另外一端去读数据,则会阻塞
    char c;
    // 管道读写默认是阻塞的
    if(read(fd[0],&c,1)<0)
    {
        perror("read pipe error");
    }
}
// 利用管道进行通知
void notify_pipe()
{
    char c = 'c';
    if(write(fd[1],&c,1)!=1)
    {
        perror("write pipe error");
    }
}
// 销毁管道
void destory_pipe()
{
    close(fd[0]);
    close(fd[1]);
}
  • cal_shm.c

#include <stdio.h>
#include<sys/shm.h>
#include<stdlib.h>
#include<string.h>
#include"tell.h"


int main()
{
    // 创建共享内存
    int shmid;
    if((shmid=shmget(IPC_PRIVATE,1024,
                       IPC_CREAT|IPC_EXCL|0777))<0)
       {
           perror("shmget error");
           exit(1);
       }
    pid_t pid;
    init(); // 初始化管道
    if((pid=fork())<0)
    {
        perror("fork error");
        exit(1);
    }
    else if(pid>0)
    {
        // 进行共享内存的映射
        int *pi = (int*)shmat(shmid,0,0);
        if(pi==(int*)-1)
        {
            perror("shmat error");
            exit(1);
        }
        // 往共享内存中写入数据(通过操作映射的地址即可)
        *pi = 100;
        *(pi+1) = 20;
        // 操作完毕解除共享内存映射
        shmdt(pi);
        // 写入数据完毕,通知子进程读取数据
        notify_pipe();
        //关闭fd
        destory_pipe();
        // 避免僵尸进程
        wait(0);
    }
    else // 子进程 
    {
        // 子进程阻塞,等待父进程写数据
        wait_pipe();
        // 子进程从共享内存中读取数据
        // 子进程进行共享内存的映射
        int *pi = (int*)shmat(shmid,0,0);
        if(pi==(int*)-1)
        {
            perror("shmat error");
            exit(1);
        }
        printf("start:%d,end:%d\n",*pi,*(pi+1));
        // 解除映射
        shmdt(pi);
        // 删除共享内存,两个进程一个进行删除即可
        shmctl(shmid,IPC_RMID,NULL);
        // 子进程复制父进程的fd,也需要关闭
        destory_pipe();
    }
    return 0;
}

在这里插入图片描述

案例(互斥访问)

  • 银行

在这里插入图片描述

  • account.h

#ifndef __ACCOUNT_H__
#define __ACCOUNT_H__

#include<pthread.h>
typedef struct{
	int code;
	double balance;
}Account;

extern double withdraw(Account *a,double amt);
extern double deposit(Account *a,double amt);
extern double get_balance(Account *a);


#endif

  • account.h

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include"account.h"


double withdraw(Account *a,double amt)
{
	if(amt<0 || amt>a->balance){
		return 0.0;
	}
	double balance = a->balance;
	sleep(1);
	balance -= amt;
	a->balance = balance;
	return amt;
}
 double deposit(Account *a,double amt)
 {
 if(amt<0){
	return 0.0;
 }
	double balance = a->balance;
	sleep(1);
 	balance -= amt;
	a->balance = balance;
	return amt;
 }
 double get_balance(Account *a)
 {
 	assert(a!=NULL);
	double balance = a->balance;
 	return balance;
 }

  • account_test.c
#include <stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/shm.h>
#include"account.h"


int main()
{
    // 在共享内存中创建银行账户
    int shmid;
    if((shmid=shmget(IPC_PRIVATE,sizeof(Account),
                     IPC_CREAT|IPC_EXCL|0777))<0)
     {
       perror("shmget error");
       exit(1);
    }
    // 进行共享内存映射i(a就是映射的地址)
    Account *a = (Account*)shmat(shmid,0,0);
    if(a==(Account*)-1)
    {
        perror("shamt error");
        exit(1);
    }

    a->code = 100001;
    a->balance = 10000;
    printf("balance : %f\n",a->balance);

    pid_t pid;
    if((pid=fork())<0)
    {
        perror("fork error");
        exit(1);
    }
    else if(pid>0) // 父进程
    {
        //父进程执行取款操作
        double amt = withdraw(a,10000);
     
        printf("父进程  pid:%d withdraw: %f from code %d ",
               getpid(),amt,a->code);
        // 对共享内存的操作要在解除映射之前
       printf("balance : %f\n",a->balance);
        wait(0);
        shmdt(a);
    }
    else // 子进程
    {
        double amt = withdraw(a,10000); 
        // 这里子进程也可以再次映射,也可以不需要(因为子进程可以继承返回后的内存地址)
        
        printf("子进程  pid:%d withdraw: %f from  code %d\n",getpid(),amt,a->code);
        shmdt(a);
    }



    return 0;
}

  • 注意没有利用同步机制
    在这里插入图片描述

在这里插入图片描述

信号集属性

在这里插入图片描述

创建信号量集

在这里插入图片描述

信号量集控制

在这里插入图片描述在这里插入图片描述

信号量集操作

在这里插入图片描述

案例:银行账户案例(互斥访问)

  • pc.h

#ifndef __PV_H__
#define __PV_H__

// 初始化semnums个信号灯/信号量的值(value)
extern int I(int semnums,int value);

// 对信号量集(semid)中的信号灯(semnum)做P(value)操作
extern void P(int semid,int semnum,int value);

// 对信号量集(semid)中的信号灯(semnum)做V(value)操作
extern void V(int semid,int semnum,int value);

// 销毁信号量集(semid)
extern void D(int semid);

#endif

  • pc.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<sys/sem.h>
#include"pv.h"

union semun
{
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

// 创建信号量集,并初始化信号量集
int I(int semnums,int value)
{
    // 创建信号集
    int semid = semget(IPC_PRIVATE,semnums,
                       IPC_CREAT|IPC_EXCL|0777);
    if(semid < 0)
        return -1;
    union semun un;
    unsigned short *array = (unsigned short*)
        calloc(semnums,sizeof(unsigned short));
    
    for(int i = 0;i<semnums;i++)
        array[i] = value;
    un.array = array;

    /*
     *初始化信号量集中的所有信号灯的初值
    0:表示要初始化所有信号灯
     * */
    if(semctl(semid,0,SETALL,un)<0)
    {
        perror("semctl error");
        return -1;
    }
    free(array);
    return semid;
}
//对信号量集(semid)中的信号灯(semnum)做P(value)操作
void P(int semid,int semnum,int value)
{
    assert(value>=0);
    /*
     * 定义sembuf类型的结构体数组,放置若干个结构体变量
     * 对应要操作的信号量,要作的P或V操作
     */
    struct sembuf ops[] = {{semnum,-value,SEM_UNDO}};
    // 这里的负号表示做减操作,注意数组只有一个成员表示值操作一个信号量
    
    if(semop(semid,ops,
             sizeof(ops)/sizeof(struct sembuf))<0)
    {
        perror("semop error");
    }
}
// 对信号量集(semid)中的信号灯(semnum)做V(value)操作
void V(int semid,int semnum,int value)
{
    assert(value>=0);
    struct sembuf ops[] = {{semnum,value,SEM_UNDO}};
    if(semop(semid,ops,
             sizeof(ops)/sizeof(struct sembuf))<0)
    {
        perror("semop error");
    }
}
// 销毁信号量集(semid)
void D(int semid)
{
    if(semctl(semid,0,IPC_RMID,NULL)<0)
    {
        perror("semctl error");
    }
}

在这里插入图片描述

  • account.h
#ifndef __ACCOUNT_H__
#define __ACCOUNT_H__

#include<pthread.h>
typedef struct{
	int code;
	double balance;
    int semid; // 在共享资源上绑定一个信号量集
}Account;

extern double withdraw(Account *a,double amt);
extern double deposit(Account *a,double amt);
extern double get_balance(Account *a);


#endif


  • account.c
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include"account.h"
#include"pv.h"

double withdraw(Account *a,double amt)
{
	
    assert(a!=NULL);
    // 对信号量集semid中的0号信号量/信号灯作P(1)
    P(a->semid,0,1);
    if(amt<0 || amt>a->balance){
		// 对信号量集semid中的0号信号量/信号灯作V(1)操作
       V(a->semid,0,1);
        return 0.0;
	}
    
    double balance = a->balance;
	sleep(1);
	balance -= amt;
	a->balance = balance;
    // 对信号量集semid中的0号信号量/信号灯作V(1)操作
    V(a->semid,0,1); 
    return amt;
}
 double deposit(Account *a,double amt)
 {
    assert(a!=NULL);
    P(a->semid,0,1);
    if(amt<0){
        V(a->semid,0,1);
	    return 0.0;
 }
	double balance = a->balance;
	sleep(1);
 	balance -= amt;
	a->balance = balance;
    V(a->semid,0,1);
	return amt;
 }
 double get_balance(Account *a)
 {
 	assert(a!=NULL);
	P(a->semid,0,1);
    double balance = a->balance;
 	V(a->semid,0,1);
    return balance;
 }


  • account_test.c
#include <stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/shm.h>
#include"account.h"
#include"pv.h"

int main()
{
    // 在共享内存中创建银行账户
    int shmid;
    if((shmid=shmget(IPC_PRIVATE,sizeof(Account),
                     IPC_CREAT|IPC_EXCL|0777))<0)
     {
       perror("shmget error");
       exit(1);
    }
    // 进行共享内存映射i(a就是映射的地址)
    Account *a = (Account*)shmat(shmid,0,0);
    if(a==(Account*)-1)
    {
        perror("shamt error");
        exit(1);
    }

    a->code = 100001;
    a->balance = 10000;
    
    // 创建信号量集并初始化(1个信号量/信号灯,初值为1)
    a->semid = I(1,1);
    if(a->semid < 0)
    {
        perror("I() init fail");
    }
    printf("balance : %f\n",a->balance);


    pid_t pid;
    if((pid=fork())<0)
    {
        perror("fork error");
        exit(1);
    }
    else if(pid>0) // 父进程
    {
        //父进程执行取款操作
        double amt = withdraw(a,10000);
     
        printf("父进程  pid:%d withdraw: %f from code %d ",
               getpid(),amt,a->code);
        // 对共享内存的操作要在解除映射之前
       printf("balance : %f\n",a->balance);
        wait(0);
        // 销毁信号量
        D(a->semid);
        // 解除共享内存的映射
        shmdt(a);
        // 删除共享内存
        shmctl(shmid,IPC_RMID,NULL);
    }
    else // 子进程
    {
        double amt = withdraw(a,10000); 
        // 这里子进程也可以再次映射,也可以不需要(因为子进程可以继承返回后的内存地址)
        
        printf("子进程  pid:%d withdraw: %f from  code %d\n",getpid(),amt,a->code);
        shmdt(a);
    }



    return 0;
}

在这里插入图片描述

案例:读者写者问题(互斥访问)

在这里插入图片描述
📌 选择 IPC 方式
简单父子进程通信 → 管道(Pipe)
多个无关进程的消息传输 → 消息队列(Message Queue)
进程间共享大块数据 → 共享内存(Shared Memory)
控制进程同步 → 信号量(Semaphore)
🚀 实际应用:

Shell 管道 (|) 采用 匿名管道。
Linux 日志系统 使用 消息队列。
数据库缓存 采用 共享内存(如 MySQL 的 InnoDB)。
进程锁(多进程文件访问控制) 使用 信号量。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值