歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux管理 >> Linux網絡 >> linux網絡編程之System V信號量(一) 封裝一個信號量集操作函數的工具

linux網絡編程之System V信號量(一) 封裝一個信號量集操作函數的工具

日期:2017/3/3 16:26:19   编辑:Linux網絡

與消息隊列和共享內存一樣,信號量集也有自己的數據結構:

struct semid_ds {

struct ipc_perm sem_perm; /* Ownership and permissions */

time_t sem_otime; /* Last semop time */

time_t sem_ctime; /* Last change time */

unsigned short sem_nsems; /* No. of semaphores in set */

};

同樣地,第一個條目也是共有的ipc 對象內核結 構,剩下的是私有成員。

Each semaphore in a semaphore set has the following associated values:

unsigned short semval; /* semaphore value */

unsigned short semzcnt; /* # waiting for zero */

unsigned short semncnt; /* # waiting for increase */

pid_t sempid; /* process that did last op */

即每一個在信號集中的信號量都有上述4個相關的變量。

1、semval :當前某信號量的資源數目

2、semzcnt:當sem_op(見 struct sembuf)為0,且semop 函數沒有設置IPC_NOWAIT 標志,且當前semval 不為0 ,此時進程會阻塞等待直到4個事件發生,具體可man 2 semop 一下,然後semzcnt 會加1,表示等待這個信號量的資源變為 0的進程數加1。

3、semncnt:當sem_op(見 struct sembuf)< 0,且semop 函數沒有設置IPC_NOWAIT 標志,且 當前semval < |sem_op| ,此時進程會阻塞等待直到4個事件發生,具體可man 2 semop 一下,然後semncnt 會加1,表 示等待這個信號量的資源增加的進程數加1。

4、當正確執行了semop 函數,則每個信號量的sempid 參數都被設置為 改變此信號量的進程pid。

以下是幾個信號量集操作函數:

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);

int semctl (int semid, int semnum, int cmd, ...);

int semop(int semid, struct sembuf *sops, unsigned nsops);

功 能:用來創建和訪問一個信號量集

原型int semget(key_t key, int nsems, int semflg);

參數

key: 信號集的名 字

nsems:信號集中信號量的個數

semflg: 由九個權限標志構成,它們的用法和創建文件時使用的mode模式標志是一樣 的

返回值:成功返回一個非負整數,即該信號集的標識碼;失敗返回-1

功能:用於控制信號量集

原型int semctl(int semid, int semnum, int cmd, ...);

參數

semid:由semget返回的信號集標識碼

semnum:信號集中信號 量的序號,從0開始編號

cmd:將要采取的動作(有三個可取值)

最後一個參數是 union semun,具體成員根據cmd 的 不同而不同

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 (Linux- specific) */

};

返回值:成功返回0;失敗返回-1

cmd 取值如下:

SETVAL 設置信號量集中的信號量的計數值

GETVAL 獲取信號量集中的信號量的計數值

IPC_STAT把semid_ds結 構中的數據設置為信號集的當前關聯值

IPC_SET在進程有足夠權限的前提下,把信號集的當前關聯值設置為semid_ds數據 結構中給出的值

IPC_RMID刪除信號集

功能:用來創建和訪問一個信號量集

原型int semop(int semid, struct sembuf *sops, unsigned nsops);

參數

semid:是該信號量集的標識碼,也就是semget函數的返回值

sops:是個指向 一個結構體的指針

nsops:信號量的個數

返回值:成功返回0;失敗返回-1

struct sembuf

{

unsigned short sem_num; /* semaphore number */

short sem_op; /* semaphore operation */

short sem_flg; /* operation flags */

};

sem_num: 是信號量的編號。

sem_op:是信號量一次PV操作時加減的數值,一般只會用到兩個值,一個是“-1”,也就是P操作,等 待信號量變得可用;另一個是“+1”,也就是我們的V操作,發出信號量已經變得可用。當然+-n 和0 都是允許的。需要注 意的是只有+n 才確保將semval +n 後馬上返回,而-n 和 0 很可能是會阻塞的,見文章上面的分析,+-n 需要進程對信號 集有寫的權限,而0 只需要讀的權限。

sem_flag:的兩個取值是IPC_NOWAIT或SEM_UNDO,設為前者如果當某個信號 量的資源為0時進行P操作,此時不會阻塞等待,而是直接返回資源不可用的錯誤;設為後者,當退出進程時對信號量資源的 操作撤銷;不關心時設置為0即可。

當要對一個信號量集中的多個信號量進行操作時,sops 是結構體數組的指針, 此時nsops 不為1。此時對多個信號量的操作是作為一個單元原子操作,要麼全部執行,要麼全部不執行。

下面來封 裝一個信號量集操作函數的工具:

semtool.c

#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
    
#define ERR_EXIT(m) \
        do \
        { \

                perror(m); \
                exit(EXIT_FAILURE); \
        } while(0)
    
union semun
{
    int val;                  /* value for SETVAL */
    struct semid_ds *buf;     /* buffer for IPC_STAT, IPC_SET */
    unsigned short *array;    /* array for GETALL, SETALL */
    /* Linux specific part: */
    struct seminfo *__buf;    /* buffer for IPC_INFO */
};
    
int sem_create(key_t key)
{
    int semid = semget(key, 1, 0666 | IPC_CREAT | IPC_EXCL);
    if (semid == -1)
        ERR_EXIT("semget");
    
    return semid;
}
    
int sem_open(key_t key)
{
    int semid = semget(key, 0, 0);
    if (semid == -1)
        ERR_EXIT("semget");
    
    return semid;
}
    
int sem_p(int semid)
{
    struct sembuf sb = {0, -1, /*IPC_NOWAIT*/SEM_UNDO};
    int ret = semop(semid, &sb, 1);
    if (ret == -1)
        ERR_EXIT("semop");
    
    return ret;
}
    
int sem_v(int semid)
{
    struct sembuf sb = {0, 1, /*0*/SEM_UNDO};
    int ret = semop(semid, &sb, 1);
    if (ret == -1)
        ERR_EXIT("semop");
    
    return ret;
}
    
int sem_d(int semid)
{
    int ret = semctl(semid, 0, IPC_RMID, 0);
    if (ret == -1)
        ERR_EXIT("semctl");
    return ret;
}
    
int sem_setval(int semid, int val)
{
    union semun su;
    su.val = val;
    int ret = semctl(semid, 0, SETVAL, su);
    if (ret == -1)
        ERR_EXIT("semctl");
    
    printf("value updated...\n");
    return ret;
}
    
int sem_getval(int semid)
{
    int ret = semctl(semid, 0, GETVAL, 0);
    if (ret == -1)
        ERR_EXIT("semctl");
    
    printf("current val is %d\n", ret);
    return ret;
}
    
int sem_getmode(int semid)
{
    union semun su;
    struct semid_ds sem;
    su.buf = &sem;
    int ret = semctl(semid, 0, IPC_STAT, su);
    if (ret == -1)
        ERR_EXIT("semctl");
    
    printf("current permissions is %o\n", su.buf->sem_perm.mode);
    return ret;
}
    
int sem_setmode(int semid, char *mode)
{
    union semun su;
    struct semid_ds sem;
    su.buf = &sem;
    
    int ret = semctl(semid, 0, IPC_STAT, su);
    if (ret == -1)
        ERR_EXIT("semctl");
    
    printf("current permissions is %o\n", su.buf->sem_perm.mode);
    sscanf(mode, "%o", (unsigned int *)&su.buf->sem_perm.mode);
    ret = semctl(semid, 0, IPC_SET, su);
    if (ret == -1)
        ERR_EXIT("semctl");
    
    printf("permissions updated...\n");
    
    return ret;
}
    
void usage(void)
{
    fprintf(stderr, "usage:\n");
    fprintf(stderr, "semtool -c\n");
    fprintf(stderr, "semtool -d\n");
    fprintf(stderr, "semtool -p\n");
    fprintf(stderr, "semtool -v\n");
    fprintf(stderr, "semtool -s <val>\n");
    fprintf(stderr, "semtool -g\n");
    fprintf(stderr, "semtool -f\n");
    fprintf(stderr, "semtool -m <mode>\n");
}
    
int main(int argc, char *argv[])
{
    int opt;
    
    opt = getopt(argc, argv, "cdpvs:gfm:");
    if (opt == '?')
        exit(EXIT_FAILURE);
    if (opt == -1)
    {
        usage();
        exit(EXIT_FAILURE);
    }
    
    key_t key = ftok(".", 's');
    int semid;
    switch (opt)
    {
    case 'c':
        sem_create(key);
        break;
    case 'p':
        semid = sem_open(key);
        sem_p(semid);
        sem_getval(semid);
        break;
    case 'v':
        semid = sem_open(key);
        sem_v(semid);
        sem_getval(semid);
        break;
    case 'd':
        semid = sem_open(key);
        sem_d(semid);
        break;
    case 's':
        semid = sem_open(key);
        sem_setval(semid, atoi(optarg));
        break;
    case 'g':
        semid = sem_open(key);
        sem_getval(semid);
        break;
    case 'f':
        semid = sem_open(key);
        sem_getmode(semid);
        break;
    case 'm':
        semid = sem_open(key);
        sem_setmode(semid, argv[2]);
        break;
    }
    
    return 0;
}

首先來介紹一個getopt 函數, int getopt(int argc, char * const argv[],const char *optstring);

可以解析命令行選項參數,前兩個參數由main 函數傳遞,第三個參數是一個字符串集,即解析命令行參數看是否存在這些 字符。如./semtool -s 3 則s為選項,3為選項參數,optarg 是一個全局指針變量 extern char *optarg; 通 過atoi(optarg) 可以獲取數字3。

"cdpvs:gfm:" 表示選項s 和 m 後面可接參數,我們未使用一個while 循環去解析命令行參數,即這些選項只能同時出現一個,當未使用選項時打印輸出使用方法。

根據解析到的選項來 調用不同的函數,這些函數內部都調用了原始的信號量集操作函數,參照函數解釋都不難理解。

需要注意一點是, 這裡為了只創建一個信號量集,只對這個信號量集的信號量進行操作,在sem_create 中指定了IPC_EXCL 選項,即當key 已 存在時返回錯誤,不再創建信號量集,而我們使用了ftok 函數產生一個唯一的key,傳入的參數一定,則每次產生的key 值 一樣,當第二次次執行./semtool -c ,會返回file exist 的錯誤,當然先刪除當前信號量集,再create 是可以的,此時 雖然key 還是一樣的,但返回的semid 是不同的。

且這個唯一的信號量集中只有唯一的一個信號量,即0號信號量, 我們只對這個信號量進行PV操作。

使用舉例如下:

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ipcs -s

------ Semaphore Arrays ---- ----

key semid owner perms nsems

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./semtool

usage:

semtool -c

semtool -d

semtool -p

semtool -v

semtool -s <val>

semtool - g

semtool -f

semtool -m <mode>

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./semtool -c

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ipcs -s

------ Semaphore Arrays --------

key semid owner perms nsems

0x730135db 98304 simba 666 1

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./semtool -v

current val is 1

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./semtool -v

current val is 1

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./semtool -s 3

value updated...

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./semtool -g

current val is 3

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./semtool -p

current val is 2

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./semtool -m 600

current permissions is 666

permissions updated...

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./semtool - d

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ipcs -s

------ Semaphore Arrays ------- -

key semid owner perms nsems

因為我們在PV操作中指定了SEM_UNDO 選項,當進程退出時撤銷操作,所以連續執行兩 次V操作後信號量的資源還是為0(創建後信號量默認資源為0,不一定所有系統實現都會如此,應該顯式地初始化為0)。通 過-s 可以設置信號量的資源數。ipcs -s 輸出中的nsems 表示信號量的個數,當前只有一個;./semtool -v 輸出中的 current value 表示這個信號量的資源數。

Copyright © Linux教程網 All Rights Reserved