共享内存与信号量

一. 共享内存

最为高效的进程间通信方式
进程直接读写内存,不需要任何数据的拷贝
  •为了在多个进程间交换信息,内核专门留出了一块内存区
  •由需要访问的进程将其映射到自己私有地址空间
  •进程直接读写这一内存区而不需要进行数据的拷贝,提高了效率
 
多个进程共享一段内存,需要依靠某种同步机制,如互斥锁和信号量等

 

l共享内存编程步骤:
  1. 创建共享内存
    •函数shmget()
    •从内存中获得一段共享内存区域
 
  2. 映射共享内存
    •把这段创建的共享内存映射到具体的进程空间中
    •函数shmat()
 
  3. 使用这段共享内存
    •可以使用不带缓冲的I/O读写命令对其进行操作
 
  4. 撤销映射操作: 函数shmdt()
 
  5. 删除共享内存: 函数shctl()
 

 程序代码如下:

/*
 * 共享内存测试程序(创建,写入数据) shamA.c
 * Created on: Oct 11, 2016
 * Author: zhangming
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <signal.h>

char *ptrStart;
int shmid;

void deal(int s){
    if(s == SIGINT){
        //4.卸载共享内存shmdt
        shmdt(ptrStart);

        //5.删除共享内存shctl
        shmctl(shmid,IPC_RMID,NULL);
        exit(0);
    }
}

main(){
    signal(SIGINT,deal);

    //1.创建共享内存shmget
    key_t key = ftok(".",255);
    if(key==-1) {
        printf("ftok error:%m\n"),exit(-1);
    }
    shmid = shmget(key,4,IPC_CREAT|IPC_EXCL|0666);
    if(shmid==-1) {
        printf("get error:%m\n"),exit(-1);
    }

    //2.挂载共享内存shmat
    ptrStart = shmat(shmid,0,0);
    if(ptrStart == NULL) {
        printf("at error:%m\n"),exit(-1);
    }

    //3.访问共享内存
    int i = 0;
    while(1){
        *ptrStart = 'a'+i;
        if(*ptrStart == 'z'){
            break;
        }
        sleep(1);
        i++;
    }
}
/*
 * 共享内存测试程序(访问,读出数据) shamB.c
 * Created on: Oct 11, 2016
 * Author: zhangming
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/shm.h>
#include <sys/ipc.h>

char *ptrStart;
int shmid;

void deal(int s){
    if(s==SIGINT){
        //4.卸载共享内存shmdt
        shmdt(ptrStart);
        exit(0);
    }
}

main(){
    signal(SIGINT,deal);

    //1.创建共享内存shmget
    key_t key=ftok(".",255);
    if(key==-1) {
        printf("ftok error:%m\n"),exit(-1);
    }
    shmid=shmget(key,4,0);
    if(shmid==-1) {
        printf("get error:%m\n"),exit(-1);
    }

    //2.挂载共享内存shmat
    ptrStart = shmat(shmid,0,0);
    if(ptrStart == NULL){
        printf("at error:%m\n"),exit(-1);
    }

    //3.访问共享内存
    while(1){
        sleep(1);
        fprintf(stdout,"%c ",*ptrStart);
        fflush(stdout);
        if(*ptrStart == 'z'){ //只读取一次
            printf("\n");
            break;
        }
    }
}

运行结果截图:

二.信号量

信号量: 解决进程之间的同步与互斥的IPC机制 

多个进程同时运行,之间存在关联
  •同步关系
  •互斥关系
互斥与同步关系存在的根源在于临界资源
  •临界资源是在同一个时刻只允许有限个(通常只有一个)进程可以访问(读)或修改(写)的资源
    –硬件资源(处理器、内存、存储器以及其他外围设备等)
    –软件资源(共享代码段,共享结构和变量等)
  •临界区,临界区本身也会成为临界资源
 
一个称为信号量的变量
  •信号量对应于某一种资源,取一个非负的整型值
  •信号量值指的是当前可用的该资源的数量,若它等于0则意味着目前没有可用的资源
在该信号量下等待资源的进程等待队列
对信号量进行的两个原子操作(PV操作)
  •P操作
  •V操作
 
最简单的信号量是只能取0 和1 两种值,叫做二维信号量
 
编程步骤:
  创建信号量或获得在系统已存在的信号量
    •调用semget()函数
    •不同进程使用同一个信号量键值来获得同一个信号量
  初始化信号量
    •使用semctl()函数的SETVAL操作
    •当使用二维信号量时,通常将信号量初始化为1
  进行信号量的PV操作
    •调用semop()函数
    •实现进程之间的同步和互斥的核心部分
  如果不需要信号量,则从系统中删除它
    •使用semclt()函数的IPC_RMID操作
    •在程序中不应该出现对已被删除的信号量的操作
 

 

 通过对信号量PV操作,消除父子进程间的竞争条件,使得其调用顺序可控,代码如下:
/*
 * sem_util.h
 * Created on: Oct 11, 2016
 * Author: zhangming
 */

#ifndef SEM_UTIL_H_
#define SEM_UTIL_H_

#endif /* SEM_UTIL_H_ */

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

void init_sem(int,int); //将信号量sem_id设置为init_value
void del_sem(int); //删除sem_id信号量
void sem_p(int);  //对sem_id执行p操作
void sem_v(int);  //对sem_id执行V操作
/*
 * 信号量初始化,PV操作封装
 * sem_util.c
 * Created on: Oct 11, 2016
 * Author: zhangming
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include "sem_util.h"

// 将信号量sem_id设置为init_value
void init_sem(int sem_id,int init_value) {
    union semun sem_union;
    sem_union.val = init_value;

    if(semctl(sem_id,0,SETVAL,sem_union) == -1){
        /**
         *perror(s) 用来将上一个函数发生错误的原因输出到标准设备(stderr)
         *参数 s 所指的字符串会先打印出,后面再加上错误原因字符串
         */
        perror("Sem init");
        exit(-1);
    }
}

// 删除sem_id信号量
void del_sem(int sem_id) {
    union semun sem_union;

    if(semctl(sem_id,0,IPC_RMID,sem_union) == -1){
        perror("Sem delete");
        exit(-1);
    }
}

// 对sem_id执行p操作
void sem_p(int sem_id) {
    /**
     * 函数原型 int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops)
     * 第三个参数num_sem_ops为第二个参数的个数(即数组元素的个数)
     */
    struct sembuf sem_buf;
    sem_buf.sem_num=0; //信号量编号
    sem_buf.sem_op=-1; //P操作
    sem_buf.sem_flg=SEM_UNDO; //系统退出前未释放信号量,系统自动释放
    if(semop(sem_id,&sem_buf,1) == -1){
        perror("Sem P operation");
        exit(-1);
    }
}

// 对sem_id执行V操作
void sem_v(int sem_id) {
    struct sembuf sem_buf;
    sem_buf.sem_num=0; //信号量编号
    sem_buf.sem_op=1; //V操作
    sem_buf.sem_flg=SEM_UNDO; //系统退出前未释放信号量,系统自动释放
    if (semop(sem_id,&sem_buf,1)==-1) {
        perror("Sem V operation");
        exit(-1);
    }
}
/*
 * sem_test.c
 * Created on: Oct 11, 2016
 * Author: zhangming
 */

#include "sem_util.c"

#define DELAY_TIME 15

int main() {
    pid_t pid;
    int sem_id;
    key_t sem_key;

    sem_key=ftok(".",'a');
    //以0666且create mode创建一个信号量,返回给sem_id
    sem_id=semget(sem_key,1,0666|IPC_CREAT);
    //将sem_id设为1
    init_sem(sem_id,1);

    if ((pid=fork())<0) {
        perror("Fork error!\n");
        exit(-1);
    } else if (pid==0) {
        sem_p(sem_id); //P操作
        printf("Child running...\n");
        sleep(DELAY_TIME);
        printf("Child pid:%d,returned pid:%d.\n",getpid(),pid);
        sem_v(sem_id); //V操作
        exit(0);
    } else {
        sem_p(sem_id); //P操作
        printf("Parent running!\n");
        sleep(DELAY_TIME);
        printf("Parent pid:%d,returned pid:%d.\n",getpid(),pid);
        sem_v(sem_id); //V操作
        waitpid(pid,0,0);
        del_sem(sem_id);
        exit(0);
    }
}

运行结果截图:

posted @ 2016-10-12 09:52  水火379  阅读(736)  评论(0)    收藏  举报