進程間通信--信號量+共享內存

信號量是一個計數器,常用於處理進程和線程的同步問題,特別是對臨界資源訪問的同步。
獲取一次信號量的操作就是對信號量減一,而釋放一次信號量的操作就是對信號量加一。
Linux內核爲每個信號集提供了一個semid_ds數據結構.該結構定義如下(linux/sem.h):

/* Obsolete, used only for backwards compatibility and libc5 compiles */
struct semid_ds {
    struct ipc_perm    sem_perm;        /* 對信號操作的許可權 */
    __kernel_time_t    sem_otime;        /*對信號操作的最後時間 */
    __kernel_time_t    sem_ctime;        /*對信號進行修改的最後時間 */
    struct sem    *sem_base;        /*指向第一個信號 */
    struct sem_queue *sem_pending;        /* 等待處理的掛起操作 */
    struct sem_queue **sem_pending_last;    /* 最後一個正在掛起的操作 */
    struct sem_undo    *undo;            /* 撤銷的請求 */
    unsigned short    sem_nsems;        /* 數組中的信號數 */
};


(1)Linux下使用系統函數創建和打開信號集.這個函數定義在頭文件sys/sem.h中,函數原型如下:
int semget(key_t key, int nsems, int semflg);
該函數執行成功則返回一個信號集的標識符,失敗返回-1。函數第一個參數由ftok()得到鍵值。第二個參數nsems指明要創建的信號集包含的信號個數,如果只是打開信號集,把nsems設置爲0即可;第三個參數semflg爲操作標誌,可以取如下值。
IPC_CREAT:調用semget時,它會將此值與其他信號集的key進行比較,如果存在相同的Key,說明信號集已經存在,此時返回給信號集的標識符,否則新建一個信號集並返回其標識符。
IPC_EXCL:該宏須和IPC_CREAT一起使用。使用IPC_CREAT|IPC_EXCL時,表示如果發現信號集已經存在,則返回錯誤,錯誤碼是EEXIST。
(2)信號量的操作,信號量的值和相應資源的使用情況有關,當它的值大於0時,表示當前可用資源的數量,當它的值小於0時,其絕對值表示等待使用該資源的進程個數.信號量的值僅能由PV操作來改變。在Linux下,PV操作通過調用函數semop實現。該函數定義在文件/sys/sem.h,原型如下:

int semop(int semid, struct sembuf *sops, size_t nsops);
參數semid爲信號集的標識符;參數sops指向進行操作的結構體數組首地址;參數nsops指出將要進行操作的信號的個數。semop函數調用成功返回0,否則返回-1.
sops參數的定義如下:(linux/sem.h)

struct  sembuf {
        ushort  sem_num; //信號在信號集的索引
        short   sem_op;  //操作類型
        short   sem_flg; //操作標誌
};

 表 1  sem_op的取值和意義
 取值範圍 操作意義 
 sem_op > 0  信號加上sem_op.表示進程釋放控制的資源
 sem_op = 0  如果沒有設置IPC_NOWAIT,則進程進入睡眠,直到信號值爲0;否則進程不會睡眠,直接返回EAGAIN
 sem_op < 0 信號加上sem_op的值,若沒有設置IPC_NOWAIT,則進程進入睡眠,直到資源可用。否則直接返回EAGAIN 

(3)信號量的控制,使用信號量時,往往需要對信號集進行一些控制操作,比如刪除信號集、對內核維護的信號集的數據結構semid_ds進行設置,獲取信號集中信號值等。通過semctl可以操作:(sys/sem.h)
int semctl(int semid, int semnum, int cmd,...);

函數中,參數semid爲信號集的標識符,參數semnum標識一個特定的信號;cmd指明控制操作的類型。最後的“...”說明函數的參數是可選的,它依賴於第3個參數cmd,它通過共用體變量semun選擇要操作的參數.semun定義在Linux/sem.h:

/* arg for semctl system calls. */
union semun {
    int val;            /* value for SETVAL */
    struct semid_ds *buf;    /* buffer for IPC_STAT & IPC_SET */
    unsigned short *array;    /* array for GETALL & SETALL */
    struct seminfo *__buf;    /* buffer for IPC_INFO */
    void *__pad;
};

以上各個字段含義如下:
1)val:僅用於SETVAL操作類型,設置某個信號的值等於val
2)buf:用於IPC_STAT和IPC_SET,存取semid_ds結構
3)array:用於SETALL和GETALL操作
4)buf:爲控制IPC_INFO提供的緩存
cmd的宏含義如下:
IPC_SET:對信號量的屬性進行設置
IPC_RNID:刪除semid指定的信號集
GETPID:返回最後一個執行semop操作的進程ID
GETVAL:返回信號集semnum指定信號的值。
GETALL:返回信號集中所用信號的值.
GETNCNT:返回正在等待資源的進程的數量.
GETZCNT:返回正在等待完全空閒資源的進程的數量.
SETVAL:設置信號集中semnum指定的信號的值
SETALL:設置信號集中所用信號的值.

共享內存:
共享內存就是分配一塊能被其它進程訪問的內存。每個共享內存段在內核中維護着一個內部結構shmid_ds:(linux/shm.h)

/* Obsolete, used only for backwards compatibility and libc5 compiles */
struct shmid_ds {
    struct ipc_perm        shm_perm;    /* 操作許可 */
    int            shm_segsz;    /* 共享內存大小,字節爲單位 */
    __kernel_time_t        shm_atime;    /* 最後一個進程訪問共享內存的時間 */
    __kernel_time_t        shm_dtime;    /* 最後一個進程離開共享內存的時間 */
    __kernel_time_t        shm_ctime;    /* 最後一次修改共享內存的時間 */
    __kernel_ipc_pid_t    shm_cpid;    /* 創建共享內存的進程ID */
    __kernel_ipc_pid_t    shm_lpid;    /* 最後操作共享內存的進程ID */
    unsigned short        shm_nattch;    /* 當前使用該貢獻內存的進程數量 */
    unsigned short         shm_unused;    /* compatibility */
    void             *shm_unused2;    /* ditto - used by DIPC */
    void            *shm_unused3;    /* unused */
};


(1)創建共享內存:(linux/shm.h)

int shmget(key_t key, size_t size, int shmglf);
參數key和shmflg可以參考semflg的參數。size是以字節爲單位指定的內存的大小。

(2)共享內存的操作:(linux/shm.h)
void *shmat (int shmid, const void *shmaddr, int shmflg);
在使用共享內存前,必須通過shmat函數將其附加到進程的地址空間。shmat調用成功後會返回一個指向共享內存區域的指針,使用該指針就可以訪問共享內存了。如果失敗返回-1。
shmat參數shmid是shmget的返回值。參數shmflg爲存取權限標誌;參數shmaddr爲共享內存的附加點。參數shmaddr不同取值情況說明如下:
1)如果爲空,則由內核選取一個空閒的內存區;否則,返回地址取決於調用者是否給shmflg設置了SHM_RND值,如果沒有指定,則共享內存區附加到由shmaddr指定的地址,否則附加地址爲shmaddr向下舍入一個共享內存底端邊界地址後的地址。
當進程結束使用共享內存時,要通過函數斷開與共享內存的鏈接。
(sys/shm.h):
int shmdt(const void *shmaddr);
參數shmaddr爲shmat的返回值,該函數調用成功後,返回0,否則返回-1。
(3)共享內存的控制。(sys/shm.h):
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
參數shmid爲共享內存區的標識符,即shmget函數的返回值.buf是指向shmid_ds結構體的指針;cmd爲操作標誌位,支持以下3種操作:
1)IPC_RMID : 從系統中刪除由shmid指向的共享內存區
2)IPC_SET:設置共享內存的shmid_ds結構
3)IPC_STAT:讀取共享內存區的shmid_ds機構,並將其存儲到buf指向的地址。

這裏是以簡單的讀者和寫者爲例,來學習信號量和共享內存。
整個程序的設計規定如下:
1、首先讓寫者獲取信號量,去寫臨界區,這是的臨界區就是共享內存。完成後釋放掉信號量。
2、當讀者獲取信號量後,就去讀臨界區的數據,讀出數據完成後,釋放掉信號量。

以上讀者和寫者分別是兩個進程,代碼如下:
src/shm_write.c (寫者代碼):

#include "shm_mem.h"

int main(int argc, char** argv)
{
    int semid, shmid;
    char *shmaddr;
    char write_str[SHM_SIZE];
    char *ret;
    if((shmid = creatshm(".", 57, SHM_SIZE)) == -1) //創建或者獲取共享內存
        return -1;

/*建立進程和共享內存連接*/
    if((shmaddr = shmat(shmid, (char*)0, 0)) == (char *)-1){
        perror("attch shared memory error!\n");
        exit(1);
    }    
    if((semid = creatsem("./", 39, 1, 1)) == -1)//創建信號量
        return -1;
    while(1){
        wait_sem(semid, 0);//等待信號量可以被獲取
        sem_p(semid, 0);  //獲取信號量

/***************寫共享內存***************************************************/
        printf("write : ");
        ret = fgets(write_str, 1024, stdin);
        if(write_str[0] == '#') // '#'結束讀寫進程
            break;
        int len = strlen(write_str);
        write_str[len] = '\0';
        strcpy(shmaddr, write_str);

/****************************************************************************/
        sem_v(semid, 0); //釋放信號量
        usleep(1000);  //本進程睡眠.
    }
    sem_delete(semid); //把semid指定的信號集從系統中刪除
    deleteshm(shmid);   //從系統中刪除shmid標識的共享內存
    return 0;
}


src/shm_read.c (讀者代碼):

#include "shm_mem.h"

int main(int argc, char** argv)
{
    int semid, shmid;
    char *shmaddr;
    printf("What!!!!!!!!!!!\n");
    if((shmid = creatshm(".", 57, SHM_SIZE)) == -1)
        return -1;
    if((shmaddr = shmat(shmid, (char*)0, 0)) == (char *)-1){
        perror("attch shared memory error!\n");
        exit(1);
    }
    if((semid = opensem("./", 39)) == -1)
        return -1;
    printf("read start....................\n");        
    while(1){
        printf("read : ");
        wait_sem(semid, 0);  //等待信號量可以獲取
        if(sem_p(semid, 0) == -1) 獲取信號量失敗退出。當寫者寫入'#'時表示退出
            break;
        printf("%s", shmaddr);

        sem_v(semid, 0);
        usleep(1000);
    }    
    return 0;
}


shm_mem.h:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <error.h>

#define SHM_SIZE     1024

union semun{
    int val;
    struct semid_ds *buf;
    unsigned short *array;
    struct seminfo *buf_info;
    void *pad;
};

/* 創建信號量函數*/
int creatsem(const char *pathname, int proj_id, int members, int init_val)
{
    key_t msgkey;
    int index, sid;
    union semun semopts;
    
    if((msgkey = ftok(pathname, proj_id)) == -1){
        perror("ftok error!\n");
        return -1;
    }
    if((sid = semget(msgkey, members, IPC_CREAT|0666)) == -1){
        perror("semget call failed.\n");
        return -1;
    }
    semopts.val = init_val;
    for(index = 0; index < members; index++){
        semctl(sid, index, SETVAL, semopts);
    }
    
    return sid;
}

int opensem(const char *pathname, int proj_id)
{
    key_t msgkey;
    int sid;
    
    if((msgkey = ftok(pathname, proj_id)) == -1){
        perror("ftok error!\n");
        return -1;
    }
    
    if((sid = semget(msgkey, 0, 0666)) == -1){
        perror("open semget call failed.\n");
        return -1;
    }
    return sid;
}

/* p操作, 獲取信號量*/
int sem_p(int semid, int index)
{
    //struct sembuf sbuf = {0, -1, SEM_UNDO};

    struct sembuf sbuf = {0, -1, IPC_NOWAIT};
    if(index < 0){
        perror("index of array cannot equals a minus value!\n");
        return -1;
    }
    sbuf.sem_num = index;
    if(semop(semid, &sbuf, 1) == -1){
        perror("A wrong operation to semaphore occurred!\n");
        return -1;
    }
    return 0;
}

/* V操作, 釋放信號量*/
int sem_v(int semid, int index)
{
    //struct sembuf sbuf = {0, 1, SEM_UNDO};

    struct sembuf sbuf = {0, 1, IPC_NOWAIT};
    if(index < 0){
        perror("index of array cannot equals a minus value!\n");
        return -1;
    }
    sbuf.sem_num = index;
    if(semop(semid, &sbuf, 1) == -1){
        perror("A wrong operation to semaphore occurred!\n");
        return -1;
    }
    return 0;
}

/* 刪除信號量幾*/
int sem_delete(int semid)
{
    return (semctl(semid, 0, IPC_RMID));
}

/* 等待信號量爲1*/
int wait_sem(int semid, int index)
{
    while(semctl(semid, index, GETVAL, 0) == 0)
    {
        //wait_num++;

        usleep(500);
    }
    //printf("wait_num = %x\n", wait_num);

    return 1;

}

/* 創建共享內存*/
int creatshm(char *pathname, int proj_id, size_t size)
{
    key_t shmkey;
    int sid;
    
    if((shmkey = ftok(pathname, proj_id)) == -1){
        perror("ftok error!\n");
        return -1;
    }
    if((sid = shmget(shmkey, size, IPC_CREAT|0666)) == -1){
        perror("shm call failed!\n");
        return -1;
    }
    return sid;
}

/* 刪除共享內存*/
int deleteshm(int sid)
{
    void *p = NULL;
    return (shmctl(sid, IPC_RMID, p));
}


<script type=text/javascript charset=utf-8 src="http://static.bshare.cn/b/buttonLite.js#style=-1&uuid=&pophcol=3&lang=zh"></script> <script type=text/javascript charset=utf-8 src="http://static.bshare.cn/b/bshareC0.js"></script>
閱讀(1210) | 評論(3) | 轉發(0) |
給主人留下些什麼吧!~~
78_avatar_small.jpg

lanlovehua2010-11-18 10:23:12

在shm_mem.h裏的int creatsem(const char *pathname, int proj_id, int members, int init_val)函數裏修改如下: //union semun semopts; union semun semopts_x; 試一試。

78_avatar_small.jpg

lanlovehua2010-11-18 10:05:35

這個是頭文件的問題,請看下面的文章: http://topic.csdn.net/t/20060714/15/4880830.html

00_avatar_small.jpg

chinaunix網友2010-11-16 11:04:07

謝謝你的代碼 ,但我運行的時候有個錯, error: storage size of ‘semopts’ isn’t known??

評論熱議
發佈了93 篇原創文章 · 獲贊 3 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章