前言


提示:以下是本篇文章正文内容,下面案例可供参考
本文参考:https://blog.csdn.net/weixin_34293059/article/details/86276502?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164558665416780261921257%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=164558665416780261921257&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allbaidu_landing_v2~default-4-86276502.pc_search_result_control_group&utm_term=linux+%E4%BF%A1%E5%8F%B7%E9%87%8F&spm=1018.2226.3001.4187

一、信号量简介:

在对于临界区资源管理的过程中,多个程序同时访问一个共享资源经常容易引发一系列问题:如死锁,结果不唯一等等,

在1965年,由荷兰科学家E.W.Dijkstra提出了一种新的进程同步工具,信号量及其PV操作。

对于信号量的定义

让多个进程通过特殊变量展开交互,一个进程在某一个关键点上被迫停止执行直至接收到对应的特殊变量值,通过这一措施,任何复杂的进程交互要求均可得到满足,这种特殊的变量就是信号量。

信号量的种类

一般信号量

设s为一个记录型数据结构,其中value为整型变量,系统初始化时为其赋值,PV操作的原语描述如下:

P(s):将信号量value值减1,若结果小于0,则执行P操作的进程被阻塞,若结果大于等于0,则执行P操作的进程将继续执行。

V(s):将信号量的值加1,若结果不大于0,则执行V操作的进程从信号量s有关的list所知队列中释放一个进程,使其转化为就绪态,自己则继续执行,若结果大于0,则执行V操作的进程继续执行。

二值信号量:

设s为一个记录型数据结构,其中分量value仅能取值0或1,二值信号量的PV操作的原语描述和一般信号量相同,虽然二值信号量仅能取值0或1,但可以证明他有着与其他信号量相同的表达能力。

二、编程实现

对于与信号量操作有关的接口,Linux下主要提供了以下几个函数,值得注意的是,在Linux下的C接口中,这些函数的操作对象都是信号量值组,也就是一个信号量值的链表

1、ftok函数生成键值

每一个共享存储段都有一个对应的键值(key)相关联(消息队列、信号量也同样需要)。

所需头文件:#include<sys/ipc.h>
函数原型 :key_t ftok(const char *path ,int id);

所需头文件:#include<sys/ipc.h>
函数原型 :key_t ftok(const char *path ,int id);

path为一个已存在的路径名

id为0~255之间的一个数值,代表项目ID,自己取

返回值:成功返回键值(相当于32位的int)。出错返回-1

例如:key_t key = ftok( “/tmp”, 66);

2、semget函数创建信号量

所需头文件:
	   #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>
       
函数原型:int semget(key_t key, int num_sems, int sem_flags);

该函数的作用是创建一个新信号量或取得一个已有信号量。

第一个参数key是整数值(唯一非零),就是Linux线程操作中经常用到的键值,可以通过ftok函数得到,不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget函数并提供一个键,再由系统生成一个相应的信号标识符(semget函数的返回值),只有semget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作。

第二个参数num_sems指定需要的信号量数目,它的值几乎总是1。

第三个参数sem_flags是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。

semget函数成功返回一个相应信号标识符(非零),失败返回-1

2. semop函数:改变信号量的值

所需头文件:
       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>
int semop(int sem_id, struct sembuf *sops, size_t nsops);

该函数的作用是改变信号量的值,其实就是为了信号量的PV操作而准备的,这个函数可以讲的地方比较多,下面会详细介绍:

函数的第一个参数 semid 为信号量集的标识符;

第2个参数 sops 指向进行操作的结构体数组的首地址,在 semop 的第二个参数 sops 指向的结构体数组中,每个 sembuf 结构体对应一个特定信号的操作。因此对信号量进行操作必须熟悉该数据结构,该结构定义在 linux/sem.h ,结构体如下:

struct sembuf{  
unsigned short sem_num; //信号在信号集中的索引,0代表第一个信号,1代表第二个信号  
short sem_op; //操作类型  
short sem_flg; //操作标志  
}; 

对于该结构中各个成员都具有特殊的含义,具体含义的介绍如下:

sem_op 参数:

sem_op > 0 信号加上 sem_op 的值,表示进程释放控制的资源;

sem_op = 0 如果sem_flg没有设置IPC_NOWAIT,则调用进程进入睡眠状态,直到信号量的值为0;否则进程不会睡眠,直接返回 EAGAIN

sem_op < 0 信号加上 sem_op 的值。若没有设置 IPC_NOWAIT ,则调用进程阻
塞,直到资源可用;否则进程直接返回EAGAIN

sem_flg 参数:

该参数可设置为 IPC_NOWAIT 或 SEM_UNDO 两种状态。只有将 sem_flg 指定为 SEM_UNDO 标志后,semadj (所指定信号量针对调用进程的调整值)才会更新。 此外,如果此操作指定SEM_UNDO,系统更新过程中会撤消此信号灯的计数(semadj)。此操作可以随时进行—它永远不会强制等待的过程。调用进程必须有改变信号量集的权限。
sem_flg公认的标志是 IPC_NOWAIT 和 SEM_UNDO。如果操作指定SEM_UNDO,当该进程终止时它将会自动撤消。

第3个参数 nsops 指出将要进行操作的信号的个数。semop 函数调用成功返回 0,失败返回 -1。

该函数所做的对于信号量的操作都是原子操作,即整个行为是一个整体,是不可打断的。所有操作是否可以立即执行取决于在个人sem_flg领域的IPC_NOWAIT标志的存在。

semctl函数信号量的初始化和删除

所需头文件:
       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>

int semctl(int sem_id, int sem_num, int command, ...);

int semctl(int sem_id,int sem_num,int command,[union semun sem_union]);
command:有两个值SETVAL,IPC_RMID,分别表示初始化和删除信号量。
sem_union:可选参数,结构如下:

union semun{  
    int val; 
    struct semid_ds *buf;  
    unsigned short *arry;  
}; 

函数的第一个参数 semid 为信号量集的标识符;
函数的第二个参数sem_num则是表示即将要进行操作的信号量的编号,即信号量集合的索引值,其中第一个信号量的索引值为0。

函数的第3个参数command代表将要在集合上执行的命令,其取值含义如下,通常用特定的宏代替:

IPC_STAT:获取某个信号量集合的semid_ds结构,并将其储存在semun联合体的buf参数所指的地址之中

IPC_SET:设置某个集合的semid_ds结构的ipc_perm成员的值,该命令所取的值是从semun联合体的buf参数中取到的

IPC_RMID:从内核删除该信号量集合

GETALL:用于获取集合中所有信号量的值,整数值存放在无符号短整数的一个数组中,该数组有联合体的array成员所指定

GETNCNT:返回当前正在等待资源的进程的数目

GETPID:返回最后一次执行PV操作(semop函数调用)的进程的PID

GETVAL:返回集合中某个信号量的值

GETZCNT:返回正在等待资源利用率达到百分之百的进程的数目

SETALL:把集合中所有信号量的值,设置为联合体的array成员所包含的对应值

SETVAL:将集合中单个信号量的值设置为联合体的val成员的值

总结

提示:这里对文章进行总结:

#include<stdio.h>
#include<stdlib.h>
#include<sys/sem.h>
union semun
{
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};
int sem_id;
int set_semvalue()
{
    union semun sem_union;    
    sem_union.val = 1;
    if(semctl(sem_id,0,SETVAL,sem_union)==-1)
        return 0;
    return 1;
}
int semaphore_p()
{
    struct sembuf sem_b;
    sem_b.sem_num = 0;
    sem_b.sem_op = -1;
    sem_b.sem_flg = SEM_UNDO;
    if(semop(sem_id,&sem_b,1)==-1)
    {
        fprintf(stderr,"semaphore_p failed\n");
        return 0;
    }
    return 1;
}
int semaphore_v()
{
    struct sembuf sem_b;
    sem_b.sem_num = 0;
    sem_b.sem_op = 1;
    sem_b.sem_flg = SEM_UNDO;
    if(semop(sem_id,&sem_b,1)==-1)
    {
        fprintf(stderr,"semaphore_v failed\n");
        return 0;
    }
    return 1;
}
void del_semvalue()
{
    //删除信号量
    union semun sem_union;
    if(semctl(sem_id,0,IPC_RMID,sem_union)==-1)
        fprintf(stderr,"Failed to delete semaphore\n");
}
int main(int argc,char *argv[])
{
    char message = 'x';
    //创建信号量
     sem_id = semget((key_t)1234,1,0666|IPC_CREAT);
    if(argc>1)
    {
        //初始化信号量
        if(!set_semvalue())
        {
            fprintf(stderr,"init failed\n");
            exit(EXIT_FAILURE);
        }
        //参数的第一个字符赋给message
        message = argv[1][0];
    }
    int i=0;
    for(i=0;i<5;i++)
    {
        //等待信号量
        if(!semaphore_p())
            exit(EXIT_FAILURE);
        printf("%c",message);
        fflush(stdout);
        sleep(1);
        //发送信号量
        if(!semaphore_v())
            exit(EXIT_FAILURE);
        sleep(1);
    }
    printf("\n%d-finished\n",getpid());
    if(argc>1)
    {
        //退出前删除信号量
        del_semvalue();
    }
    exit(EXIT_SUCCESS);
}
#include <stdio.h>
#include<sys/ipc.h>
#include <sys/types.h>
#include <sys/sem.h>


void pGetKey(int semId)//p操作获取钥匙
{

	struct sembuf set;

	set.sem_num = 0;
	set.sem_op=-1;
	set.sem_flg = SEM_UNDO ;
	semop(semId,&set,1);
	printf("pGetKey\n");
}


void vPutbacKey(int semId)//v操作放回钥匙
{
	struct sembuf set;
	printf("vPutKey\n");
	set.sem_num = 0;
	set.sem_op=1;
	set.sem_flg = SEM_UNDO ;
	semop(semId,&set,1);
}

// struct sembuf{  
// unsigned short sem_num; //信号在信号集中的索引,0代表第一个信号,1代表第二个信号  
// short sem_op; //操作类型  
// short sem_flg; //操作标志  
// }; 


union semun {  
    int val; 
    struct semid_ds *buf;  
    unsigned short *arry;  
}; 

int main(int argc, char const *argv[])
{
	

	key_t key;
	key = ftok("./" ,66); 
	int semId;
	//int semget(key_t key, int num_sems, int sem_flags);
	semId = semget(key,1,IPC_CREAT|0666);
	if(semId == 1)
	{
		printf("creat error!\n");
	}
	union semun initsem;
	initsem.val = 0;
	semctl( semId, 0, SETVAL , initsem);

	int pid = fork();

	if(pid > 0)
	{
		pGetKey(semId);
		printf("this is father!\n");
		vPutbacKey(semId);
	}
	else if(pid == 0) {
		printf("this is child!\n");
		vPutbacKey(semId);
	}else{
		printf("creat process error!\n");
	}

	return 0;
}
Logo

更多推荐