ftok函数_Linux进程间通信详解(五) 信号量及函数

本文深入介绍了信号量的概念,作为进程间通信的一种方式,用于资源的同步与互斥访问。详细阐述了单值与多值信号量的区别,并展示了如何通过semget()和semop()函数创建、操作信号量集。同时,通过示例程序解释了ftok()生成键值的原理,以及服务器-客户端模式下如何使用信号量进行资源管理。此外,还提供了一个获取信号量信息及实现资源申请与释放的示例代码,帮助理解信号量在实际编程中的应用。
摘要由CSDN通过智能技术生成

d1b62e413bb86e3a6f214fe1a918cd63.png

信号量的概念

信号量,或称信号灯,其原理是一种数据操作锁的概念,本身不具备数据交换的功能,它负责协调各个进程,保证保证两个或多个关键代码段不被并发调用,确保公共资源的合理使用。信号量分为单值和多值两种。

在信号的实际应用中,是不能只定义一个信号量的,而只能定义一个信号量集,其中包含一组信号量,同一信号量集中的信号量使用同一个引用ID,这样的设置是为了多个资源或同步操作的需要。每个信号量集都有一个与之对应的结构,其中记录了信号量集的各种信息,该结构定义为:

struct semid_ds
{
    struct ipc_perm sem_perm;   // operation permission struct
    struct sem *sem_base;       // ptr to first semaphore in set
    unsigned short sem_nsems;   // numbers of semaphore in set
    time_t sem_otime;    // last semop time
    time_t sem_ctime;    // last change time
};

ipc_perm结构体包含了信号量集的键值、所有者信息及操作权限等,定义如下:

struct ipc_perm
{
    __key_t __key;    // key value
    __uid_t uid;      // owner uid
    __gid_t gid;      // owner gid
    __uid_t cuid;     // creater uid
    __gid_t cgid;     // creater gid
    unsigned short int mode;    // read/write permission
    unsigned short int __seq;   // seq number
    ......
};

sem结构体记录了一个信号量的信息,定义如下:

struct sem
{
    unsigned short semval;    // semaphore value, always >= 0
    pid_t sempid;             // pid for last operation
    unsigned short semncent;  // numbers of processes awaiting semval > currval
    unsigned short semzcnt;   // numbers of processes awaiting semval = 0
};

信号量集的相关操作

创建/打开信号量集

使用semget()函数创建或打开一个信号量集,其函数原型为:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int semget(key_t key, int nsems, int flag);

运行成功返回信号量集的ID号,失败返回-1。

参数key为一个键值,可通过ftok()函数生成,参数nsems为创建的信号量集中包含的信号量个数,参数flag为操作参数,具体使用方法与创建共享内存的shmget()使用类似。

对信号量集的操作

使用semop()函数操作一个信号量集,其函数原型为:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int semop(int semid, struct sembuf semoparray[], size_t nops);

运行成功返回0,失败返回-1。

参数semid为semget()函数返回的信号量集ID号,参数semoparray是一个struct sembuf结构类型的数组,参数nops为前一数组参数的元素个数,sembuf的定义如下:

struct sembuf
{
    unsigned short sem_num;
    short sem_op;
    short sem_flg;
}

其中sem_num是信号集中的某一资源(即指定操作的信号量),其取值为0到资源总数(ipc_perm.sem_nsems)之间的整数;sem_op为一个整数,指明要执行的操作;sem_flg说明函数semop()的行为。

  • sem_op > 0:表示进程对资源使用完毕,释放相应的资源数,并将sem_op的值加到信号量的值上。
  • sem_op = 0:进程阻塞直到信号量的相应值为0,当信号量已经为0,函数立即返;信号量值不为0,则依据sem_flgIPC_NOWAIT位决定函数动作:
  • sem_flg指定IPC_NOWAIT,则semop()函数出错返回EAGAIN
  • sem_flg没有指定IPC_NOWAIT,则将信号量的semncnt的值减1,然后进程挂起直到下述情况发生:
    • 信号量为0,则将信号量的semncnt的值加1,函数semop()成功返回;
    • 信号量被删除,函数semop()出错返回EIDRM
    • 进程捕捉到信号,并从信号处理函数返回,信号量的semncnt的值减1,函数semop()出错返回EINTR
  • sem_op < 0:请求sem_op的绝对值的资源,若相应的资源数可以满足请求,则将该信号量的值减去sem_op的绝对值,函数成功返回;若资源数不足,这个操作与sem_flg有关:
  • sem_flg指定IPC_NOWAIT,则semop()函数出错返回EAGAIN
  • sem_flg没有指定IPC_NOWAIT,则将信号量的semncnt的值加1,然后进程挂起直到下述情况发生:
    • 资源数满足请求,信号值减去sem_op的绝对值,成功返回;
    • 信号量被删除,函数semop()出错返回EIDRM
    • 进程捕捉到信号,并从信号处理函数返回,信号量的semncnt的值减1,函数semop()出错返回EINTR

信号量集的控制

与共享内存的控制相似,信号量集也有自己的控制函数semctl(),函数原型如下:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int semctl(int semid, int semnum, int cmd, union semun arg);

运行成功返回大于等于0的值,失败返回-1,并设置错误变量errno。

参数semid为semget()函数返回的信号量集ID号,参数semnum指定信号量集中的某一信号量,类似于下标索引,参数cmd定义函数的操作,具体含义与后面的参数arg有关,arg是一个结构体,定义如下:

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

cmd的取值含义如下:

f64fa925d9e6a9abc09372c5a77452b6.png

编程示例

示例1:信号量创建(内含ftok生成key原理)

创建信号量示例,sem_create.c:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
#include <sys/stat.h>
#include <stdlib.h>
#define PATHNAME "/tmp"

int main(int argc, char *argv[])
{
    key_t keyid;
    int sigid;
    int nsems, semflg;
    nsems = 1;
    semflg = IPC_CREAT|0666;

    // below 4 lines code not necessary, just show the PATHNANE -> dev & inode,
    // then combine with proj_id to calculate the keyid (ftok work principle)
    struct stat stat_info;
    stat(PATHNAME, &stat_info);
    printf("dev:%lxn", stat_info.st_dev);
    printf("inode:%lxn", stat_info.st_ino);

    // generate key
    if((keyid = ftok(PATHNAME, 1)) == -1)
    {
        perror("ftok() failedn");
        exit(1);
    }
    else
        printf("key is %xn", keyid);

    // create a semaphore
    if((sigid = semget(keyid, nsems, semflg)) == -1)
    {
        perror("semget() failedn");
        exit(1);
    }
    printf("sem create ok!n");

    return 0;
}

运行结果:

$ ./sem_create
dev:804
inode:280001
key is 1040001
sem create ok!

这里说明一下ftok()函数生成key的原理ftok()定义如下:

key_t ftok(const char *pathname, int proj_id);

首先根据一个任意存在的pathname路径提取其所属文件系统的特有信息,包括设备号(stat.st_dev)inode号(stat.st_ino),其获取方法参见上述程序中的sata结构,然后再结合ftok()函数的proj_id参数,按照如下规则计算key:

key1 = stat.st_ino & 0xffff;   // 保留低16位
key2 = stat.st_dev & 0xff;     // 保留低8位
key2 << = 16;                  // 左移16位
key3 = proj_id & 0xff;         // 保留低8位
key3 << = 24;                  // 左移24位
key = key1|key2|key3;          // 三者进行或运算

本例中,pathname=’/tmp‘,ftok()函数提取的设备号为0x804,inode号为0x280001,proj_id为0x01,根据以上运算过程,key1=0x01,key2=0x40000,key3=0x1000000,三者求或得到key=0x1040001,与程序输出的结果一致。

示例2:服务器-客户端模式

服务器端,信号量的创建与初始化,监视信号量资源使用情况,sem_server.c:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define PATHNAME "/tmp"
#define RESOURCE 3

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

int main(int argc, char *argvp[])
{
    key_t keyid;
    int sigid;
    int nsems, semflg;
    union semun semopt;
    struct sembuf sbuf = {0, 0, SEM_UNDO};
    nsems = 1;
    semflg = IPC_CREAT|0666;

    // generate key
    if((keyid = ftok(PATHNAME, 3)) == -1)
    {
        perror("ftok() failedn");
        exit(1);
    }
    else
        printf("key is %xn", keyid);

    // create a semaphore
    if((sigid = semget(keyid, nsems, semflg)) == -1)
    {
        perror("semget() failedn");
        exit(1);
    }
    else
        printf("create semaphore okn");

    // initial the semaphore
    semopt.val = RESOURCE;
    if(semctl(sigid, 0, SETVAL, semopt) == -1)
    {
        perror("semctl() failedn");
        exit(1);
    }
    else
        printf("initial the semphore okn");

    // monitor the semphore usage status
    while(1)
    {
        if(semop(sigid, &sbuf, 1) == 0)// use up!!!
            printf("resources have been exhaustedn");
        sleep(3);
    }
    return 0;
}

客户端程序,使用按键pv模拟P操作和V操作,sem_client.c:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define PATHNAME "/tmp"
#define RESOURCE 3

int main(int argc, char *argvp[])
{
    key_t keyid;
    int sigid;
    int nsems, semflg;
    char opt;
    struct sembuf pbuf = {0, -1, IPC_NOWAIT}; // P operate
    struct sembuf vbuf = {0, 1, IPC_NOWAIT}; // V operate
    nsems = 1;
    semflg = IPC_CREAT|0666;

    // generate key
    if((keyid = ftok(PATHNAME, 3)) == -1)
    {
        perror("ftok() failedn");
        exit(1);
    }
    else
        printf("key is %xn", keyid);

    // get the semaphore
    if((sigid = semget(keyid, nsems, semflg)) == -1)
    {
        perror("semget() failedn");
        exit(1);
    }
    else
        printf("get the semaphore okn");

    // use p/v to simulate the PV operation
    while(1)
    {
        opt = getchar();
        switch(opt)
        {
            case 'p':// --
                    if(semop(sigid, &pbuf, 1) == -1)
                        printf("-->resources have been exhaustedn");// exhausted
                    else
                        printf("-->Total unused resources:%dn", semctl(sigid, 0, GETVAL, 0));
                    break;
            case 'v':// ++
                    if(semop(sigid, &vbuf, 1) == -1)
                        printf("-->V opreation failedn");
                    else if(semctl(sigid, 0, GETVAL, 0) > RESOURCE)
                    {
                        printf("-->resources to achieve maximumn");// achieve maximum
                        semop(sigid, &pbuf, 1);
                    }
                    else
                        printf("-->Total unused resources:%dn", semctl(sigid, 0, GETVAL, 0));
                    break;
            case 's':// show
                    printf("-->Total unused resources:%dn", semctl(sigid, 0, GETVAL, 0));
                    break;
            case 'q':// quit
                    exit(0);
        }
        sleep(1);
    }
    return 0;
}

编译上面两个程序并执行,先运行服务器端程序:

$ ./sem_server
key is 3040001
create semaphore ok
initial the semphore ok

可以看到信号量创建并初始化成功,进入监视状态,在另一个shell中运行客户端程序:

$ ./sem_client
key is 3040001
get the semaphore ok
p
-->Total unused resources:2
p
-->Total unused resources:1
p
-->Total unused resources:0
v
-->Total unused resources:1
s
-->Total unused resources:1
q

运行后,使用键盘的pv键入shell,继续观察服务器端的结果,当客户端多次使用p键使资源耗尽后,服务器端会每隔3秒进行相应的输出提示报警,客户端使用v键增加资源,服务器端停止报警。

$ ./sem_server
key is 3040001
create semaphore ok
initial the semphore ok
resources have been exhausted
resources have been exhausted
resources have been exhausted

最后可以使用Ctrl+c终止服务器端程序。

示例3

获取各种信号量的信息,利用信号量实现共享资源的申请和释放,sem_app.c:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <errno.h>
#include <sys/sem.h>
#include <stdlib.h>
#include <unistd.h>
#define SEM_PATH "."
#define max_tries 3

int semid;

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

int main(void)
{
    int flag1, flag2, key, i, init_ok, tmperrno;
    struct semid_ds sem_info;
    struct seminfo sem_info2;
    union semun arg;
    struct sembuf askfor_res, free_res;
    flag1 = IPC_CREAT|IPC_EXCL|00666;
    flag2 = IPC_CREAT|00666;

    // generate key
    key = ftok(SEM_PATH, 'a');
    if(key == -1)
    {
        perror("ftok error");
        exit(1);
    }

    // create a semaphore
    init_ok = 0;
    semid = semget(key, 1, flag1);
    if(semid < 0)
    {
        tmperrno = errno;
        perror("semget");
        if(tmperrno == EEXIST)
        {
            //
            semid = semget(key, 1, flag2);
            arg.buf = &sem_info;
            for(i=0; i<max_tries; i++)
            {
                if(semctl(semid, 0, IPC_STAT, arg) == -1)
                {
                    perror("semctl error");
                    i = max_tries;
                }
                else
                {
                    if(arg.buf->sem_otime!=0)
                    {
                        i = max_tries;
                        init_ok = 1;
                    }
                    else
                        sleep(1);
                 }
             }
            if(!init_ok)
            {
                arg.val = 1;
                if(semctl(semid, 0, SETVAL, arg) == -1)
                    perror("semctl setval error");
            }
        }
        else
        {
            perror("semget error, process exit");
            exit(1);
        }
    }
    else // semid >= 0, do some initializing
    {
        arg.val = 1;
        if(semctl(semid, 0, SETVAL, arg) == -1)
            perror("semctl setval error");
    }

    // get some information about the semaphore and the limit of semaphore in Linux
    arg.buf = &sem_info;
    if(semctl(semid, 0, IPC_STAT, arg) == -1)
        perror("semctl IPC_STAT");
    printf("owner's uid is %dn", arg.buf->sem_perm.uid);
    printf("owner's gid is %dn", arg.buf->sem_perm.gid);
    printf("creater's uid is %dn", arg.buf->sem_perm.cuid);
    printf("creater's gid is %dn", arg.buf->sem_perm.cgid);

    arg.__buf = &sem_info2;
    if(semctl(semid, 0, SEM_INFO, arg) == -1)
        perror("semctl IPC_INFO");
    printf("the number of entries in semaphore map is %dn", arg.__buf->semmap);
    printf("max number of semaphore identifiers is %dn", arg.__buf->semmni);//o);
    printf("mas number of semaphore in system is %dn", arg.__buf->semmns);
    printf("the number of undo structures system wide is %dn", arg.__buf->semmnu);
    printf("max number of semaphore per semid is %dn", arg.__buf->semmsl);
    printf("max number of ops per semop call is %dn", arg.__buf->semopm);
    printf("max number of undo entries per process is %dn", arg.__buf->semume);
    printf("the size of struct sem_undo is %dn", arg.__buf->semusz);
    printf("the maximum semaphore value is %dn", arg.__buf->semvmx);

    // now ask for available resource
    askfor_res.sem_num = 0;
    askfor_res.sem_op = -1;
    askfor_res.sem_flg = SEM_UNDO;
    if(semop(semid, &askfor_res, 1) == -1)
        perror("semop error");

    sleep(3);
    printf("now free the resourcen");

    // now free resource
    free_res.sem_num = 0; // resources num
    free_res.sem_op = 1;
    free_res.sem_flg = SEM_UNDO;
    if(semop(semid, &free_res, 1) == -1)
        if(errno == EIDRM)
            printf("the semaphore set was removedn");

    if(semctl(semid, 0, IPC_RMID) == -1)
        perror("semctl IPC_RMID");
    else
        printf("remove sem okn");

    return 0;
}

编译运行:

$ ./sem_app
owner's uid is 1000
owner's gid is 1000
creater's uid is 1000
creater's gid is 1000
the number of entries in semaphore map is 1024000000
max number of semaphore identifiers is 32000
mas number of semaphore in system is 1024000000
the number of undo structures system wide is 1024000000
max number of semaphore per semid is 32000
max number of ops per semop call is 500
max number of undo entries per process is 500
the size of struct sem_undo is 1
the maximum semaphore value is 32767
now free the resource
remove sem okt

参考:

  1. 《精通Linux C编程》- 程国钢
  2. 《Linux C编程完全解密》- 闫敬 吴淑坤
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值