• Linux进程间通信—信号量


    一、概述

      进程间通信(interprocess communication,简称 IPC)指两个进程之间的通信。系统中的每一个进程都有各自的地址空间,并且相互独立、隔离,每个进程都处于自己的地址空间中。所以同一个进程的不同模块譬如不同的函数)之间进行通信都是很简单的,譬如使用全局变量等。但是,两个不同的进程之间要进行通信通常是比较难的,因为这两个进程处于不同的地址空间中。
      Linux 内核提供了多种 IPC 机制,其中System V IPC 包括:System V 信号量、System V消息队列、System V 共享内存。这三种通信机制有很多相似之处。
    在这里插入图片描述
      信号量是一个计数器,与其它进程间通信方式不大相同,它主要用于控制多个进程间或一个进程内的多个线程间对共享资源的访问,相当于内存中的标志,进程可以根据它判定是否能够访问某些共享资源,同时,进程也可以修改该标志,除了用于共享资源的访问控制外,还可用于进程同步。
      它常作为一种锁机制,防止某进程在访问资源时其它进程也访问该资源,因此,主要作为进程间以及同一个进程内不同线程之间的同步手段。Linux 提供了一组精心设计的信号量接口来对信号量进行操作,它们声明在头文件 sys/sem.h 中。

    二、Linux中使用信号量的API

      为了进行临界区保护。信号量一种访问机制,让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来调协进程对共享资源的访问的。
      信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。最简单的信号量是只能取0和1的变量,这也是信号量最常见的一种形式,叫做二进制信号量。而可以取多个正整数的信号量被称为通用信号量。
      由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:
      P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行
      V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.
      例如:两个进程共享信号量sv,一旦其中一个进程执行了P(sv)操作,它将得到信号量,并可以进入临界区,使sv减1。而第二个进程将被阻止进入临界区,因为当它试图执行P(sv)时,sv为0,它会被挂起以等待第一个进程离开临界区域并执行V(sv)释放信号量,这时第二个进程就可以恢复执行。

    2.1、ftok函数

      系统建立IPC通讯 (消息队列、信号量和共享内存) 时必须指定一个ID值。通常情况下,该id值通过ftok函数得到。函数原型:

    key_t ftok( const char * fname, int id )
    
    • 1

      fname:就是指定的文件名(已经存在的文件名),一般使用当前目录
      id:子序号。虽然是int类型,但是只使用8bits(1-255)。
      返回值:消息队列使用的ID值。

    2.2、semget函数

      它的作用是创建一个新信号量或取得一个已有信号量,原型为:

    int semget(key_t key, int num_sems, int sem_flags);
    
    • 1

      key:信号量关联的键,由ftok函数产生。不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget函数并提供一个键,再由系统生成一个相应的信号标识符(semget函数的返回值),只有semget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作。
      num_sems:指定需要的信号量数目,它的值几乎总是1。
      sem_flags:是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。
      返回值:相应信号标识符(非零),失败返回-1.

    2.3、semop函数

      在 Linux 下,PV 操作通过调用semop函数来实现。它的作用是改变信号量的值,原型为:

    int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
    
    • 1

      sem_id:是由semget返回的信号量标识符
      sembuf结构的定义如下:

    struct sembuf{
        short sem_num;//除非使用一组信号量,否则它为0
        short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
                        //一个是+1,即V(发送信号)操作。
        short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,
                        //并在进程没有释放该信号量而终止时,操作系统释放信号量
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

      num_sem_ops:信号操作结构的数量,恒大于或等于1。
      semop函数执行P操作时通常执行以下操作

     struct  sembuf  buf  = { 0, -1, SEM_UNDO};
     semop ( semid, &buf, 1) 
    
    • 1
    • 2

      semop函数执行V操作时通常执行以下操作

     struct  sembuf  buf  = { 0, 1, SEM_UNDO};
     semop ( semid, &buf, 1) 
    
    • 1
    • 2

    2.4、semctl函数

      该函数用来控制信号量,它与共享内存的shmctl函数和消息队列的msgctl相似,它的原型为:

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

      sem_id、sem_num两个参数与semget函数中的一样。
      command:通常是下面两个值中的其中一个
        SETVAL:用来把信号量初始化为一个已知的值。p 这个值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置。
        IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。
      如果有第四个参数,它通常是一个union semum结构,定义如下:

    union semun{
        int val;
        struct semid_ds *buf;
        unsigned short *arry;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    三、例程

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
     
    union semun
    {
    	int val;
    	struct semid_ds *buf;
    	unsigned short *arry;
    };
     
    static int sem_id = 0;
    static int set_semvalue()
    {
    	//用于初始化信号量,在使用信号量前必须这样做
    	union semun sem_union;
     
    	sem_union.val = 1;
    	if(semctl(sem_id, 0, SETVAL, sem_union) == -1)
    		return 0;
    	return 1;
    }
     
    static void del_semvalue()
    {
    	//删除信号量
    	union semun sem_union;
     
    	if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
    		fprintf(stderr, "Failed to delete semaphore\n");
    }
     
    static int semaphore_p()
    {
    	//对信号量做减1操作,即等待P(sv)
    	struct sembuf sem_b;
    	sem_b.sem_num = 0;
    	sem_b.sem_op = -1;//P()
    	sem_b.sem_flg = SEM_UNDO;
    	if(semop(sem_id, &sem_b, 1) == -1)
    	{
    		fprintf(stderr, "semaphore_p failed\n");
    		return 0;
    	}
    	return 1;
    }
     
    static int semaphore_v()
    {
    	//这是一个释放操作,它使信号量变为可用,即发送信号V(sv)
    	struct sembuf sem_b;
    	sem_b.sem_num = 0;
    	sem_b.sem_op = 1;//V()
    	sem_b.sem_flg = SEM_UNDO;
    	if(semop(sem_id, &sem_b, 1) == -1)
    	{
    		fprintf(stderr, "semaphore_v failed\n");
    		return 0;
    	}
    	return 1;
    }
    int main(int argc, char *argv[])
    {
    	char message = 'X';
    	int i = 0;
     	key_t sem_key = Ftok(FILE_PATH, 'a'));//获取系统IPC键值
    	//创建信号量
    	sem_id = semget(sem_key, 1, 0666 | IPC_CREAT);
     
    	if(argc > 1)
    	{
    		//程序第一次被调用,初始化信号量
    		if(!set_semvalue())
    		{
    			fprintf(stderr, "Failed to initialize semaphore\n");
    			exit(EXIT_FAILURE);
    		}
    		//设置要输出到屏幕中的信息,即其参数的第一个字符
    		message = argv[1][0];
    		sleep(2);
    	}
    	for(i = 0; i < 10; ++i)
    	{
    		//进入临界区
    		if(!semaphore_p())
    			exit(EXIT_FAILURE);
    		//向屏幕中输出数据
    		printf("%c", message);
    		//清理缓冲区,然后休眠随机时间
    		fflush(stdout);
    		sleep(rand() % 3);
    		//离开临界区前再一次向屏幕输出数据
    		printf("%c", message);
    		fflush(stdout);
    		//离开临界区,休眠随机时间后继续循环
    		if(!semaphore_v())
    			exit(EXIT_FAILURE);
    		sleep(rand() % 2);
    	}
     
    	sleep(10);
    	printf("\n%d - finished\n", getpid());
     
    	if(argc > 1)
    	{
    		//如果程序是第一次被调用,则在退出前删除信号量
    		sleep(3);
    		del_semvalue();
    	}
    	exit(EXIT_SUCCESS);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
  • 相关阅读:
    今年618各云厂商的香港服务器优惠活动汇总
    MYSQL数据库之备份与恢复
    linux网络编程——UDP编程
    Http状态401,弹出原生登录弹窗问题
    git drop掉的commit如何找回
    【Python】常用距离计算方法
    【LeetCode:2512. 奖励最顶尖的 K 名学生 | 模拟+哈希表+堆】
    HTML5中表单提交的几种验证方法
    Go通过cobra快速构建命令行应用
    保姆级教学!!! GIT:将本地文件夹上传到github仓库中
  • 原文地址:https://blog.csdn.net/xxxx123041/article/details/127812218