Linux进程间通信(五) - 信号灯(史上最全)及其经典应用案例

信号灯概述

什么是信号灯

信号灯用来实现同步,用于多线程,多进程之间同步共享资源(临界资源)。

PV原语:信号灯使用PV原语

P原语操作的动作是:

u sem减1。

u sem减1后仍大于或等于零,则进程继续执行。

u 若sem减1后小于零,则该进程被阻塞后进入与该信号相对应的队列中,然后转进程调度。

V原语操作的动作是:

u sem加1。

u 若相加结果大于零,则进程继续执行。

u 若相加结果小于或等于零,则从该信号的等待队列中唤醒一等待进程,然后再返回原进程继续执行或转进程调度。

信号灯分类

按信号灯实现原理,信号灯分两种,一种是有名信号灯,一种是基于内存的信号灯。

有名信号灯,是根据外部名字标识,通常指代文件系统中的某个文件。而基于内存的信号灯,它主要是把信号灯放入内存的。

基于内存的信号灯,同步多线程时,可以放到该多线程所属进程空间里;如果是同步多进程,那就需要把信号灯放入到共享内存中(方便多个进程访问)。

按实现方式,信号灯分为POSIX信号灯和System V信号灯,System V信号灯是由内核维护的,Posix信号灯是由文件系统中的路径名对应的名字来标识的。在目前的Linux中,System V使用更为广泛,POSIX一般是在更老的系统中使用。

信号灯操作

进程在信号灯上的几种操作:

1) 创建一个信号灯。还要求调用者指定初始值,对二值来说通常是1。

2) 等待一个信号灯。测试信号灯的值,如果<=0则等待,否则将其减1。注:测试其值并减1必须作为一个原子操作。

3) 挂出一个信号灯。将信号灯的值加1。挂出操作也必须是原子的。

4)获取信号灯状态。

问题:如何将等待某个信号灯的所有进程排队,如何唤醒这些可能很多的进程中的一个,所幸这些都是由实现来处理的。

二值信号灯可用于互斥目的。除了可以象互斥锁那样使用外,信号灯还有一个互斥锁没有提供的特性:互斥锁必须总是由锁住他的线程解锁,信号灯的挂出却不必由执行过它的等待操作的同一线程执行。比如生产者消费者问题是生产者和消费者互相唤醒的。

共享内存信号灯同时属于两个进程的地址空间。

信号灯有一个与之关联的值,挂出一个信号即使当前没有线程在等待该信号也没关系,与之相反的是,pthread_cond_signal如果当时没有任何线程阻塞在pthread_cond_wait中,则信号丢失。

POSIX信号灯

图1:POSIX有名信号灯和基于内存信号灯系统调用关系

POSIX有名信号灯

函数说明

#include <fcntl.h> /* For O_* constants */ #include <sys/stat.h> /* For mode constants */ #include <semaphore.h>

// 用来打开已经存在的信号灯

sem_t *sem_open(const char *name, int oflag);

// 用来创建信号灯

sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);

// 获得信号灯,得不到就阻塞;如果获得信号灯,信号灯数量减1

int sem_wait(sem_t *sem);

// 尝试获得,得不到返回失败,errno设置为EAGAIN

int sem_trywait(sem_t *sem);

// sem_getvalue返回指定信号灯的当前值,如果该信号灯已上锁,那么返回或为0,或为某个负数,其绝对值就是等待该信号灯解锁的线程数。

int sem_getvalue(sem_t *sem, int *sval);

// 释放信号灯,信号数量加1

int sem_post(sem_t *sem);

// 删除以name命名的信号灯,只有当系统中所有使用该信号灯的进程都释放,才会真的

删除

int sem_unlink(const char *name);

创建信号灯
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h>
#include <pthread.h> 
#include <semaphore.h> 
#include <fcntl.h>           
#include <sys/stat.h>    
#include <errno.h>
#include <string.h>

sem_t* namedSem;
#define FILENAME "/tmp/count_named_sem.txt"
#define MUTEXNAME "my_named_sem"

int main(int argc, char** argv) 
{
    int fd, inum = 0;
    namedSem = sem_open(MUTEXNAME, O_CREAT|O_EXCL, 0644, 1);
    if (SEM_FAILED == namedSem)
    {
        if (errno != EEXIST)
        {
            printf("sem_open error : %s\n", strerror(errno));
            return -1;
        }
        printf("sem_open %s exist! so open\n", MUTEXNAME);
        namedSem = sem_open(MUTEXNAME, O_RDWR);
    }
    printf("sem_open succ\n", MUTEXNAME);
    
    fd = open(FILENAME, O_RDWR|O_CREAT|O_TRUNC, 0777);
    write(fd, &inum, sizeof(int));
    return 0;
}

结果说明

[root@rocket ipc]# g++ -g -o ipc_posix_named_sem_create ipc_posix_named_sem_create.cpp –lrt

[root@rocket ipc]# ./ipc_posix_named_sem_create

sem_open succ

[root@rocket ipc]# ./ipc_posix_named_sem_create

sem_open my_named_sem exist! so open

sem_open succ

删除信号灯
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h>
#include <pthread.h> 
#include <semaphore.h> 
#include <fcntl.h>           
#include <sys/stat.h>    
#include <errno.h>
#include <string.h>

sem_t* namedSem;
#define FILENAME "/tmp/count_named_sem.txt"
#define MUTEXNAME "my_named_sem"

int main(int argc, char** argv) 
{
    namedSem = sem_open(MUTEXNAME, O_CREAT|O_EXCL, 0644, 1);
    if (SEM_FAILED == namedSem)
    {
        if (errno != EEXIST)
        {
            printf("sem_open error : %s\n", strerror(errno));
            return -1;
        }
        printf("sem_open %s exist! so open\n", MUTEXNAME);
        namedSem = sem_open(MUTEXNAME, O_RDWR);
    }
    
    int ret = sem_unlink(MUTEXNAME);
    if (-1 == ret)
    {
        printf("sem_unlink error: %s\n", strerror(errno));
        return -1;
    }
    printf("sem_unlink %s succ!\n", MUTEXNAME);
    return 0;
}

结果说明

[root@rocket ipc]# g++ -g -o ipc_posix_named_sem_unlink ipc_posix_named_sem_unlink.cpp -lrt

[root@rocket ipc]# ./ipc_posix_named_sem_unlink

sem_open my_named_sem exist! so open

sem_unlink my_named_sem succ!

[root@rocket ipc]# ./ipc_posix_named_sem_unlink

sem_unlink my_named_sem succ!

案例设计:使用信号灯加锁更新文件

u 生成一个文件,我们在里面写一个int,值为0,并初始化一个信号灯,信号数量为1

u 用一个使用信号灯加锁的进程,启动多份更新

u 用一个未使用信号灯加锁的进程,启动多份更新

u 检查加锁和不加锁更新的结果是否符合预期

#include <stdio.h> 
#include <stdlib.h> 
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h> 
#include <semaphore.h> 
#include <fcntl.h>           
#include <sys/stat.h>    
#include <errno.h>
#include <string.h>

sem_t* namedSem;
#define FILENAME "/tmp/count_named_sem.txt"
#define MUTEXNAME "my_named_sem"

int main(int argc, char** argv) 
{
    int fd, inum, iloop;
    if (argc != 2)
    {
        printf("usage: ./ipc_posix_sem_countlock <loopnum>\n");
        return 0;
    }
    namedSem = sem_open(MUTEXNAME, O_CREAT|O_EXCL, 0644, 1);
    if (SEM_FAILED == namedSem)
    {
        if (errno != EEXIST)
        {
            printf("sem_open error : %s\n", strerror(errno));
            return -1;
        }
        printf("sem_open %s exist! so open\n", MUTEXNAME);
        namedSem = sem_open(MUTEXNAME, O_RDWR);
    }
    printf("sem_open succ\n", MUTEXNAME);
    
    iloop = atoi(argv[1]);
    fd = open(FILENAME, O_RDWR);
    for (int i = 0; i < iloop; ++i)
    {
        sem_wait(namedSem);
        lseek(fd, 0, SEEK_SET);
        read(fd, &inum, sizeof(int));
        usleep(1000);
        inum++;
        lseek(fd, 0, SEEK_SET);
        write(fd, &inum, sizeof(int));
        sem_post(namedSem);
    }
    printf("pid %d countlock down\n", getpid());
    return 0;
}

结果说明

[root@rocket ipc]# od -i /tmp/count_named_sem.txt

0000000 0

0000004

[root@rocket ipc]# ./ipc_posix_named_sem_countlock 1000 &

[root@rocket ipc]# ./ipc_posix_named_sem_countlock 1000 &

[root@rocket ipc]# ./ipc_posix_named_sem_countlock 1000 &

[root@rocket ipc]# ./ipc_posix_named_sem_countlock 1000 &

pid 115794 countlock down

pid 115795 countlock down

pid 115796 countlock down

pid 115797 countlock down

[root@rocket ipc]# od -i /tmp/count_named_sem.txt

0000000 4000

0000004

运行不加锁版本(去掉上面代码中的sem_wait和sem_post)

[root@rocket ipc]# od -i /tmp/count_named_sem.txt

0000000 0

0000004

[root@rocket ipc]# ./ipc_countlock_without_sem 1000 &

[root@rocket ipc]# ./ipc_countlock_without_sem 1000 &

[root@rocket ipc]# ./ipc_countlock_without_sem 1000 &

[root@rocket ipc]# ./ipc_countlock_without_sem 1000 &

pid 116351 countlock down

pid 116352 countlock down

pid 116353 countlock down

pid 116354 countlock down

[root@rocket ipc]# od -i /tmp/count_named_sem.txt

0000000 2245 没加锁,这里的结果是不正确的

0000004

POSIX基于内存的信号灯

POSIX基于内存的信号灯的sem_wait和sem_post和POSIX有名信号灯是同一个实现,唯一不同在于构造和析构是在内存中进行的,而不是基于文件系统的某个路径名。

在sem_init函数中,如果shared为0,那么待初始化的信号灯是在同一进程的各个线程间共享的,否则该信号灯是在进程间共享的。当shared非0时,该信号灯必须存放在即将使用他的所有进程都能访问的某种类型的共享内存区中。

基于内存的信号灯的持续性由它所在的内存持续性决定。

函数说明

// 初始化一个信号量

int sem_init(sem_t *sem, int pshared, unsigned int value);

// 释放信号量

int sem_destory(sem_t *sem)

基于内存的信号灯的使用(线程之间进行同步)
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h>
#include <pthread.h> 
#include <semaphore.h> 

sem_t binSem;

void* helloWorld(void* arg) 
{
    while(1) 
    {
        // Wait semaphore
        sem_wait(&binSem);
        printf("Hello World\n");
     }
}

int main(int argc, char** argv) 
{
    // Result for System call
    int res = 0;

    // Initialize semaphore
    sem_init(&binSem, 0, 0);

    // Create thread
    pthread_t thdHelloWorld;
    pthread_create(&thdHelloWorld, NULL, helloWorld, NULL);

    while(1) 
    {
        // Post semaphore
        sem_post(&binSem);
        printf("In main, sleep several seconds.\n");
        sleep(1);
     }

    // Wait for thread synchronization
    void *threadResult;
    pthread_join(thdHelloWorld, &threadResult);

    return 0;
}

结果说明

[root@rocket ipc]# g++ -g -o ipc_posix_sem_thread ipc_posix_sem_thread.cpp -lrt

[root@rocket ipc]# ./ipc_posix_sem_thread

In main, sleep several seconds.

Hello World

In main, sleep several seconds.

Hello World

In main, sleep several seconds.

Hello World

In main, sleep several seconds.

基于内存的信号灯的使用(进程之间进行同步,使用共享内存存放信号灯)

# 创建

#include <sys/mman.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <pthread.h> 
#include <semaphore.h> 

int main(int argc, char** argv) 
{
    sem_t* shm_sem;

    const char* name = "/dev/shm/my_systemv_shm1";
    key_t key = ftok(name,0);
    if (key == -1)
    {
        perror("ftok error");
        return -1;
    }
    int shm_id=shmget(key, sizeof(sem_t), IPC_CREAT);
    if(shm_id == -1)
    {
        perror("shmget error");
        return -1;
    }
    shm_sem=(sem_t*)shmat(shm_id,NULL,0);

    // Initialize semaphore
    sem_init(shm_sem, 1, 0); // pshared = 1
    return 0;
}

结果说明

[root@rocket ipc]# ipcs

------ Shared Memory Segments --------

key shmid owner perms bytes nattch status

0x00000000 0 gdm 600 393216 2 dest

0x00000000 32769 gdm 600 393216 2 dest

0x00000000 65538 gdm 600 393216 2 dest

0x00000000 98307 gdm 600 393216 2 dest

[root@rocket ipc]# g++ -g -o ipc_posix_sem_mmap_create ipc_posix_sem_mmap_create.cpp –lrt

[root@rocket ipc]# ./ipc_posix_sem_mmap_create

[root@rocket ipc]# ipcs

------ Shared Memory Segments --------

key shmid owner perms bytes nattch status

0x00000000 0 gdm 600 393216 2 dest

0x00000000 32769 gdm 600 393216 2 dest

0x00000000 65538 gdm 600 393216 2 dest

0x00000000 98307 gdm 600 393216 2 dest

0x00108d43 229380 root 0 32 0

这里看到已经创建成功共享内存并初始化了信号灯。

# 信号灯V操作

#include <sys/mman.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <pthread.h> 
#include <semaphore.h> 

int main(int argc, char** argv) 
{
    sem_t* shm_sem;

    const char* name = "/dev/shm/my_systemv_shm1";
    key_t key = ftok(name,0);
    if (key == -1)
    {
        perror("ftok error");
        return -1;
    }
    int shm_id=shmget(key, sizeof(sem_t), IPC_CREAT);
    if(shm_id == -1)
    {
        perror("shmget error");
        return -1;
    }
    shm_sem=(sem_t*)shmat(shm_id,NULL,0);

    while(1)
    {
        // Post semaphore
        sem_post(shm_sem);
        printf("In main, sleep several seconds.\n");
        sleep(1);
    }

    return 0;
}

# 信号灯P操作

#include <sys/mman.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <pthread.h> 
#include <semaphore.h> 

int main(int argc, char** argv) 
{
    sem_t* shm_sem;

    const char* name = "/dev/shm/my_systemv_shm1";
    key_t key = ftok(name,0);
    if (key == -1)
    {
        perror("ftok error");
        return -1;
    }
    int shm_id=shmget(key, sizeof(sem_t), IPC_CREAT);
    if(shm_id == -1)
    {
        perror("shmget error");
        return -1;
    }
    shm_sem=(sem_t*)shmat(shm_id,NULL,0);

    int semvalue;
    sem_getvalue(shm_sem, &semvalue);
    printf("current sem value = %d\n", semvalue);
    
    while(1) 
    {
        // Wait semaphore
        sem_wait(shm_sem);
        printf("Hello World\n");
    }
    
    return 0;
}

结果说明

writer先跑起来

[root@rocket ipc]# ./ipc_posix_sem_mmap_writer

In main, sleep several seconds.

In main, sleep several seconds.

In main, sleep several seconds.

In main, sleep several seconds.

In main, sleep several seconds.

In main, sleep several seconds.

In main, sleep several seconds.

In main, sleep several seconds.

In main, sleep several seconds.

在另一个终端启动reader

[root@rocket ipc]# ./ipc_posix_sem_mmap_reader

current sem value = 7

Hello World

Hello World

Hello World

Hello World

Hello World

Hello World

Hello World

System V信号灯

函数说明

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

// 创建一个新的信号量或是获得一个已存在的信号量键值

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

key:所创建或打开信号量集的键值。需要是唯一的非零整数。

nsems:创建的信号量集中的信号量的个数,该参数只在创建信号量集时有效。

flag:调用函数的操作类型,也可用于设置信号量集的访问权限。

// 用来改变信号量的值(包含P操作和V操作)

struct  sembuf{ unsigned short sem_num;  /* semaphore number *//*信号灯在信号灯集中的编号*/ short          sem_op;   /* semaphore operation *//*P操作或者V操作*/ short          sem_flg;  /* operation flags */ };

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

int semtimedop(int semid, struct sembuf *sops, unsigned nsops, struct timespec *timeout);

sem_num:是相对应的信号量集中的某一个资源,所以其值是一个从0到相应的信号量集的资源总数(ipc_perm.sem_nsems)之间的整数。除非使用一组信号灯了,否则它的取值一般为0。

sem_op:是信号量在一次操作中需要改变的数值。通常只会用到两个值:-1---P操作,1---V操作。

sem_flg:说明函数semop的行为。通常被设置为SEM_UNDO。它将使得操作系统跟着当前进程对这个信号量的修改情况,如果这个进程在没有释放该信号量的情况下终止,操作系统将自动释放该进程持有的信号量。用一个通俗的说法:IPC_UNDO标志保证进程终止后,它对信号量的修改都撤销,好像它从来没有操作过信号量一样。这个标志要特别注意,使用不当容易造成一些诡异的问题。

这里需要强调的是semop同时操作多个信号灯,在实际应用中,对应多种资源的申请或释放。semop保证操作的原子性,这一点尤为重要。尤其对于多种资源的申请来说,要么一次性获得所有资源,要么放弃申请,要么在不占有任何资源情况下继续等待,这样,一方面避免了资源的浪费;另一方面,避免了进程之间由于申请共享资源造成死锁。关于这一点,可以参考https://cloud.tencent.com/developer/article/1021123里面的银行家算法,semop就是银行家算法的一个实现。

也许从实际含义上更好理解这些操作:信号灯的当前值记录相应资源目前可用数目;sem_op > 0对应相应进程要释放sem_op数目的共享资源;sem_op=0可以用于对共享资源是否已用完的测试;sem_op<0相当于进程要申请-sem_op个共享资源。再联想操作的原子性,更不难理解该系统调用何时正常返回,何时睡眠等待。

// 允许信号量信息的直接控制(包含初始化信号灯和删除信号灯)

// 这个联合体需要在程序声明,用于semctl函数的SETVAL选项的传值,作为第四个参数 union semun { int val; struct semid_ds *buf; unsigned short *array; }

int semctl(int semid, int semnum, int cmd, …/*union semun arg*/);

IPC_STAT

获取信号灯信息,信息由arg.buf返回;

IPC_SET

设置信号灯信息,待设置信息保存在arg.buf中(在manpage中给出了可以设置哪些信息);

GETALL

返回所有信号灯的值,结果保存在arg.array中,参数sennum被忽略;

GETNCNT

返回等待semnum所代表信号灯的值增加的进程数,相当于目前有多少进程在等待semnum代表的信号灯所代表的共享资源;

GETPID

返回最后一个对semnum所代表信号灯执行semop操作的进程ID;

GETVAL

返回semnum所代表信号灯的值;

GETZCNT

返回等待semnum所代表信号灯的值变成0的进程数;

SETALL

通过arg.array更新所有信号灯的值;同时,更新与本信号集相关的semid_ds结构的sem_ctime成员;

SETVAL

设置semnum所代表信号灯的值为arg.val;

信号灯创建并获取状态

#include <sys/mman.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <pthread.h> 
#include <sys/sem.h>

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;  
};  

int main(int argc, char** argv) 
{
    const char* name = "/dev/shm/my_systemv_shm2";
    union semun un;
    
    key_t key = ftok(name,0);
    if (key == -1)
    {
        perror("ftok error");
        return -1;
    }
    int semid = semget(key, 1, 0666|IPC_CREAT|IPC_EXCL);  
    if (-1 == semid)
    {        
        if (errno != EEXIST)  
        {  
            printf("semget error: %s\n", strerror(errno));
            return -1;  
        }
        semid = semget(key, 0, 0666);
        printf("semget get succ\n");
    }
    else
    {
        printf("semget create succ\n");
        un.val = 1;
        if (semctl(semid, 0, SETVAL, un) == -1)
        {
            printf("semctl error: %s\n", strerror(errno));
            return -1;
        }
    }
    struct semid_ds buf;
    un.buf = &buf;
    int ret = semctl(semid, 0, IPC_STAT, un);
    if (-1 == ret)
    {
        printf("semctl error: %s\n", strerror(errno));
        return -1;
    }
    printf("semid = %d, semvalue = %d\n", semid, un.buf->sem_nsems);
    
    return 0;
}

结果说明

[root@rocket ipc]# g++ -g -o ipc_systemv_sem_mmap_create ipc_systemv_sem_mmap_create.cpp

[root@rocket ipc]# ./ipc_systemv_sem_mmap_create

semget create succ

semid = 131073, semvalue = 1

[root@rocket ipc]# ipcs

------ Shared Memory Segments --------

key shmid owner perms bytes nattch status

0x00000000 0 gdm 600 393216 2 dest

0x00000000 32769 gdm 600 393216 2 dest

0x00000000 65538 gdm 600 393216 2 dest

0x00000000 98307 gdm 600 393216 2 dest

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

key semid owner perms nsems

0x00000000 0 root 600 1

0x00108f11 131073 root 666 1

信号灯删除

#include <sys/mman.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <pthread.h> 
#include <sys/sem.h>

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;  
};  

int main(int argc, char** argv) 
{
    const char* name = "/dev/shm/my_systemv_shm2";
    key_t key = ftok(name,0);
    if (key == -1)
    {
        perror("ftok error");
        return -1;
    }
    int semid = semget(key, 1, 0666|IPC_CREAT|IPC_EXCL);  
    if (-1 == semid)
    {        
        if (errno != EEXIST)  
        {  
            printf("semget error: %s\n", strerror(errno));
            return -1;  
        }
        semid = semget(key, 0, 0666);
        printf("semget get succ\n");
    }
    else
    {
        printf("semget create succ\n");
    }
    
    int ret = semctl(semid, 0, IPC_RMID);
    if (-1 == ret)
    {
        printf("semctl error: %s\n", strerror(errno));
        return -1;
    }
    printf("semid %d delete succ\n", semid);
    
    return 0;
}

结果说明

[root@rocket ipc]# g++ -g -o ipc_systemv_sem_mmap_delete ipc_systemv_sem_mmap_delete.cpp

[root@rocket ipc]# ipcs

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

key semid owner perms nsems

0x00000000 0 root 600 1

0x00108f11 131073 root 666 1

[root@rocket ipc]# ./ipc_systemv_sem_mmap_delete

semget get succ

semid 131073 delete succ

[root@rocket ipc]# ipcs

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

key semid owner perms nsems

0x00000000 0 root 600 1

可以看到,这里已经成功删除semid为131073的信号灯。

也可以使用ipcrm -s命令删除,ipcsrm -s semid。

信号灯V操作

#include <sys/mman.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <pthread.h>
#include <sys/sem.h>

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;  
};  

int sem_p(int sem_id)
{
    struct sembuf sem_buf;
    sem_buf.sem_num = 0; // 信号量编号
    sem_buf.sem_op = -1; // P操作
    sem_buf.sem_flg = 0;
    //sem_buf.sem_flg = SEM_UNDO; // 系统退出前未释放信号量,系统自动释放
    if (semop(sem_id, &sem_buf, 1) == -1) 
    {  
        perror("Sem P operation");  
        exit(1);  
    }  
    return 0;  
}

int sem_v(int sem_id)
{
    struct sembuf sem_buf;  
    sem_buf.sem_num = 0;  
    sem_buf.sem_op = 1; // V操作
    sem_buf.sem_flg = 0;
    // sem_buf.sem_flg = SEM_UNDO;  
    if (semop(sem_id, &sem_buf, 1) == -1) 
    {  
        perror("Sem V operation");  
        exit(1);  
    }  
    return 0;  
}

int main(int argc, char** argv) 
{
    const char* name = "/dev/shm/my_systemv_shm2";
    key_t key = ftok(name,0);
    if (key == -1)
    {
        perror("ftok error");
        return -1;
    }
    int semid = semget(key, 1, 0666);  
    if (-1 == semid)
    {
        printf("semget get error: %s\n", strerror(errno));
    }
    else
    {
        printf("semget create succ\n");
    }
    
    union semun un;
    printf("semid = %d, semvalue = %d\n", semid, semctl(semid, 0, GETVAL, 0));
    
    while(1)
    {
        // Post semaphore
        sem_v(semid);
        printf("In main, sleep several seconds.\n");
        sleep(1);
    }
    
    return 0;
}

结果说明

[root@rocket ipc]# ./ipc_systemv_sem_mmap_writer

semget create succ

semid = 294913, semvalue = 0

In main, sleep several seconds.

In main, sleep several seconds.

In main, sleep several seconds.

In main, sleep several seconds.

In main, sleep several seconds.

In main, sleep several seconds.

信号灯P操作

#include <sys/mman.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <pthread.h>
#include <sys/sem.h>

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;  
};  

int sem_p(int sem_id)
{
    struct sembuf sem_buf;
    sem_buf.sem_num = 0; // 信号量编号
    sem_buf.sem_op = -1; // P操作
    sem_buf.sem_flg = 0;
    //sem_buf.sem_flg = SEM_UNDO; // 系统退出前未释放信号量,系统自动释放
    if (semop(sem_id, &sem_buf, 1) == -1) 
    {  
        perror("Sem P operation");  
        exit(1);  
    }  
    return 0;  
}

int sem_v(int sem_id)
{
    struct sembuf sem_buf;  
    sem_buf.sem_num = 0;  
    sem_buf.sem_op = 1; // V操作
    sem_buf.sem_flg = 0;
    //sem_buf.sem_flg = SEM_UNDO;  
    if (semop(sem_id, &sem_buf, 1) == -1) 
    {  
        perror("Sem V operation");  
        exit(1);  
    }  
    return 0;  
}

int main(int argc, char** argv) 
{
    const char* name = "/dev/shm/my_systemv_shm2";
    key_t key = ftok(name,0);
    if (key == -1)
    {
        perror("ftok error");
        return -1;
    }
    int semid = semget(key, 1, 0666);  
    if (-1 == semid)
    {
        printf("semget get error: %s\n", strerror(errno));
    }
    else
    {
        printf("semget create succ\n");
    }
    
    union semun un;
    printf("semid = %d, semvalue = %d\n", semid, semctl(semid, 0, GETVAL, 0));
    
    while(1)
    {
        // Wait semaphore
        sem_p(semid);
        printf("Hello World\n");
        printf("semid = %d, semvalue = %d\n", semid, semctl(semid, 0, GETVAL, 0));
    }
    
    return 0;
}

结果说明

[root@rocket ipc]# ./ipc_systemv_sem_mmap_reader

semget create succ

semid = 294913, semvalue = 3

Hello World

semid = 294913, semvalue = 2

Hello World

semid = 294913, semvalue = 1

Hello World

semid = 294913, semvalue = 0

semvalue为0的时候,semop就会阻塞,直到另一个进程调用sem_v函数。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏网站那些事

强制更改社保卡密码,跟社保局的碰撞试验

昨天早晨,我妈叫我把回来重庆后参加工作的社保缴纳的截图发给她,她给村里的一个类似于会计工作的人,用于统计整个村的社保缴纳情况,我一想,这个简单呐,以前在惠州的时...

38730
来自专栏点滴积累

geotrellis使用(二十九)迁移geotrellis至1.1.1版

目录 前言 升级过程 总结 一、前言        由于忙着安装OpenStack等等各种事情,有半年的时间没有再亲密的接触geotrellis,甚至有半年的时...

36840
来自专栏IT技术精选文摘

GO语言版鹅厂广告交易实时平台价格解析

腾讯广告实时交易平台在向竞价胜出一方返回成交价的时候,先对价格进行TEA加密,再对密文进行BASE64编码,接收方先对BASE64解码,再对密文解密,双方事先...

11430
来自专栏微信公众号:Java团长

购物车的原理及实现(仿京东实现原理)

1)用户没登陆用户名和密码,添加商品, 关闭浏览器再打开后 不登录用户名和密码 问:购物车商品还在吗?

26210
来自专栏帅小子的日常

购物车的原理以及实现

78430
来自专栏用户2442861的专栏

轻松理解AOP(面向切面编程)

本文主要介绍AOP思想,而不是Spring,Spring在本文只做为理解AOP的工具和例子,所以也不打算介绍Spring的Aspect、Join point、...

10810
来自专栏一个会写诗的程序员的博客

《Kotin 极简教程》第14章 使用 Kotlin DSL第14章 使用 Kotlin DSL《Kotlin极简教程》正式上架:

我们在前面的章节中,已经看到了 Kotlin DSL 的强大功能。例如Gradle 的配置文件 build.gradle (Groovy),以及前面我们涉及到的...

15710
来自专栏向治洪

Kotlin DSL详解

DSL简介 所谓DSL领域专用语言(Domain Specified Language/ DSL),其基本思想是“求专不求全”,不像通用目的语言那样目标范围涵盖...

42670
来自专栏吴伟祥

一些免费的学习资源 原

HTML5 Canvas编程:http://blog.csdn.net/column/details/canvas-programming.html GTK编...

45930
来自专栏Java架构

京东Java架构师讲解购物车的原理及Java实现

1)用户没登陆用户名和密码,添加商品, 关闭浏览器再打开后 不登录用户名和密码 问:购物车商品还在吗? 

52650

扫码关注云+社区

领取腾讯云代金券