Linux系统编程之进程间的通信(信号量)

前言:
信号量的相关概念:
信号量:主要用于同步与互斥。为了防止出现因多个进程访问临界资源而引发的一系列问题,信号量可以提供这样一种访问机制,在任一时刻只能有一个执行线程访问代码的临界区域,也就是说信号量是用来协调进程对临界资源的访问。

信号量的操作:信号量是一种特殊的变量,对信号量的访问必须是原子操作,信号量的操作只有两种:P操作(-1,申请资源)和V操作(+1,释放资源)。最简单的信号量只有两种取值0和1,称这样的信号量为二元信号量。可以取值为正整数N的信号量称为多元信号量,它允许多个线程并发的访问资源。

临界资源:能被多个进程共享,但一次只能允许一个进程使用的资源称为临界资源。

临界区:涉及到临界资源的部分代码,称为临界区。

互斥:亦称间接制约关系,在一个进程的访问周期内,另一个进程就不能进行访问,必须进行等待。当占用临界资源的进程退出临界区后,另一个进程才允许去访问此临界资源。

例如,在仅有一台打印机的系统中,有两个进程A和进程B,如果进程A需要打印时, 系统已将打印机分配给进程B,则进程A必须阻塞。一旦进程B将打印机释放,系统便将进程A唤醒,并将其由阻塞状态变为就绪状态。

同步:亦称直接制约关系,它是指为完成某种任务而建立的两个或多个进程,这些进程因为需要在某些位置上协调它们的工作次序而等待、传递信息所产生的制约关系。进程间的同步就是源于它们之间的相互合作。所谓同步其实就是两个进程间的制约关系。

例如,输入进程A通过单缓冲向进程B提供数据。当该缓冲区空时,进程B不能获得所需数据而阻塞,一旦进程A将数据送入缓冲区,进程B被唤醒。反之,当缓冲区满时,进程A被阻塞,仅当进程B取走缓冲数据时,才唤醒进程A。

原子性:对于进程的访问,只有两种状态,要么访问完了,要么不访问。当一个进程在访问某种资源的时候,即便该进程切出去,另一个进程也不能进行访问。

总结:
(1)信号量本质上是一个能被两个进程或多个进程都能看到的计数器。这个计数器用来表示临界资源的多少,计数器不以传输数据为目的,以保护临界资源为目的。
(2)信号量是进程间通信的一种,本身是一种临界资源,所以必须也要保护信号量的安全性。
(3)信号量的PV操作必须是原子操作。P操作:-1,表示申请资源成功;V操作:+1,表示释放资源成功。
(4)两个进程共享一个二元信号量sem,它的过程是这样的:其中一个进程执行了P操作(sem-1),它将得到信号量,并可以进入临界区,此时sem=0。另一个进程请求临界资源(P操作)的时候,他将被阻止进入临界区,并挂起等待,当第一个进程离开临界区并执行了V操作(sem+1)释放了资源的时候,第二个进程才可以恢复执行。

1.信号量集函数:
(1)semget函数:
函数原型:

扫描二维码关注公众号,回复: 14313188 查看本文章
 int semget(key_t key, int nsems, int semflg);

作用:用来创建和访问一个信号量集。

参数详解:
key:建值,信号量集的名字
nsems:信号量集中信号量的个数
semflg:标识函数的行为及权限。取值如下:
IPC_CREAT:如果不存在就创建
IPC_EXCLIPC_CREAT搭配使用,如果已经存在,则返回失败
权限位:设置共享内存的访问权限

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

(2)semctl函数:
函数原型:

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

作用:用于控制信号量集

参数详解:
semid:由semget返回的信号量集的标识码
semnum:信号量集中信号量的序号
cmd: 将要采取的动作,取值如下:
SETVAL:设置信号量集中信号量的计数值
GETVAL:获取信号量集中信号量的计数值
IPC_STAT:把semid_ds结构中的数据设置为信号量集的当前关联值
IPC_SET:在进程有足够权限的前提下,把信号集的当前关联值设置为semid_ds数据结构中给出的值
IPC_RMID:删除信号量集,不要第四个参数
最后一个参数根据命令的不同而不同

返回值:成功返回0,失败返回-1

SETVAL:初始化信号量的值(信号量成功创建后,需要设置初始值),这个值由第四个参数决定。第四参数是一个自定义的共同体,如下:

  // 用于信号等操作的共同体。
  union semun
  {
    
    
    int val;
    struct semid_ds *buf;
    unsigned short *arry;
  };

销毁一个信号量的做法:

  semctl(semid,0,IPC_RMID);

(3)semop函数
函数原型:

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

作用:用来创建和访问一个信号量集

参数详解:
semid:由semget返回的信号量集的标识码
sops:指向一个信号量结构体的指针
nsops:信号量的个数

返回值:成功返回0,失败返回-1

struct sembuf{
    
       
     unsigned short sem_num;  信号量的编号
     short          sem_op;   信号量一次PV操作时加减的数值,一般只会用到两个值:
                             -1:P操作,等待信号变得可用;+1:V操作,发出信号量已经变得可用
     short          sem_flg;  标志,有两个取值:
                              IPC_NOWAIT:表示队列满不等待,非阻塞,返回EAGAIN错误。
          IPC_UNDO: 使操作系统跟踪信号,并在进程没有释放该信号量而终止时,操作系统释放信号量 

};

2.函数介绍完毕,那么现在直接上demo
semdemo17.c

#include <stdio.h>
#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, ...);

 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 main()
{
    
    
	int semid;
	int pid;
	key_t key;
	key = ftok(".",125);
	           //信号量集中的信号量的个数
	semid = semget(key,1,IPC_CREAT|0666);//获取或创建信号量
	
	if(semid == -1)
	{
    
    
		printf("sorry get the pid error!\n");
	}
	
	union semun set;
	set.val = 1;
	    //信号量集中的第几个信号
	semctl(semid,0,SETVAL,set);//初始化信号量
	        //SETVAL设置信号量的值,这里设置为set
	pid = fork();
	
	if(pid > 0)
	{
    
    
		printf("this is father!\n");
	}
	else if(pid == 0)
	{
    
    
		printf("this is child\n");
	}
	else
	{
    
    
		printf("Sorry creat fork error\n");
	}
	
	return 0;
}

这个demo用于初认识信号量。

semdemo18.c

#include <stdio.h>
#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);


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

void pGetKey(int semid)
{
    
    
        struct sembuf init;

        init.sem_num = 0;
        init.sem_op = -1;
        init.sem_flg = 0;

        semop(semid,&init,1);

        printf("pGetKey ok!\n");
}

void vPutBackKey(int semid)
{
    
    
        struct sembuf init;

        init.sem_num = 0;
        init.sem_op = 1;
        init.sem_flg = 0;

        semop(semid,&init,1);

        printf("vPutBackKey ok!\n");
}

int main()
{
    
    
        key_t key;
        int semid;
        int pid;

        key = ftok(".",125);

        semid = semget(key,1,IPC_CREAT|0666);

        if(semid == -1)
        {
    
    
                printf("Sorry creat semid error!\n");
        }
        
        union semun set;
        set.val = 0;

        semctl(semid,0,SETVAL,set);

        pid = fork();

        if(pid > 0)
        {
    
    
                pGetKey(semid);
                printf("this is father!\n");
                vPutBackKey(semid);

                semctl(semid,0,IPC_RMID);
        }
        else if(pid == 0)
        {
    
    
                vPutBackKey(semid);
                printf("this is child!\n");
        }
        else
        {
    
    
                printf("creat error!\n");
        }

        return 0;
}

这时候锁的数量为0,所以父进程在执行拿锁的时候阻塞在那里,让子进程放锁回去后才继续执行。

结果:

CLC@Embed_Learn:~/SECOND/IPC$ ./a.out 
vPutBackKey ok!
this is child!
pGetKey ok!
this is father!
vPutBackKey ok!

这就是信号量拿锁与放锁的基本操作
学习笔记,仅供参考

猜你喜欢

转载自blog.csdn.net/weixin_51976284/article/details/124803896