• 五十、进程间通信——System V IPC 之共享内存


    50.1 共享内存

    50.1.1 共享内存的概念

    • 共享内存区域是被多个进程共享的一部分物理内存
    • 多个进程都可把该共享内存映射到自己的虚拟内存空间。所有用户空间的进程若要操作共享内存,都要将其映射到自己虚拟内存空间中,通过映射的虚拟内存空间地址去操作共享内存,从而达到进程间的数据通信
    • 共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容
    • 本身不提供同步机制,可通过信号量进行同步
    • 提升数据处理效率,一种效率最高的 IPC 机制

      

    50.1.2 共享内存的映射

      

    50.1.3 共享内存的属性

      

    50.1.4 共享内存使用步骤

    • 使用 shmget 函数创建共享内存
    • 使用 shmat 函数映射共享内存,将这段创建的共享内存映射到具体的进程虚拟内存空间

    50.1.5 创建共享内存

      

    • 函数参数
      • key:用户指定的共享内存键值
      • size:共享内存大小
      • shmflg:IPC_CREAT、IPC_EXCL 等权限组合
    • 返回值:
      • 如果成功,返回内核中共享内存的标识 ID。如果失败,返回 -1
      • errno:
        • EINVAL:无效的内存段大小
        • EEXIST:内存段已经存在,无法创建
        • EIDRM:内存段已经被删除
        • ENOENT:内存段不存在
        • EACCES:权限不够
        • ENOMEM:没有足够的内存来创建内存段

    50.1.6 共享内存控制函数

      

    • 函数参数:
      • shmid:共享内存 ID
      • cmd:
        • IPC_STAT:获取共享内存段属性
        • IPC_SET:设置共享内存段属性
        • IPC_RMID:删除共享内存段
        • SHM_LOCK:锁定共享内存段页面(页面映射到物理内存,不和外存进行换入换出操作)
        • SHM_UNLOCK:解除共享内存段页面的锁定
      • buf:共享内存属性指针
    • 返回值:成功返回 0,出错返回 -1

    50.1.7 共享内存的映射和解除映射

      

    • 函数参数:
      • shmid:共享内存 ID
      • shmaddr:映射到进程虚拟内存空间的地址,建议设置为 0,由操作系统分配
      • shmflg:若 shmaddr 设置为 0,则 shmflag 也设置为 0
        • SHM_RND
        • SHMLBA:地址为 2 的乘方
        • SHM_RDONLY:只读方式链接
    • 返回值:
      • shmat:成功,则返回共享内存映射到进程虚拟内存空间的地址;失败,则返回 -1
      • shmdt:如果失败,则返回 -1
      • errno:
        • EINVAL:无效的 IPC ID 值或者无效的地址
        • ENOMEM:没有足够的内存
        • EACCES:存取权限不够
    • 函数说明:
      • 子进程不继承父进程创建的共享内存,大家是共享的。子进程继承父进程映射的地址

    50.2 例子

    50.2.1 共享内存常规操作--单向同步

      tell.h

     1 #ifndef __TELL_H
     2 #define __TELL_H
     3 
     4 
     5 /** 管道初始化 */
     6 extern void init();
     7 
     8 /** 利用管道进行等待 */
     9 extern void wait_pipe();
    10 
    11 /** 利用管道进行通知 */
    12 extern void notify_pipe();
    13 
    14 /** 销毁管道 */
    15 extern void destroy_pipe();
    16 
    17 #endif

      tell.c

     1 #include <stdlib.h>
     2 #include <stdio.h>
     3 #include <string.h>
     4 #include <unistd.h>
     5 #include "tell.h"
     6 
     7 static int fd[2];
     8 
     9 /** 管道初始化 */
    10 void init()
    11 {
    12     if(pipe(fd) < 0) {
    13         perror("pipe error");
    14     }
    15 }
    16 
    17 /** 利用管道进行等待 */
    18 void wait_pipe()
    19 {
    20     char c;
    21 
    22     /** 管道读写默认是阻塞性的 */
    23     if(read(fd[0], &c, 1) < 0){
    24         perror("wait pipe error");
    25     }
    26 }
    27 
    28 /** 利用管道进行通知 */
    29 void notify_pipe()
    30 {
    31     char c = 'c';
    32     if(write(fd[1], &c, 1) != 1){
    33         perror("notify pipe error");
    34     }
    35 }
    36 
    37 /** 销毁管道 */
    38 void destroy_pipe()
    39 {
    40     close(fd[0]);
    41     close(fd[1]);
    42 }

      cal_shm.c

     1 #include <sys/shm.h>
     2 #include <sys/ipc.h>
     3 #include <sys/types.h>
     4 #include <sys/wait.h>
     5 #include <unistd.h>
     6 #include <stdio.h>
     7 #include <stdlib.h>
     8 #include <string.h>
     9 #include "tell.h"
    10 
    11 
    12 int main(void)
    13 {
    14     /** 创建共享内存 */
    15     int shmid;
    16     if((shmid = shmget(IPC_PRIVATE, 1024, IPC_CREAT | IPC_EXCL | 0777)) < 0){
    17         perror("shmget error");
    18         return 1;
    19     }
    20 
    21     pid_t pid;
    22     init(); ///< 初始化管道
    23     if((pid = fork()) < 0){
    24         perror("fork error");
    25         return 1;
    26     }
    27     else if(pid > 0){
    28         /** 进行共享内存映射 */
    29         int *pi = (int *)shmat(shmid, 0, 0);
    30         if(pi == (int *)-1){
    31             perror("shmat error");
    32             return 1;
    33         }
    34 
    35         /** 往共享内存中写入数据(通过操作映射的地址即可) */
    36         *pi = 100;
    37         *(pi + 1) = 20;
    38 
    39         /** 操作完毕解除共享内存的映射 */
    40         shmdt(pi);
    41 
    42         /** 通知子进程去读取数据 */
    43         notify_pipe();
    44 
    45         destroy_pipe();
    46 
    47         wait(0);
    48     }
    49     else{
    50         /** 子进程阻塞,等待父进程先往共享内存中写入数据 */
    51         wait_pipe();
    52 
    53         /** 子进程读取共享内存中的数据 */
    54         /** 子进程进行共享内存的映射 */
    55         int *pi = (int *)shmat(shmid, 0, 0);
    56         if(pi == (int *)-1){
    57             perror("shmat error");
    58             return 1;
    59         }
    60         printf("start: %d, end: %d
    ", *pi, *(pi + 1));
    61 
    62         /** 读取完毕后解除映射 */
    63         shmdt(pi);
    64 
    65         /** 删除共享内存 */
    66         shmctl(shmid, IPC_RMID, NULL);
    67 
    68         destroy_pipe();
    69 
    70     }
    71 
    72     return 0;
    73 }

      编译运行如下:

      

    50.2.2 进程间互斥案例---银行账户(ATM)

      

      atm_account.h

     1 #ifndef __ATM_ACCOUNT_H__
     2 #define __ATM_ACCOUNT_H__
     3 
     4 #include <math.h>
     5 #include <malloc.h>
     6 #include <stdlib.h>
     7 #include <stdio.h>
     8 #include <string.h>
     9 #include <unistd.h>
    10 #include <sys/shm.h>
    11 #include <sys/ipc.h>
    12 
    13 /** 账户信息 */
    14 typedef struct {
    15     int         code;       ///< 银行账户的编码
    16     double      balance;    ///< 账户余额
    17 }atm_Account;
    18 
    19 /** 取款 */
    20 extern double atm_account_Withdraw(atm_Account *account, double amt);
    21 /** 存款 */
    22 extern double atm_account_Desposit(atm_Account *account, double amt);
    23 /** 查看账户余额 */
    24 extern double atm_account_BalanceGet(atm_Account *account);
    25 
    26 #endif

      atm_account.c

     1 #include "atm_account.h"
     2 
     3 /** 取款: 成功,则返回取款金额 */
     4 double atm_account_Withdraw(atm_Account *account, double amt)
     5 {
     6     if(NULL == account) {
     7         return 0.0;
     8     }
     9 
    10     if(amt < 0 || amt > account->balance) {
    11         return 0.0;
    12     }
    13 
    14     double balance_tmp = account->balance;
    15     sleep(1);
    16     balance_tmp -= amt;
    17     account->balance = balance_tmp;
    18 
    19     return amt;
    20 }
    21 
    22 /** 存款: 返回存款的金额 */
    23 double atm_account_Desposit(atm_Account *account, double amt)
    24 {
    25     if(NULL == account){
    26         return 0.0;
    27     }
    28 
    29     if(amt < 0){
    30         return 0.0;
    31     }
    32 
    33     double balance_tmp = account->balance;
    34     sleep(1);
    35     balance_tmp += amt;
    36     account->balance = balance_tmp;
    37 
    38     return amt;
    39 }
    40 
    41 /** 查看账户余额 */
    42 double atm_account_BalanceGet(atm_Account *account)
    43 {
    44     if(NULL == account){
    45         return 0.0;
    46     }
    47 
    48     double balance_tmp = account->balance;
    49 
    50     return balance_tmp;
    51 }

      atm_handler.h

     1 #ifndef __ATM_HANDLER_H__
     2 #define __ATM_HANDLER_H__
     3 
     4 #include "atm_account.h"
     5 #include <sys/wait.h>
     6 #include <sys/shm.h>
     7 #include <sys/ipc.h>
     8 #include <stdio.h>
     9 #include <stdlib.h>
    10 #include <string.h>
    11 
    12 typedef enum {
    13     ATM_ERROR_NONE,
    14     ATM_ERROR_ACCOUNT_CREATE
    15 }atm_error_t;
    16 
    17 /** 账户操作结构体 */
    18 typedef struct {
    19     char            name[20];   ///< 操作人的姓名
    20     atm_Account     *account;   ///< 操作的账户
    21     double          amt;        ///< 操作的金额
    22 }atm_handler_t;
    23 
    24 extern atm_error_t atm_handler_main(void);
    25 
    26 #endif

      atm_handler.c

     1 #include "atm_handler.h"
     2 
     3 
     4 /** 账户操作主函数 */
     5 atm_error_t atm_handler_main(void)
     6 {
     7     /** 在共享内存中创建银行账户 */
     8     int shmid;
     9     if((shmid = shmget(IPC_PRIVATE, sizeof(atm_Account), IPC_CREAT | IPC_EXCL | 0777)) < 0) {
    10         perror("shmget error");
    11         return 1;
    12     }
    13 
    14     /** 进程共享内存映射 */
    15     atm_Account *a = (atm_Account *)shmat(shmid, 0, 0);
    16     if(a == (atm_Account *)-1){
    17         perror("shmat error");
    18         return 1;
    19     }
    20     a->code = 100001;
    21     a->balance = 10000;
    22     printf("balance: %f
    ", a->balance);
    23 
    24     pid_t pid;
    25     if((pid = fork()) < 0){
    26         perror("fork error");
    27         return 1;
    28     }
    29     else if(pid > 0){
    30         /** 父进程执行取款操作 */
    31         double amt = atm_account_Withdraw(a, 10000);
    32         printf("pid %d withdraw %f form code %d
    ", getpid(), amt, a->code);
    33         wait(0);
    34         /** 对共享内存的操作要在解除映射之前 */
    35         printf("balance: %f
    ", a->balance);
    36         shmdt(a);
    37     }
    38     else {
    39         /** 子进程也执行取款操作 */
    40         double amt = atm_account_Withdraw(a, 10000);
    41         printf("pid %d withdraw %f form code %d
    ", getpid(), amt, a->code);
    42         shmdt(a);
    43     }
    44 
    45     return ATM_ERROR_NONE;
    46 }

      atm_test.c

    1 #include "atm_handler.h"
    2 
    3 int main(void)
    4 {
    5     atm_handler_main();
    6     return 0;
    7 }

      Makefile

     1 #PROJECT_ROOT = $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
     2 PROJECT_ROOT = $(shell pwd)
     3 SRC_DIR = $(PROJECT_ROOT)/src
     4 INCLUDE_DIR = $(PROJECT_ROOT)/include
     5 OBJ_DIR = $(PROJECT_ROOT)/obj
     6 BIN_DIR = $(PROJECT_ROOT)/bin
     7 
     8 
     9 # 找出 src 目录下的所有 .c 文件
    10 C_SRCS = $(wildcard $(SRC_DIR)/*.c)
    11 # 将所有的 src 下的 .c 文件替换为 .o 文件
    12 C_OBJS = $(patsubst %c, %o, $(C_SRCS))
    13 TARGET = test
    14 SHARE_LIB = libatm.a
    15 
    16 C_SRC_MAIN = atm_test.c
    17 
    18 CC = gcc
    19 CCFLAGS += fPIC
    20 LDFLAGS += -shared -fPIC
    21 ASFLAGS +=
    22 ARFLAGS = -crs
    23 LIBS_FLAGS = -L$(BIN_DIR)
    24 
    25 RM = rm -rf
    26  
    27 CFLAGS += -Wall -g -I$(INCLUDE_DIR)
    28 INCDIR += -I$(INCLUDE_DIR)
    29 
    30 
    31 
    32 .PHONY: all clean test
    33 
    34 all: $(TARGET)
    35     cp $(SHARE_LIB) $(BIN_DIR)
    36     cp $(SRC_DIR)/*.o $(OBJ_DIR)/
    37     $(RM) $(SHARE_LIB) $(SRC_DIR)/*.o
    38 
    39 $(TARGET): $(SHARE_LIB)
    40     $(CC) $(C_SRC_MAIN) -o $(TARGET) $(CFLAGS) $(INCDIR) $(LIBS_FLAGS) -latm
    41 
    42 $(SHARE_LIB): $(C_OBJS)
    43     $(AR) $(ARFLAGS) $(SHARE_LIB) $(C_OBJS)
    44     cp $(SHARE_LIB) $(BIN_DIR)
    45 
    46 $(C_OBJS) : %.o:%.c
    47     $(CC) -c $(CFLAGS) $(INCDIR) -o $@ $<
    48 
    49 clean:
    50     $(RM) mshell 
    51     $(RM) $(SHARE_LIB) 
    52     $(RM) $(OBJ_DIR)/$(OBJS)/*.o 
    53     $(RM) $(SRC_DIR)/*.o

      编译执行结果如下:

      

      两个账户都取款到 10000元,那是因为我们当前在共享内存的操作没有做互斥,下一节将添加互斥。

  • 相关阅读:
    ListView点击事件
    ListView优化:
    自定义ListView
    ListView简单使用
    mysql中show processlist过滤和杀死线程
    自定义控件
    yum配置中driver-class-name: com.mysql.jdbc.Driver报错
    CSS+HTML
    maven的配置
    Model、ModelMap、ModelAndView的作用及区别
  • 原文地址:https://www.cnblogs.com/kele-dad/p/10317092.html
Copyright © 2020-2023  润新知