[Linux]信号量

信号量是一个计数器,用于为多个进程提供对共享数据对象的访问。

在信号量上只有三种操作可以进行,初始化、递增和增加,这三种操作都是原子操作。递减操作可以用于阻塞一个进程,增加操作用于解除阻塞一个进程。

为了获得共享资源,需要测试信号量,若信号量为正,则进程可以使用该资源,这时信号量值减一。否则信号量值为0,进程进入休眠状态。当进程不再使用由一个信号量控制的共享资源时,信号量值加一。如果有正在休眠的进程,则唤醒它们。常用的信号量的形式为二元信号量。即是原子性的。

信号量的功能:负责数据操作的互斥、同步等功能。本质上是一种数据操作锁。

我们为什么要使用信号量呢?为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使⽤用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式地执行。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来协调进程对共享资源的访问的。其中共享内存的使用就要用到信号量。

信号量只能进行两种操作等待和发送信号,PV操作,即P(sv)和V(sv),P申请资源,则将可用资源数-1,V释放资源,则将可用资源数+1。

内核为每个信号量集合维护着一个semid_ds结构:

struct semid_ds
{struct ipc_perm sem_perm;unsigned short sem_nsems; //该集合的信号量数目time_t sem_otime;time_t sem_ctime;
};

每个信号量都有一个无名的结构:

  unsigned short  semval;   /* semaphore value */unsigned short  semzcnt;  /* # waiting for zero */unsigned short  semncnt;  /* # waiting for increase */pid_t           sempid;   /* process that did last op */

当我们想使用信号量时,首先要通过调用函数semget来获得一个信号量ID:

#include<sys/sem.h>
int semget(key_t key,int nsems,int flag);
//成功,返回信号量ID,出错返回-1

semctl函数包含了多种信号量操作

int semctl(int semid,int semnum,int cmd,.../* union semun arg */);
//第四个参数是可选的,取决于请求的命令,如果使用该参数,则其类型是semun,它是多个命令特定的联合。
union semun
{int val;       //for SETVAL;struct semid_ds *buf;     //for IPC_STAT and IPC_SET;unsigned short *array;    //for GETALL and SETALL
};

我们通常使用的:IPC_RMID:从系统中删除该信号量集合

SETVAL:设置成员semnum的semval值,该值由arg.val指定

函数semop自动执行信号量集合上的操作数组。

int semop(int semid,struct sembuf semoparray[],size_t nops);
//成功,返回0,失败,返回-1;semoparray是一个指针,指向由sembuf结构表示的信号量操作数组,nops规定该数组中操作的数量
struct sembuf
{unsigned short sem_num;   //0,1,2,3,....short sem_op;  //(负值,0,正值)-1,0,1short sem_flg;  //IPC_NOWAIT,SEM_UNDO
};

对集合中每个成员的操作由相应的sem_op值规定。此值可以为负值,0,正值。最易于处理的是为正值,说明需要释放资源,则sem_op的值会加到信号量值上,如果指定了undo标志,则从此信号量调整之后的值上减去sem_op;如果sem_op是负值,说明需要申请资源,则信号量会减去sem_op的绝对值,如果指定undo标志,则sem_op的绝对值也加到信号量的调整值上。sem_op为0,表示调用进程希望等待到该信号量值为0。

对于信号量调整,如果在进程终止时,它占用了经由信号量分配的资源,那么就会成为一个问题。无论何时只要为信号量操作指定了SEM_UNDO标志,然后分配资源(sem_op< 0),那么内核就会记住该特定信号量,分配给调用进程多少资源。对每个操作都指定SEM_UNDO,以处理在未释放资源条件下进程终止的情况。

信号量主要解决互斥与同步问题,下面举个栗子:(实现父子进程输出成对AA或BB)

//comm.h
#ifndef _COMM_H_
#define _COMM_H_#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>#define PATHNAME "."
#define PROJ_ID 0X6666
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 CreateSem(int nums);
int  GetSem(int nums);
int DestroySem(int semid);
int initSem(int semid,int nums,int initval);
int SemP(int semid,int who);
int SemV(int semid,int who);#endif  //_COMM_H_
#include"comm.h"static int CommSemSet(int nsems,int flags)
{key_t key = ftok(PATHNAME,PROJ_ID);if(key < 0){//printf("%d\n", key);perror("ftok");return -1;}int semid = semget(key,nsems,flags);if(semid < 0){perror("semget");return -2;}return semid;
}int  CreateSemSet(int nums)
{return CommSemSet(nums,IPC_CREAT|IPC_EXCL|0666);
}int GetSem(int nums)
{return CommSemSet(nums,IPC_CREAT);
}
int DestroySem(int semid)
{if(semctl(semid,0,IPC_RMID) < 0){perror("semctl");return -1;}return 0;
}int initSem(int semid,int nums,int initval)
{union semun _un;_un.val =  initval;if(semctl(semid,nums,SETVAL,_un) < 0){perror("semctl");return -1;}return 0;
}static int CommPV(int semid,int who,int op)
{struct sembuf _sem;_sem.sem_num = who;_sem.sem_op = op;_sem.sem_flg = 0;if(semop(semid,&_sem,1) < 0){perror("semop");return -1;}   return 0;
}
int SemP(int semid,int who)
{return CommPV(semid,who,-1);
}
int SemV(int semid,int who)
{return CommPV(semid,who,1);
}
//sem.c
#include"comm.h"int main()
{int semid = CreateSemSet(1);initSem(semid,0,1); pid_t id = fork();if(id == 0)//child{int _semid = GetSem(0);while(1){SemP(_semid,0);printf("A");fflush(stdout);usleep(123456);printf("A");fflush(stdout);usleep(345678); SemV(_semid,0);}}else{while(1){SemP(semid,0);printf("B");fflush(stdout);usleep(234567);printf("B");fflush(stdout);usleep(456789);//  usleep(121212);SemV(semid,0);}wait(NULL);}DestroySem(semid);printf("sem quit!\n");return 0;
}
//Makefile
sem:sem.c comm.cgcc -o $@ $^
.PHONY:clean
clean:rm -f sem

运行结果:

这里写图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/382699.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[Linux]关于SIGCHLD

之前我们就学过&#xff0c;关于wait和waitpid来处理僵尸进程&#xff0c;父进程等待子进程结束后自己才退出&#xff0c;这样的方法有俩种方式&#xff0c;一种是父进程死死的等子进程退出&#xff0c;也就是使用阻塞的方式等待子进程退出&#xff0c;另一种方式是通过非阻塞的…

C语言思维导图

本人能力有限&#xff0c;知识点难免概括不全&#xff0c;如有错误欢迎指正

pthread和互斥量条件变量函数意义速查表

数据类型 pthread_t 线程 互斥量和条件变量

[Linux]共享内存

共享内存是UNIX提供的进程间通信手段中速度最快的一种&#xff0c;也是最快的IPC形式。为什么是最快的呢&#xff0c;因为数据不需要在客户进程和服务器进程之间复制&#xff0c;所以是最快的一种IPC。这是虚存中由多个进程共享的一个公共内存块。 两个不同进程A、B共享内存的…

[Linux]gdb调试多进程多线程例程

gdb相信学linux的同学已经比较熟悉了吧&#xff0c;它是linux下代码调试工具。我们在写c语言&#xff0c;c的代码时经常会用到&#xff0c;它有一些常用的调试命令: run&#xff08;r&#xff09;&#xff1a;运行程序&#xff0c;如果有断点在下一个断点处停止 start&#xf…

[Linux]守护进程(精灵进程)

一、守护进程是什么 守护进程是生存期很长的一种进程&#xff0c;可以说它是7*24小时工作的。&#xff08;什么是7*24&#xff0c;一周7天&#xff0c;每天24小时&#xff0c;这不就是一年365天一直在工作嘛&#xff0c;还搞的这么诙谐&#xff0c;哈哈&#xff09;。它们常常…

浅谈shell中的clear命令实现

NAME(名称) clear - 清除终端屏幕 SYNOPSIS(总览) clear DESCRIPTION(描述) clear可以在允许的情况下清屏. 它会在环境变量中查找终端的类型, 然后到terminfo数据库中找出清屏的方法. 《man手册》 #include <stdio.h>int clear_main(int argc, char **argv) {/* Th…

[Linux]ARP协议

概念&#xff1a; 1. ARP协议(地址解析协议):由IP地址转换为MAC地址的协议。IP地址&#xff1a;网络号主机号。MAC地址&#xff1a;数据链路层的物理地址&#xff08;硬件地址&#xff09;。IP协议使用了ARP协议&#xff0c;因此被划归为网络层&#xff0c;但其用途是从网络层…

Makefile使用及多文件gdb 调试

文件内容 [koulocalhost makefile]$ cat 1.c #include "3.h" int main() {key_t key ftok(".",1);printf("%d\n",add(1,2));return 0; }[koulocalhost makefile]$ cat 2.c #include "3.h" int add(int a, int b) {return a b; } [k…

[Linux]CRC校验

CRC(Cyclic Redundancy Check),循环冗余校验码&#xff0c;是数据通信领域中最常用的一种差错校验码&#xff0c;其特征是信息字段和校验字段的长度可以任意选定。 CRC校验步骤&#xff1a; CRC分为两部分&#xff0c;前部分为信息码&#xff0c;后部分为校验码&#xff1b;设…

python字符串系列

1.find方法用于在长串中查找子串&#xff0c;返回子串中最左位置的下标&#xff0c;如果没找到&#xff0c;则返回-1 2.join方法用于在队列中添加元素 3.lower返回字符串的小写字母版 4.replace返回字符串中所有匹配项均被替换之后得到字符串 5.split将字符串分割成序列 6.stri…

linux网络编程Internet Socket地址,套接字,和函数

文章内容节选《linux/UNIX 系统网络编程》 Internet domain socket地址有两种&#xff1a;IPv4 IPv6 IPv4被存储在结构体中&#xff0c; 该结构体在 netinet/in.h 中进行定义 cd usr/include/netinet/in.h struct in_addr {in_addr_t s_addr; //32位IPv4地址 }struct so…

浅谈socket网络编程函数参数(一)

socket函数解析 概念: 每个进程的进程空间里都有一个socket描述符表。套接字描述符表属于一个进程&#xff0c;而socket地址结构位于操作系统的内核缓冲。 函数原型 #include <sys/socket.h>int socket(int domain, int type, int protocol);函数参数 family参数 默…

为什么计算机起始时间是1970年1月1日

1969年8月&#xff0c;贝尔实验室的程序员肯汤普逊利用妻儿离开一个月的机会&#xff0c;开始着手创造一个全新的革命性的操作系统&#xff0c;他使用B编译语言在老旧的PDP-7机器上开发出了Unix的一个版本。随后&#xff0c;汤普逊和同事丹尼斯里奇改进了B语言&#xff0c;开发…

TCP三次挥手四次握手(面试总结)

1、 为什么建立连接协议是三次握手&#xff0c;而关闭连接却是四次握手呢&#xff1f; 全双工通信。 这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后&#xff0c;它可以把ACK和SYN&#xff08;ACK起应答作用&#xff0c;而SYN起同步作用&#xff09;放在一个…

csdn怎么快速转载别人的文章

如何转载 用谷歌浏览器加载文章地址&#xff0c;打开文章F12打开Developer Tools&#xff0c;并打开Elements页面 将文章开头部分的文字作为关键字在Elements界面搜索 以此文为例&#xff1a;http://blog.csdn.net/aggressive_snail/article/details/54375876 搜索找了好久关…

解释性语言和汇编性语言对比

解释性语言和编译型语言的区别和不同解释性语言编译型语言概念计算机不能直接的理解高级语言&#xff0c;只能直接理解机器语言&#xff0c;所以必须要把高级语言翻译成机器语言&#xff0c;计算机才能执行高级语言的编写的程序。翻译的方式有两种&#xff0c;一个是编译&#…