性无码一区二区三区在线观看,少妇被爽到高潮在线观看,午夜精品一区二区三区,无码中文字幕人妻在线一区二区三区,无码精品国产一区二区三区免费

一口Linux
認(rèn)證:優(yōu)質(zhì)創(chuàng)作者
所在專題目錄 查看專題
Linux中常見同步和互斥機(jī)制設(shè)計(jì)原理
Linux信號(hào)量(1)-SYSTEM V
Linux信號(hào)量(2)-POSIX 信號(hào)量
Linux信號(hào)量(3)-內(nèi)核信號(hào)量
兩個(gè)線程,兩個(gè)互斥鎖,怎么形成一個(gè)死循環(huán)?
Linux庫概念,動(dòng)態(tài)庫和靜態(tài)庫的制作,如何移植第三方庫
作者動(dòng)態(tài) 更多
某通信公司筆試題,你會(huì)做幾道?
2天前
10種初學(xué)者最常見的c語言段錯(cuò)誤實(shí)例及原因分析
05-30 12:13
linux系統(tǒng)監(jiān)控工具小神器:btop
05-17 17:37
有沒有權(quán)貴開后門讓子女做軟件開發(fā)人員?
05-10 23:36
一文包你學(xué)會(huì)網(wǎng)絡(luò)數(shù)據(jù)抓包
03-15 09:26

Linux信號(hào)量(1)-SYSTEM V

信號(hào)量概念

信號(hào)量本質(zhì)上是一個(gè)計(jì)數(shù)器(不設(shè)置全局變量是因?yàn)檫M(jìn)程間是相互獨(dú)立的,而這不一定能看到,看到也不能保證++引用計(jì)數(shù)為原子操作),用于多進(jìn)程對(duì)共享數(shù)據(jù)對(duì)象的讀取,它和管道有所不同,它不以傳送數(shù)據(jù)為主要目的,它主要是用來保護(hù)共享資源(信號(hào)量也屬于臨界資源),使得資源在一個(gè)時(shí)刻只有一個(gè)進(jìn)程獨(dú)享。

信號(hào)量分類

因?yàn)楦鞣N原因,Linux下有多種信號(hào)量實(shí)現(xiàn)機(jī)制,可以分別應(yīng)用于不同的場合,分類如下:

[信號(hào)量分類]

用戶信號(hào)量主要運(yùn)行于用戶態(tài),比如進(jìn)程間都要訪問某個(gè)文件,那么只有獲得信號(hào)量的進(jìn)程才能打開文件,其他進(jìn)程會(huì)進(jìn)入休眠,我們也可以查看當(dāng)前信號(hào)量的值,以判斷是否要進(jìn)入臨界區(qū)。

內(nèi)核信號(hào)量主要運(yùn)行于Linux內(nèi)核,主要實(shí)現(xiàn)對(duì)內(nèi)核臨界資源的互斥使用,比如某個(gè)設(shè)備只能被某一個(gè)進(jìn)程打開,無法打開設(shè)備的例程會(huì)導(dǎo)致用戶空間的進(jìn)程休眠。

POSIX有名信號(hào)量

主要應(yīng)用于線程。

 sem_t *sem_open(const char *name, int oflag, mode_t mode, int val);    int sem_wait(sem_t *sem);    int sem_trywait(sem_t *sem);    int sem_post(sem_t *sem);    int sem_close(sem_t *sem);    int sem_unlink(const char *name);

每個(gè)open的位置都要close和unlink,但只有最后執(zhí)行的unlink生效

POSIX無名信號(hào)量

主要應(yīng)用于線程。

#include<semaphore.h>sem_t sem;int sem_init(sem_t *sem, int pshared, unsigned int val); //pshared為0則線程間共享,pshared為1則父子進(jìn)程共享int sem_wait(sem_t *sem); //阻塞int sem_trywait(sem_t *sem); //非阻塞int sem_post(sem_t *sem);int sem_destroy(sem_t *sem);進(jìn)程間共享則sem必須放在共享內(nèi)存區(qū)域(mmap, shm_open, shmget),父進(jìn)程的全局變量、堆、棧中存儲(chǔ)是不行的

內(nèi)核信號(hào)量:

#include<asm/semaphore.h>void sema_init(struct semaphore *sem, int val);void down(struct semaphore *sem); //可睡眠int down_interruptible(struct semaphore *sem); //可中斷int down_trylock(struct semaphore *sem); //m非阻塞void up(struct semaphore *sem);

除此之外信號(hào)量還有一種分類方法

二值信號(hào)量(binary semaphore)和計(jì)數(shù)信號(hào)量(counting semaphore)。二值信號(hào)量:顧名思義,其值只有兩種0或1,相當(dāng)于互斥量,當(dāng)值為1時(shí)資源可用;而當(dāng)值為0時(shí),資源被鎖住,進(jìn)程阻塞無法繼續(xù)執(zhí)行。計(jì)數(shù)信號(hào)量:其值是在0到某個(gè)限制值之間的信號(hào)量。

信號(hào)量的工作原理

信號(hào)量只能進(jìn)行兩種操作等待和發(fā)送信號(hào),信號(hào)量操作總結(jié)起來,其核心是PV操作,P(sv)和V(sv),他們的行為是這樣的:

(1)P(sv):如果sv的值大于零,就給它減1;如果它的值為零,就掛起該進(jìn)程的執(zhí)行

(2)V(sv):如果有其他進(jìn)程因等待sv而被掛起,就讓它恢復(fù)運(yùn)行,如果沒有進(jìn)程因等待sv而掛起,就給它加1.

在信號(hào)量進(jìn)行PV操作時(shí)都為原子操作(因?yàn)樗枰Wo(hù)臨界資源)

注:原子操作:單指令的操作稱為原子的,單條指令的執(zhí)行是不會(huì)被打斷的

System V IPC

講解System V信號(hào)量之前,先了解下什么是System V IPC。

System V IPC一共有三種類型的IPC合稱為System V IPC:

  1. System V信號(hào)量
  2. System V消息隊(duì)列
  3. System V共享內(nèi)存

System V IPC在訪問它們的函數(shù)和內(nèi)核為它們維護(hù)的信息上有一些類似點(diǎn),主要包括:

  1. IPC鍵和ftok函數(shù)
  2. ipc_perm結(jié)構(gòu)
  3. 創(chuàng)建或打開時(shí)指定的用戶訪問權(quán)限
  4. ipcs和ipcrm命令

下表匯總了所有System V IPC函數(shù)。

信號(hào)量消息隊(duì)列共享內(nèi)存頭文件sys/sem.hsys/msg.hsys/shm.h創(chuàng)建或打開IPC的函數(shù)semgetmsggetshmget控制IPC操作的函數(shù)semctlmsgctlshmctlIPC操作函數(shù)semopmsgsnd msgrcvshmat shmdt

IPC鍵和ftok函數(shù)

三種類型的System V IPC都使用IPC鍵作為它們的標(biāo)識(shí),IPC鍵是一個(gè)key_t類型的整數(shù),該類型在sys/types.h中定義。IPC鍵通常是由ftok函數(shù)賦予的,該函數(shù)把一個(gè)已存在的路徑名pathname和一個(gè)非0整數(shù)id組合轉(zhuǎn)換成一個(gè)key_t值,即IPC鍵。

#include <sys/ipc.h>//成功返回IPC鍵,失敗返回-1key_t ftok(const char *pathname, int id);

參數(shù)說明:

  • pathname在是程序運(yùn)行期間必須穩(wěn)定存在,不能反復(fù)創(chuàng)建與刪除
  • id不能為0,可以是正數(shù)或者負(fù)數(shù)

ipc_perm結(jié)構(gòu)

內(nèi)核給每個(gè)IPC對(duì)象維護(hù)一個(gè)信息結(jié)構(gòu),即struct ipc_perm結(jié)構(gòu),該結(jié)構(gòu)及System V IPC函數(shù)經(jīng)常使用的常值定義在sys/ipc.h頭文件中。

struct ipc_perm{    uid_t   uid;   //owner's user id    gid_t   gid;   //owner's group id    uid_t   cuid;  //creator's group id    gid_t   cgid;  //creator's group id    mode_t  mode;  //read-write permissions    ulong_t seq;   //slot usage sequence number    key_t   key;   //IPC key};

創(chuàng)建與打開IPC對(duì)象

創(chuàng)建或打開一個(gè)IPC對(duì)象使用相應(yīng)的xxxget函數(shù),它們都有兩個(gè)共同的參數(shù):

  • 參數(shù)key,key_t類型的IPC鍵
  • 參數(shù)oflag,用于指定IPC對(duì)象的讀寫權(quán)限(ipc_perm.mode),并選擇是創(chuàng)建一個(gè)新的IPC對(duì)象還是打開一個(gè)已存在的IPC對(duì)象

對(duì)于參數(shù)key,應(yīng)用程序有兩種選擇:

  • 調(diào)用ftok,給它傳pathname和id
  • 指定key為IPC_PRIVATE,這將保證會(huì)創(chuàng)建一個(gè)新的、唯一的IPC對(duì)象,但該標(biāo)志不能用于打開已存在的IPC對(duì)象,只能是新建

對(duì)于參數(shù)oflag,如上所述,它包含讀寫權(quán)限、創(chuàng)建或打開這兩方面信息:

  • 可以指定IPC_CREAT標(biāo)志,其含義和Posix IPC的O_CREAT一樣
  • 還可以設(shè)置為下表所示的常值來指定讀寫權(quán)限

ipcs和ipcrm命令

由于System V IPC的三種類型不是以文件系統(tǒng)路徑名標(biāo)識(shí)的,因此無法使用ls和rm命令查看與刪除它們ipcs和ipcrm分別用于查看與刪除系統(tǒng)中的System V IPC
usage : ipcs -asmq -tclup 
    ipcs [-s -m -q] -i id
    ipcs -h for help.
usage: ipcrm [ [-q msqid] [-m shmid] [-s semid]
          [-Q msgkey] [-M shmkey] [-S semkey] ... ]

SYSTEM V 信號(hào)量

SystemV信號(hào)量并不如Posix信號(hào)量那樣“好用”,但相比之下它的年代更加久遠(yuǎn),但是SystemV使用的卻更加廣泛(尤其是在老系統(tǒng)中)。

System V信號(hào)量是指的計(jì)數(shù)信號(hào)量集(set of counting semaphores),是一個(gè)或多個(gè)信號(hào)量的集合,其中每個(gè)都是計(jì)數(shù)信號(hào)量。(注:System V 信號(hào)量是計(jì)數(shù)信號(hào)量集,Posix 信號(hào)量是單個(gè)計(jì)數(shù)信號(hào)量。)

所有函數(shù)共用頭文件

#include <sys/types.h>#include <sys/ipc.h>#include <sys/sem.h>

創(chuàng)建信號(hào)量

int semget(key_t key,int nsems,int flags)//返回:成功返回信號(hào)集ID,出錯(cuò)返回-1
  • (1)第一個(gè)參數(shù)key是長整型(唯一非零),系統(tǒng)建立IPC通訊 ( 消息隊(duì)列、 信號(hào)量和 共享內(nèi)存) 時(shí)必須指定一個(gè)ID值。通常情況下,該id值通過ftok函數(shù)得到,由內(nèi)核變成標(biāo)識(shí)符,要想讓兩個(gè)進(jìn)程看到同一個(gè)信號(hào)集,只需設(shè)置key值不變就可以。

  • (2)第二個(gè)參數(shù)nsem指定信號(hào)量集中需要的信號(hào)量數(shù)目,它的值幾乎總是1。

  • (3)第三個(gè)參數(shù)flag是一組標(biāo)志,當(dāng)想要當(dāng)信號(hào)量不存在時(shí)創(chuàng)建一個(gè)新的信號(hào)量,可以將flag設(shè)置為IPC_CREAT與文件權(quán)限做按位或操作。設(shè)置了IPC_CREAT標(biāo)志后,即使給出的key是一個(gè)已有信號(hào)量的key,也不會(huì)產(chǎn)生錯(cuò)誤。而IPC_CREAT | IPC_EXCL則可以創(chuàng)建一個(gè)新的,唯一的信號(hào)量,如果信號(hào)量已存在,返回一個(gè)錯(cuò)誤。一般我們會(huì)還或上一個(gè)文件權(quán)限

刪除和初始化信號(hào)量

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

功能:信號(hào)量控制操作。參數(shù):semid標(biāo)示操作的信號(hào)量集;semnum標(biāo)示該信號(hào)量集內(nèi)的某個(gè)成員(0,1等,直到nsems-1),semnum值僅僅用于GETVAL,SETVAL,GETNCNT,GETZCNT,GETPID,通常取值0,也就是第一個(gè)信號(hào)量;cmd:指定對(duì)單個(gè)信號(hào)量的各種操作,IPC_STAT,IPC_GETVAL,IPC_SETVAL,IPC_RMID;arg: 可選參數(shù),取決了第三個(gè)參數(shù)cmd。返回值:若成功,根據(jù)cmd不同返回不同的值,IPC_STAT,IPC_SETVAL,IPC_RMID返回0,IPC_GETVAL返回信號(hào)量當(dāng)前值;出錯(cuò)返回-1.

如有需要第四個(gè)參數(shù)一般設(shè)置為union semnu arg;定義如下

union semun
{ 
  int val;  //使用的值
  struct semid_ds *buf;  //IPC_STAT、IPC_SET 使用的緩存區(qū)
  unsigned short *arry;  //GETALL,、SETALL 使用的數(shù)組
  struct seminfo *__buf; // IPC_INFO(Linux特有) 使用的緩存區(qū)
};
  • (1)sem_id是由semget返回的信號(hào)量標(biāo)識(shí)符
  • (2)semnum當(dāng)前信號(hào)量集的哪一個(gè)信號(hào)量
  • (3)cmd通常是下面兩個(gè)值中的其中一個(gè) SETVAL:用來把信號(hào)量初始化為一個(gè)已知的值。p 這個(gè)值通過union semun中的val成員設(shè)置,其作用是在信號(hào)量第一次使用前對(duì)它進(jìn)行設(shè)置。IPC_RMID:用于刪除一個(gè)已經(jīng)無需繼續(xù)使用的信號(hào)量標(biāo)識(shí)符,刪除的話就不需要缺省參數(shù),只需要三個(gè)參數(shù)即可。

結(jié)構(gòu)體

由于system v信號(hào)量是伴隨著內(nèi)核的啟動(dòng)而生成,我們可以在源碼文件sem.c中看到static struct ipc_ids sem_ids;它是system v信號(hào)量的入口,因此在系統(tǒng)運(yùn)行過程中是一直存在的。它所保存的信息是資源(在sem中是信號(hào)量集,也可以是msg,shm)的信息。如:

  struct ipc_ids {
      int in_use;//說明已分配的資源個(gè)數(shù)
      int max_id;/在使用的最大的位置索引
      unsigned short seq;//下一個(gè)分配的位置序列號(hào)
      unsigned short seq_max;//最大位置使用序列
      struct semaphore sem; //保護(hù) ipc_ids的信號(hào)量
      struct ipc_id_ary nullentry;//如果IPC資源無法初始化,則entries字段指向偽數(shù)據(jù)結(jié)構(gòu)
      struct ipc_id_ary* entries;//指向資源ipc_id_ary數(shù)據(jù)結(jié)構(gòu)的指針
    };

它的最后一個(gè)元素 entries指向struct ipc_id_ary這樣一個(gè)數(shù)據(jù)結(jié)構(gòu),它有兩個(gè)成員:

struct ipc_id_ary {
 int size;//保存的是數(shù)組的長度值
 struct kern_ipc_perm *p[0];//它是個(gè)指針數(shù)組 ,數(shù)組長度可變,內(nèi)核初始化后它的值為128
};

正如我們?cè)谏蠄D看到的,sem_ids.entries->p指向sem_array這個(gè)數(shù)據(jù)結(jié)構(gòu),為什么呢?

我們看信號(hào)量集sem_array這個(gè)數(shù)據(jù)結(jié)構(gòu):

/* One sem_array data structure for each set of semaphores in the system. */
struct sem_array {
   struct kern_ipc_perm sem_perm; /* permissions .. see ipc.h */
   time_t   sem_otime; /* last semop time */
   time_t   sem_ctime; /* last change time */
   struct sem  *sem_base; /* ptr to first semaphore in array */指向信號(hào)量隊(duì)列
   struct sem_queue *sem_pending; /* pending operations to be processed */指向掛起隊(duì)列的首部
   struct sem_queue **sem_pending_last; /* last pending operation */指向掛起隊(duì)列的尾部
   struct sem_undo  *undo;  /* undo requests on this array */信號(hào)量集上的 取消請(qǐng)求
   unsigned long  sem_nsems; /* no. of semaphores in array */信號(hào)量集中的信號(hào)量的個(gè)數(shù)
};

這樣sem_ids.entries就跟信號(hào)量集sem_array關(guān)聯(lián)起來了,但是為什么要通過kern_ipc_perm關(guān)聯(lián)呢,為什么不直接由sem_ids指向sem_array呢,這是因?yàn)樾盘?hào)量,消息隊(duì)列,共享內(nèi)存實(shí)現(xiàn)的機(jī)制基本差不多,所以他們都是通過ipc_id_ary這個(gè)數(shù)據(jù)結(jié)構(gòu)管理,而通過kern_ipc_perm,他們與各自的數(shù)據(jù)結(jié)構(gòu)關(guān)聯(lián)起來。這樣就清楚了!在后面我們來看內(nèi)核函數(shù)sys_semget()是如何進(jìn)行創(chuàng)建信號(hào)量集,并將其加入到sem_ids.entries中的。

改變信號(hào)量的值

int semop(int semid, struct sembuf *sops, size_t nops);

功能:操作信號(hào)量,P,V 操作

參數(shù):semid:信號(hào)量集標(biāo)識(shí)符;nops是opstr數(shù)組中元素?cái)?shù)目,通常取值為1;opstr指向一個(gè)結(jié)構(gòu)數(shù)組 nsops:進(jìn)行操作信號(hào)量的個(gè)數(shù),即sops結(jié)構(gòu)變量的個(gè)數(shù),需大于或等于1。最常見設(shè)置此值等于1,只完成對(duì)一個(gè)信號(hào)量的操作 sembuf的定義如下:

struct sembuf{  short sem_num;   //除非使用一組信號(hào)量,否則它為0  short sem_op; //信號(hào)量在一次操作中需要改變的數(shù)據(jù),通   //常是兩個(gè)數(shù),一個(gè)是-1,即P(等待)操作,   //一個(gè)是+1,即V(發(fā)送信號(hào))操作。  short sem_flg; //通常為SEM_UNDO,使操作系統(tǒng)跟蹤   //信號(hào)量,并在進(jìn)程沒有釋放該信號(hào)量而終止時(shí),操作系統(tǒng)釋放信號(hào)量 };

返回值:成功返回信號(hào)量標(biāo)識(shí)符,出錯(cuò)返回-1

一般編程步驟:

  1. 創(chuàng)建信號(hào)量或獲得在系統(tǒng)中已存在的信號(hào)量 1). 調(diào)用semget(). 2). 不同進(jìn)程使用同一個(gè)信號(hào)量鍵值來獲得同個(gè)信號(hào)量
  2. 初始化信號(hào)量 1).使用semctl()函數(shù)的SETVAL操作 2).當(dāng)使用二維信號(hào)量時(shí),通常將信號(hào)量初始化為1
  3. 進(jìn)行信號(hào)量PV操作 1). 調(diào)用semop()函數(shù) 2). 實(shí)現(xiàn)進(jìn)程之間的同步和互斥
  4. 如果不需要該信號(hào)量,從系統(tǒng)中刪除 1).使用semctl()函數(shù)的IPC_RMID操作

實(shí)例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#define USE_SYSTEMV_SEM 1
#define DELAY_TIME 2
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};
// 將信號(hào)量sem_id設(shè)置為init_value
int init_sem(int sem_id,int init_value) {
    union semun sem_union;
    sem_union.val=init_value;
    if (semctl(sem_id,0,SETVAL,sem_union)==-1) {
        perror("Sem init");
        exit(1);
    }
    return 0;
}
// 刪除sem_id信號(hào)量
int del_sem(int sem_id) {
    union semun sem_union;
    if (semctl(sem_id,0,IPC_RMID,sem_union)==-1) {
        perror("Sem delete");
        exit(1);
    }
    return 0;
}
// 對(duì)sem_id執(zhí)行p操作
int sem_p(int sem_id) {
    struct sembuf sem_buf;
    sem_buf.sem_num=0;//信號(hào)量編號(hào)
    sem_buf.sem_op=-1;//P操作
    sem_buf.sem_flg=SEM_UNDO;//系統(tǒng)退出前未釋放信號(hào)量,系統(tǒng)自動(dòng)釋放
    if (semop(sem_id,&sem_buf,1)==-1) {
        perror("Sem P operation");
        exit(1);
    }
    return 0;
}
// 對(duì)sem_id執(zhí)行V操作
int sem_v(int sem_id) {
    struct sembuf sem_buf;
    sem_buf.sem_num=0;
    sem_buf.sem_op=1;//V操作
    sem_buf.sem_flg=SEM_UNDO;
    if (semop(sem_id,&sem_buf,1)==-1) {
        perror("Sem V operation");
        exit(1);
    }
    return 0;
}
int main() {
    pid_t pid;
#if USE_SYSTEMV_SEM
    int sem_id;
    key_t sem_key;
    sem_key=ftok(".",'A');
    printf("sem_key=%x\n",sem_key);
    //以0666且create mode創(chuàng)建一個(gè)信號(hào)量,返回給sem_id
    sem_id=semget(sem_key,1,0666|IPC_CREAT);
    printf("sem_id=%x\n",sem_id);
    //將sem_id設(shè)為1
    init_sem(sem_id,1);
#endif
    if ((pid=fork())<0) {
        perror("Fork error!\n");
        exit(1);
    } else if (pid==0) {
#if USE_SYSTEMV_SEM
        sem_p(sem_id); //    P操作
#endif
        printf("Child running...\n");
        sleep(DELAY_TIME);
        printf("Child %d,returned value:%d.\n",getpid(),pid);
#if USE_SYSTEMV_SEM
        sem_v(sem_id); //    V操作
#endif
        exit(0);
    } else {
#if USE_SYSTEMV_SEM
        sem_p(sem_id); //    P操作
#endif
        printf("Parent running!\n");
        sleep(DELAY_TIME);
        printf("Parent %d,returned value:%d.\n",getpid(),pid);
#if USE_SYSTEMV_SEM
        sem_v(sem_id); //    V操作
        waitpid(pid,0,0);
        del_sem(sem_id);
#endif
        exit(0);
    }
}

運(yùn)行結(jié)果如下:

 

聲明:本內(nèi)容為作者獨(dú)立觀點(diǎn),不代表電子星球立場。未經(jīng)允許不得轉(zhuǎn)載。授權(quán)事宜與稿件投訴,請(qǐng)聯(lián)系:editor@netbroad.com
覺得內(nèi)容不錯(cuò)的朋友,別忘了一鍵三連哦!
贊 3
收藏 1
關(guān)注 181
成為作者 賺取收益
全部留言
0/200
成為第一個(gè)和作者交流的人吧