Linux IPC实践(11) --System V信号量(1)

信号量API

#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);

semget

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

创建/访问一个信号量集

参数:

key: 信号集键(key)

nsems:信号集中信号量的个数

semflg: 由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志一致

返回值:成功返回一个非负整数,即该信号集的标识码;失败返回-1;

此时创建的信号量集中的每一个信号量都会有一个默认值: 0, 如果需要更改该值, 则需要调用semctl函数->更改初始值;

/** 示例1: 封装一个创建一个信号量集函数
该信号量集包含1个信号量;
权限为0666
**/
int sem_create(key_t key)
{
    int semid = semget(key, 1, IPC_CREAT|IPC_EXCL|0666);
    if (semid == -1)
        err_exit("sem_create error");
    return semid;
}
/** 示例2: 打开一个信号量集
nsems(信号量数量)可以填0,
semflg(信号量权限)也可以填0, 表示使用默认的权限打开
**/
int sem_open(key_t key)
{
    int semid = semget(key, 0, 0);
    if (semid == -1)
        err_exit("sem_open error");
    return semid;
}

shmctl

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

控制信号量集

参数

semid:由semget返回的信号集标识码

semnum:信号集中信号量的序号(注意: 从0开始The semaphores in a set are numbered starting at 0.)

cmd:将要采取的动作(常用取值如下)

如果该函数需要第四个参数(有时是不需要第四个参数的, 取决于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)*/
};
//struct semid_ds : Linux内核为System V信号量维护的数据结构
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 long   sem_nsems; /* No. of semaphores in set */
};
/** 示例1: 将信号量集semid中的第一个信号量的值设置成为value(SETVAL)
注意: semun联合体需要自己给出(从man-page中拷贝出来即可)
**/
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) */
};
int sem_setval(int semid, int value)
{
    union semun su;
    su.val = value;
    if (semctl(semid, 0, SETVAL, su) == -1)
        err_exit("sem_setval error");
    return 0;
}
/** 示例2: 获取信号量集中第一个信号所关联的值(GETVAL)
注意: 此时第四个参数可以不填, 而信号量所关联的值可以通过semctl的返回值返回(the value of semval.)
**/
int sem_getval(int semid)
{
    int value = semctl(semid, 0, GETVAL);
    if (value == -1)
        err_exit("sem_getval error");
    return value;
    return 0;
}
/** 示例3: 删除一个信号量集(注意是删除整个集合)
	IPC_RMID  Immediately  remove(立刻删除)  the  semaphore  set,  awakening  all  processes blocked in semop(2) calls on the set (with an error return and errno set to  EIDRM)[然后唤醒所有阻塞在该信号量上的进程]. The  argument  semnum  is ignored[忽略第二个参数].
**/
int sem_delete(int semid)
{
    if (semctl(semid, 0, IPC_RMID) == -1)
        err_exit("sem_delete error");
    return 0;
}

//测试代码
int main(int argc,char *argv[])
{
    int semid = sem_create(0x1234);	//创建一个信号量集
    sem_setval(semid, 500);			//设置值
    cout << sem_getval(semid) << endl;	//获取值
    sleep(10);
    sem_delete(semid);		//删除该集合
}
/**示例4: 获取/设置信号量的权限
注意:一定要设定struct semid_ds结构体, 以指定使用semun的哪个字段
**/
int sem_getmode(int semid)
{
    union semun su;

    // 注意: 下面这两行语句一定要设定.
    // (告诉内核使用的semun的哪个字段)
    struct semid_ds sd;
    su.buf = &sd;
    //
    if (semctl(semid, 0, IPC_STAT, su) == -1)
        err_exit("sem_getmode error");
    printf("current permissions is: %o\n", su.buf->sem_perm.mode);
    return 0;
}
int sem_setmode(int semid, char *mode)
{
    union semun su;
    // 注意: 下面这两行语句一定要设定.
    // (告诉内核使用的semun的哪个字段)
    struct semid_ds sd;
    su.buf = &sd;
    //
    sscanf(mode, "%o", (unsigned int *)&su.buf->sem_perm.mode);

    if (semctl(semid, 0, IPC_SET, su) == -1)
        err_exit("sem_setmode error");
    return 0;
}

semop

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

用来操纵一个信号量集, 以实现P,V操作

参数:

semid:是该信号量的标识码,也就是semget函数的返回值

sops:是个指向一个结构数组(如果信号量集中只有一个信号量的话, 只有一个结构体也可)的指针

nsops:所设置的信号量个数(如果nsops>1话, 需要将sops[第二个参数]设置成为一个结构数组, 具体参考Man-Page给出的示例代码), 第三个参数其实也指出第二个参数所表示对象的个数;

//sembuf结构体
struct sembuf
{
    unsigned short sem_num;  /*semaphore number:信号量的编号(从0开始)*/
    short          sem_op;   /* semaphore operation(+1, 0, -1) */
    short          sem_flg;  /* operation flags: 常用取值为SEM_UNDO(解释见下) */
};

sem_op是信号量一次PV操作时加减的数值,一般只会用到两个值,一个是“-1”,也就是P操作,等待信号量变得可用;另一个是“+1”,也就是V操作,发出信号量已经变得可用, 该参数还可以等于0, 表示进程将阻塞直到信号量的值等于0;

sem_flg有三个取值: SEM_UNDO(在进程结束时, 将该进程对信号量的操作复原[即:取消该进程对信号量所有的操作], 推荐使用), IPC_NOWAIT(非阻塞)或0(默认操作, 并不撤销操作);

/** 示例: P,V操作封装
**可以将sembuf的第三个参数设置为IPC_NOWAIT/0, 以查看程序的状态的变化
**/
int sem_P(int semid)
{
    struct sembuf sops = {0, -1, SEM_UNDO};
    if (semop(semid, &sops, 1) == -1)
        err_exit("sem_P error");
    return 0;
}
int sem_V(int semid)
{
    struct sembuf sops = {0, +1, SEM_UNDO};
    if (semop(semid, &sops, 1) == -1)
        err_exit("sem_V error");
    return 0;
}

/** 信号量综合运用示例:
编译完成之后, 直接运行./semtool, 程序将打印该工具的用法;
下面的这些函数调用, 只不过是对上面所封装函数的稍稍改动, 理解起来并不困难;
**/
//semtool.cpp
#include "Usage.h"

int main(int argc,char *argv[])
{
    int opt = getopt(argc, argv, "cdpvs:gfm:");
    if (opt == ‘?‘)
        exit(EXIT_FAILURE);
    else if (opt == -1)
    {
        usage();
        exit(EXIT_FAILURE);
    }

    key_t key = ftok(".", ‘s‘);
    int semid;
    switch (opt)
    {
    case ‘c‘:
        sem_create(key);
        break;
    case ‘d‘:
        semid = sem_open(key);
        sem_delete(semid);
        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 ‘s‘:
        semid = sem_open(key);
        sem_setval(semid, atoi(optarg));
        sem_getval(semid);
        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]);
        sem_getmode(semid);
        break;
    default:
        break;
    }

    return 0;
}
//Usage.h
#ifndef USAGE_H_INCLUDED
#define USAGE_H_INCLUDED

#include <iostream>
#include <string>

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

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/mman.h>
#include <sys/sem.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <grp.h>
#include <pwd.h>
#include <time.h>
#include <errno.h>
#include <mqueue.h>
using namespace std;
inline void err_quit(std::string message);
inline void err_exit(std::string message);

void usage()
{
    cerr << "Usage:" << endl;
    cerr << "./semtool -c        #create" << endl;
    cerr << "./semtool -d        #delte" << endl;
    cerr << "./semtool -p        #signal" << endl;
    cerr << "./semtool -v        #wait" << endl;
    cerr << "./semtool -s <val>  #set-value" << endl;
    cerr << "./semtool -g        #get-value" << endl;
    cerr << "./semtool -f        #print-mode" << endl;
    cerr << "./semtool -m <mode> #set-mode" << endl;
}

int sem_create(key_t key)
{
    int semid = semget(key, 1, IPC_CREAT|IPC_EXCL|0666);
    if (semid == -1)
        err_exit("sem_create error");
    return semid;
}
int sem_open(key_t key)
{
    int semid = semget(key, 0, 0);
    if (semid == -1)
        err_exit("sem_open error");
    return semid;
}

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) */
};

int sem_getmode(int semid)
{
    union semun su;

    // 注意: 下面这两行语句一定要设定.
    // (告诉内核使用的semun的哪个字段)
    struct semid_ds sd;
    su.buf = &sd;
    //
    if (semctl(semid, 0, IPC_STAT, su) == -1)
        err_exit("sem_getmode error");
    printf("current permissions is: %o\n", su.buf->sem_perm.mode);
    return 0;
}
int sem_setmode(int semid, char *mode)
{
    union semun su;
    // 注意: 下面这两行语句一定要设定.
    // (告诉内核使用的semun的哪个字段)
    struct semid_ds sd;
    su.buf = &sd;
    //
    sscanf(mode, "%o", (unsigned int *)&su.buf->sem_perm.mode);

    if (semctl(semid, 0, IPC_SET, su) == -1)
        err_exit("sem_setmode error");
    return 0;
}
int sem_getval(int semid)
{
    int value = semctl(semid, 0, GETVAL);
    if (value == -1)
        err_exit("sem_getval error");
    cout << "current value: " << value << endl;
    return value;
}
int sem_setval(int semid, int value)
{
    union semun su;
    su.val = value;
    if (semctl(semid, 0, SETVAL, su) == -1)
        err_exit("sem_setval error");
    return 0;
}

int sem_delete(int semid)
{
    if (semctl(semid, 0, IPC_RMID) == -1)
        err_exit("sem_delete error");
    return 0;
}

// 为了能够打印信号量的持续变化, 因此sem_flg我们并没用SEM_UNDO
// 但是我们推荐使用SEM_UNDO
int sem_P(int semid)
{
    struct sembuf sops = {0, -1, 0};
    if (semop(semid, &sops, 1) == -1)
        err_exit("sem_P error");
    return 0;
}
int sem_V(int semid)
{
    struct sembuf sops = {0, +1, 0};
    if (semop(semid, &sops, 1) == -1)
        err_exit("sem_V error");
    return 0;
}

inline void err_quit(std::string message)
{
    std::cerr << message << std::endl;
    exit(EXIT_FAILURE);
}
inline void err_exit(std::string message)
{
    perror(message.c_str());
    exit(EXIT_FAILURE);
}

#endif // USAGE_H_INCLUDED

附-Makefile

.PHONY: clean all
CC = g++
CPPFLAGS = -Wall -g
BIN = semtool
SOURCES = $(BIN.=.cpp)
all: $(BIN)

%.o: %.c
    $(CC) $(CPPFLAGS) -c $^ -o [email protected]
main: main.o
    $(CC) $(CPPFLAGS) $^ -lrt -o [email protected]

clean:
    -rm -rf $(BIN) *.o bin/ obj/ core
时间: 2024-10-11 12:21:34

Linux IPC实践(11) --System V信号量(1)的相关文章

Linux IPC实践(12) --System V信号量(2)

实践1:信号量实现进程互斥 父子进程执行流程如下: 父进程 子进程 P P O(print) X(print) sleep sleep O(print) X(print) V V sleep sleep 从图中可以看出, O或X总是成对出现的, 要么两个O, 要么两个X; /**P,V原语实现父子进程互斥使用终端**/ // 程序代码 int main(int argc,char *argv[]) { int semid = sem_create(IPC_PRIVATE); sem_setval

Linux IPC实践(9) --System V共享内存

共享内存API #include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg); void *shmat(int shmid, const void *shmaddr, int shmflg); int shmdt(const void *shmaddr); int shmctl(int shmid, int cmd, struct shmid_ds *buf); //

Linux IPC实践(6) --System V消息队列(3)

消息队列综合案例 消息队列实现回射客户/服务器 server进程接收时, 指定msgtyp为0, 从队首不断接收消息 server进程发送时, 将mtype指定为接收到的client进程的pid client进程发送的时候, mtype指定为自己进程的pid client进程接收时, 需要将msgtyp指定为自己进程的pid, 只接收消息类型为自己pid的消息; // client/server进程接收/发送的数据结构 const int MSGMAX = 8192; struct msgBuf

Linux IPC实践(13) --System V IPC综合实践

实践:实现一个先进先出的共享内存shmfifo 使用消息队列即可实现消息的先进先出(FIFO), 但是使用共享内存实现消息的先进先出则更加快速; 我们首先完成C语言版本的shmfifo(基于过程调用), 然后在此基础上实现C++版本的ShmFifo, 将1块共享内存与3个信号量(1个mutext信号量, 1个full信号量, 1个empty信号量)封装成一个类ShmFifo, 然后编写各自的测试代码; shmfifo说明: 将申请到的共享内存作为一块缓冲区, 将该内存的首部(p_shm到p_pa

Linux IPC实践(4) --System V消息队列(1)

消息队列概述 消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法(仅局限于本机); 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值. 消息队列也有管道一样的不足: (1)每个消息的最长字节数的上限(MSGMAX); (2)系统中消息队列的总条数也有一个上限(MSGMNI); (3)每个消息队列所能够保存的总字节数是有上限的(MSGMNB) . 查看系统限制 cat /proc/sys/kernel/msgmax  #最大消息长度限制 cat /proc/sys

Linux IPC实践(5) --System V消息队列(2)

消息发送/接收API msgsnd函数 int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); 参数 msgid: 由msgget函数返回的消息队列标识码, 也可以是通过ipcs命令查询出来的一个已经存在的消息队列的ID号 msgp:是一个指针,指针指向准备发送的消息, msgsz:是msgp指向的消息长度, 注意:这个长度不含保存消息类型的那个long int长整型 msgflg:控制着当前消息队列满或到达系统上限时

linux进程通信之SYSTEM V信号量

信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)所拥有.信号量的值为正的时候,说明它空闲.所测试的线程可以锁定而使用它.若为0,说明它被占用,测试的线程要进入睡眠队列中,等待被唤醒. 一.信号量的分类: 在学习信号量之前,我们必须先知道--Linux提供两种信号量: (1) 内核信号量,由内核控制路径使用. (2) 用户态进程使用的信号量,这种信号量又分为POSIX信号量和SYSTEM V信号量. POSIX信号量又分为有名信号量和无名信号量.有名信号量,其值保存在文件

linux进程间通讯-System V IPC 信号量

进程间通信的机制--信号量.注意请不要把它与之前所说的信号混淆起来,信号与信号量是不同的两种事物.有关信号的更多内容,可以阅读我的另一篇文章:Linux进程间通信--使用信号.下面就进入信号量的讲解. 一.什么是信号量 为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域.临界区域是指执行数据更新的代码需要独占式地执行.而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在

Linux程序设计学习笔记----System V进程间通信(信号量)

关于System V Unix System V,是Unix操作系统众多版本中的一支.它最初由AT&T开发,在1983年第一次发布,因此也被称为AT&T System V.一共发行了4个System V的主要版本:版本1.2.3和4.System V Release 4,或者称为SVR4,是最成功的版本,成为一些UNIX共同特性的源头,例如"SysV 初始化脚本"(/etc/init.d),用来控制系统启动和关闭,System V Interface Definitio