天天看點

linux程序間通信:system V 信号量和共享記憶體實作程序間同步

關于信号量和共享記憶體的相關描述已經在前幾篇提到過;

  • 信号量:即核心維護的一個正整數,可以使用核心提供的p/v接口進行該正整數的+/-操作,它主要用來表示系統中可用資源的個數,協調各個程序有序通路資源,防止發生沖突
  • 共享記憶體:作業系統從運作中的程序擁有的3G使用者空間中提供一段使用者态可以直接通路的記憶體塊,并且該記憶體區域可以被其他程序共享

信号量在共享記憶體中的使用基本流程如下圖:

linux程式間通信:system V 信号量和共享記憶體實作程式間同步

兩個信号量,一個用于控制讀端程序對共享記憶體的通路,一個用于控制寫端程序對共享記憶體的通路

當寫端程序寫入時,寫信号量sem1 程序p操作,讀信号量sem0進行v操作

當讀端程序寫入時,讀信号量sem0進行p操作,寫信号量sem1程序v操作

以下代碼通過信号量實作了每次僅有一個程序操作共享記憶體

寫端​​

​sem_write.c​

​:

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

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/types.h>

union semnum {
    int val;
    struct semid_ds *buf;
    unsigned short int *array;
    struct seminfo *__buf;
};
int sem_id;

//初始化信号量
void sem_init(int sem_id, int nsignum, int sem_value) {
    union semnum sem_union;
    sem_union.val = sem_value;
    if (semctl(sem_id,nsignum,SETVAL,sem_union) == -1) {
        printf("semctl failed\n");
        _exit(-1);
    }
}

//對信号量進行p操作 -1
void sem_p(int sem_id, int nsignum) {
    struct sembuf sops;
    sops.sem_num = nsignum;
    sops.sem_op = -1;
    sops.sem_flg = SEM_UNDO;

    if (semop(sem_id, &sops,1) == -1) {
        printf("semop P failed \n");
        _exit(-1);
    }
    
}
//對信号量進行v操作 +1
void sem_v(int sem_id, int nsignum) {
    struct sembuf sops;
    sops.sem_num = nsignum;
    sops.sem_op = 1;
    sops.sem_flg = SEM_UNDO;

    if (semop(sem_id, &sops,1) == -1) {
        printf("semop P failed \n");
        _exit(-1);
    }
}

//列印信号量
void sem_print(int sem_id, int nsignum) {
    int sem_value;
    sem_value = semctl(sem_id, nsignum, GETVAL);
    printf("sem[%d] = %d \n",nsignum, sem_value);
}

int main() {
    int shm_id;
    key_t shm_key = ftok("./",111);
    key_t sem_key = ftok("./",112);

    //建立共享記憶體,并将共享記憶體進行映射,擷取映射後的共享記憶體位址
    shm_id = shmget(shm_key , 1028 , IPC_CREAT | 0666);
    char *shm_addr = shmat(shm_id, NULL , 0);

    sem_id = semget(sem_key,2,IPC_CREAT | 0666);//建立兩個信号量
    if(sem_id == -1) {
        sem_id = semget(sem_key, 2 ,0666);
    }
    else {
        sem_init(sem_id,0,0); //初始化讀端信号量
        sem_init(sem_id,1,1); //初始化寫端信号量
    }
    
    while(1) {
        sem_p(sem_id,1); //由于接下來要寫入共享記憶體,則對寫端信号量進行p操作,-1
        fgets(shm_addr,1020,stdin);
        sem_v(sem_id,0);//由于讀程序在阻塞讀,則寫完之後立即将讀信号量進行v操作
    }
    return 0;
}      

讀端​

​sem_read.c​

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

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/types.h>

union semnum {
    int val;
    struct semid_ds *buf;
    unsigned short int *array;
    struct seminfo *__buf;
};
int sem_id;

void sem_init(int sem_id, int nsignum, int sem_value) {
    union semnum sem_union;
    sem_union.val = sem_value;
    if (semctl(sem_id,nsignum,SETVAL,sem_union) == -1) {
        printf("semctl failed\n");
        _exit(-1);
    }
}

void sem_p(int sem_id, int nsignum) {
    struct sembuf sops;
    sops.sem_num = nsignum;
    sops.sem_op = -1;
    sops.sem_flg = SEM_UNDO;

    if (semop(sem_id, &sops,1) == -1) {
        printf("semop P failed \n");
        _exit(-1);
    }
    
}

void sem_v(int sem_id, int nsignum) {
    struct sembuf sops;
    sops.sem_num = nsignum;
    sops.sem_op = 1;
    sops.sem_flg = SEM_UNDO;

    if (semop(sem_id, &sops,1) == -1) {
        printf("semop P failed \n");
        _exit(-1);
    }
}

void sem_print(int sem_id, int nsignum) {
    int sem_value;
    sem_value = semctl(sem_id, nsignum, GETVAL);
    printf("sem[%d] = %d \n",nsignum, sem_value);
}

int main() {
    int shm_id;
    key_t shm_key = ftok("./",111);
    key_t sem_key = ftok("./",112);

    shm_id = shmget(shm_key , 1028 , IPC_CREAT | 0666);
    char *shm_addr = shmat(shm_id, NULL , 0);

    sem_id = semget(sem_key,2,IPC_CREAT | 0666);
    if(sem_id == -1) {
        printf("semget failed\n");
        _exit(-1);
    }
    else {
        sem_init(sem_id,0,0); //初始化讀端信号量
        sem_init(sem_id,1,1); //初始化寫端信号量
    }
    
    while(1) {
        //對讀端信号量進行p操作,-1,接下來要從共享記憶體讀
        sem_p(sem_id,0);
        printf("from shm:%s\n",shm_addr);
        //讀完之後将寫端信号量進行v操作,+1,因為還有寫端程序需要寫入(p操作),防止信号量變為-1
        sem_v(sem_id,1);
    }
    return 0;
}