[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 VIM 程序中有游离的‘\357’ ‘\274’错误

gcc date.cpp -o date -lstdc date.cpp:18:20: 错误&#xff1a;程序中有游离的‘\357’date.Showdata()&#xfffd;&#xfffd;&#xfffd;^ date.cpp:18:21: 错误&#xff1a;程序中有游离的‘\274’date.Showdata()&#xfffd;&#xfffd;&#xfffd;^ date.cpp:18:22…

[Linux]关于SIGCHLD

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

C语言思维导图

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

转载一篇关于curl的文章

转载一篇关于curl的文章 http://www.360doc.com/content/16/0107/15/18578054_526158476.shtml

[Linux]vi/vim下添加多行注释和取消注释

添加注释&#xff08;Centos&#xff09;&#xff1a; 在命令行模式下按ctrlV进入 visual block模式&#xff08;可视化模式&#xff09; 选中你需要注释的行&#xff0c;再按大写的I&#xff0c;输入//&#xff0c;最后按俩下esc即可。 如果想让前进tab个位&#xff0c;则可在…

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

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

[Linux]共享内存

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

僵尸进程的产生,危害和解决方案

概念 僵死状态&#xff08;Zombies&#xff09;是一个比较特殊的状态。 当进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵尸进程。僵尸进程会以终止状态保持在进程表中&#xff0c;并且会一直在等待父进程读取退出状态代码。所以&#xff0c;只要子进程退出&…

CString string 转换

https://www.cnblogs.com/HappyEDay/p/7016162.html

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

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

gdb调试常用命令速查(段错误调试)

编译程序时需要加上-g&#xff0c;之后才能用gdb进行调试&#xff1a;gcc -g main.c -o main gdb中命令&#xff1a; 回车键&#xff1a;重复上一命令 &#xff08;gdb&#xff09;help&#xff1a;查看命令帮助&#xff0c;具体命令查询在gdb中输入help 命令,简写h &…

C语言字符串 小记

#include "stdafx.h" #include <iostream> #include <string.h> using namespace std;int _tmain(int argc, _TCHAR* argv[]) {char str1[] "12345"; // ""括起来的字符串 会在末尾增加 \0 cout << sizeof(str1) << en…

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

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

linux命令行界面下ctrl 常用组合键速查表

Ctrlz 暂停正在运行的程序 Ctrll 清屏 Ctrld 结束输入或退出shell Ctrla 切换到命令行开始 Ctrle 切换到命令行末尾 Ctrlu 删除光标前内容 Ctrlk 删除光标后内容 Ctrlxu 撤销操作

[Linux]运输层的端口

既然提到端口&#xff0c;我们就来分析一下为什么要使用端口的缘由吧。我们首先要知道的是&#xff0c;运输层有复用和分用的功能。应用层所有的应用进程都可以通过运输层再传送到IP层&#xff0c;这就是复用。运输层从IP层收到数据后必须交付到指明的应用进程&#xff0c;这就…

浅谈shell中的clear命令实现

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

C++ 对引用的理解

引用可以看做是数据的一个别名&#xff0c;通过这个别名和原来的名字都能够找到这份数据引用必须在定义的同时初始化&#xff0c;并且以后也要从一而终&#xff0c;不能再引用其它数据&#xff0c;这有点类似于常量&#xff08;const 变量&#xff09;。引用变量 里面 实际存储…

[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…

C++ 对引用的理解2

1.指针就是数据或代码在内存中的地址&#xff0c;指针变量指向的就是内存中的数据或代码。这里有一个关键词需要强调&#xff0c;就是内存&#xff0c;指针只能指向内存&#xff0c;不能指向寄存器或者硬盘&#xff0c;因为寄存器和硬盘没法寻址。 2.其实 C 代码中的大部分内容…