Linux POSIX Semaphore的使用

目的

Semaphores are not used to transfer data between processes. Instead, they allow processes to synchronize their actions. One common use of a semaphore is to synchronize access to a block of shared memory in order to prevent one process from accessing the shared memory at the same time as another process is updating it.
---- Chapter 47: System V Semaphore <<The Linux Programming Interface>>
簡而言之,信號量的使用是為了進(jìn)程同步的用途,避免一個進(jìn)程訪問一個共享內(nèi)存時候被另一個進(jìn)程更新。

對于POSIX semaphore而言,信號量可以允許進(jìn)程和線程進(jìn)行同步,而訪問共享的資源。

原理

semaphore是內(nèi)核維護(hù)的一個整數(shù),它的值被嚴(yán)格限制大于或者等于0. 信號量是非負(fù)整數(shù)。 那么我們可以想一下維護(hù)一個數(shù)字需要做什么? 賦值,增加,減少,判斷,那么對應(yīng)信號量的操作

  • 設(shè)置它為一個正整數(shù)的值
  • 增加這個數(shù)字
  • 減少這個數(shù)字
  • 等待這個數(shù)字變?yōu)?
    在有一些書上,會把信號量的操作叫做PV操作,為啥是P,因為這個是荷蘭計算機(jī)科學(xué)家Dijkstra,就是那個最短路徑遍歷算法的提出者發(fā)明的。在荷蘭語中P表示Prolaag表示減少, V表示Verhoog是增加的意思。P表示W(wǎng)ait, V表示Signal。

類型

SUSv3 闡釋了兩種POSIX 信號量:

命名信號量,named semaphores

這種信號量有一個名字,通過其名字來調(diào)用sem_open()函數(shù),無關(guān)的進(jìn)程也可以訪問相同的信號量

非命名信號量,unamed semephores

這種信號量并沒有一個名字,他們存在于一個約定好的內(nèi)存空間。無名信號量可以被進(jìn)程間,或者一組線程們進(jìn)行共享。被進(jìn)程共享的時候,信號量存在于共享內(nèi)存的區(qū)域。但是被進(jìn)程共享時,信號量存在于被進(jìn)程共享的內(nèi)存區(qū)域里。


關(guān)于Named Semaphores

操作

  • The sem_open() function opens or creates a semaphore, initializes the semaphore
    if it is created by the call, and returns a handle for use in later calls.
  • The sem_post(sem) and sem_wait(sem) functions respectively increment and decrement
    a semaphore’s value.
  • The sem_getvalue() function retrieves a semaphore’s current value.
  • The sem_close() function removes the calling process’s association with a semaphore
    that it previously opened.
  • The sem_unlink() function removes a semaphore name and marks the semaphore
    for deletion when all processes have closed it.

sem_open

sem_t *sem_open(const char *name, int oflag, mode_t mode, ..., unsigned int value */ ); //Returns pointer to semaphore on success, or SEM_FAILED on error

打開(創(chuàng)建)新的信號量

第二個參數(shù)是open flag,它是一個bit mask參數(shù)。

  • 如果oflag為0,我們會訪問一個已經(jīng)存在的信號量。
  • 如果oflag為 O_CREAT,系統(tǒng)會根據(jù)第一個參數(shù)(name)判斷這個名稱的信號量是否存在,如果不存在創(chuàng)建一個新的信號量。 即不存在則創(chuàng)建新的。
  • 如果oflag為O_CREAT | O_EXCL, 就是兩個條件都要滿足進(jìn)行創(chuàng)建。那么如果第一個參數(shù)name的信號量存在,sem_open會返回-1失敗,原因是O_EXCL表示 exclusive 排他的。所以如果調(diào)用sem_open("xxx", O_CREAT | O_EXCL,...) == -1,表示這個信號量已經(jīng)存在

打開已經(jīng)存在的信號量

sem_open打開一個已經(jīng)存在的信號量,那么只需要兩個參數(shù)(name, oflag)。如果O_CREAT存在在oflag中,那么還需要兩個參數(shù),就是mode 和value. 這個很明顯么,你想創(chuàng)建一個新的信號量,那么你就要提供創(chuàng)建所需要的信息。(如果信號量存在的話,這兩個參數(shù)會被忽略)

mode參數(shù)

mode也是一個bit mask的參數(shù),這個bit value和打開文件的相似。一般來說一共有三種Open Mode,即 O_RDONLY, O_WRONLY, O_RDWR,這個前綴O表示Open。
在打開一個semaphore是需要進(jìn)行部署sem_post和sem_wait,所以需要Read 和Write權(quán)限。

perms參數(shù)

Linux permissions 參數(shù)是以Linux中權(quán)限組來劃分的,一般來說Linxu把所有用戶分為 所有者USER, 群組GROUP, 其他人OTHERS 還有超級用戶ROOT,但是ROOT這里一般不參與討論。
這里直接用圖表示更加直接。

image.png

image.png

對于Liux的文件stat.h中的定義,首先可以參考這個Permission-Bits
常用的有 , 這里S前綴表示Set

  • S_IRUSR 用戶讀權(quán)限,
  • S_IWUSR 用戶寫權(quán)限
  • 沒有 用戶讀寫權(quán)限
  • S_IRWXU 用戶讀,寫,執(zhí)行權(quán)限

  • S_IRGRP 群組讀權(quán)限
  • S_IWGRP 群組寫權(quán)限
  • S_IRWXG 群組讀,寫,執(zhí)行權(quán)限

  • S_IROTH 其他人讀權(quán)限
  • S_IWOTH 其他人寫權(quán)限
  • S_IRWXO 其他人讀,寫,執(zhí)行權(quán)限

根據(jù)上圖中的映射關(guān)系,S_IRUSR 表示二進(jìn)制, 0b100000000, 這個數(shù)的十進(jìn)制是 2^8 = 256, 在linux試一下跟預(yù)期一樣
printf("%d", S_IRUSR);,輸出256.


當(dāng)然每一個權(quán)限是可以拆開的,比如用戶的讀,寫,執(zhí)行可以分別表示為 S_IRUSR S_IWUSR S_IXUSR,同理其他也一樣。 事實上,在stat.h文件里面是這樣定義的。

//宏定義的基礎(chǔ)定義,
#define __S_IREAD   0400    /* Read by owner.  */
#define __S_IWRITE  0200    /* Write by owner.  */
#define __S_IEXEC   0100    /* Execute by owner.  */
//用戶
#define S_IRUSR __S_IREAD   /* Read by owner. 這個宏就是 0x 04  */
#define S_IWUSR __S_IWRITE  /* Write by owner.  */
#define S_IXUSR __S_IEXEC   /* Execute by owner.  */
#define S_IRWXU (__S_IREAD|__S_IWRITE|__S_IEXEC) /* Read, write, and execute by owner.  */
//群組
#define S_IRGRP (S_IRUSR >> 3)  /* Read by group.  */
#define S_IWGRP (S_IWUSR >> 3)  /* Write by group.  */
#define S_IXGRP (S_IXUSR >> 3)  /* Execute by group.  */
#define S_IRWXG (S_IRWXU >> 3)  /* Read, write, and execute by group.  */
//其他人
#define S_IROTH (S_IRGRP >> 3)  /* Read by others.  */
#define S_IWOTH (S_IWGRP >> 3)  /* Write by others.  */
#define S_IXOTH (S_IXGRP >> 3)  /* Execute by others.  */
#define S_IRWXO (S_IRWXG >> 3) /* Read, write, and execute by others.  */

可以看到,最基礎(chǔ)的是 0400 這個數(shù)字,這個數(shù)字在C語言因為是以0開頭,所以是八進(jìn)制表示,它表示 4 * (8^2) = 256; 因此在賦值的時候也可以直接用數(shù)字0400賦值。
所以 S_IRUSR 表示 二進(jìn)制 0b100000000, 八進(jìn)制 0400, 十六進(jìn)制 0x100,對于S_IRGRP 表示對于S_IRUSR 進(jìn)行右移3位,即0b100000000變成 0b100000[000]最后三位去掉,變成0b000100000,這個數(shù)字是32. 如果變成S_IROTH,就是再向右移動3bit,變成0b100,即4. 所以右移也是整除2的操作,移動一次表示除以2. 其他可以類似推到

value

就是需要傳給新的信號量的初始值,這個數(shù)字是非負(fù)整數(shù)。

特別注意

如果sem_open失敗,返回的是 叫做 SEM_FAILED的一個值,這個值可不是簡單的-1.我們看一下Linux環(huán)境下的源代碼

#if __WORDSIZE == 64
# define __SIZEOF_SEM_T 32
#else
# define __SIZEOF_SEM_T 16
#endif

/* Value returned if `sem_open' failed.  */
#define SEM_FAILED      ((sem_t *) 0)

typedef union
{
  char __size[__SIZEOF_SEM_T];
  long int __align;
} sem_t;

sem_t是一個共用體,SEM_FAILED 是一個共用體指針。
SEM_FAILED 可能被定義為 *((sem_t *) 0) or ((sem_t ) –1), Linux中是以 (sem_t *) 0) 定義的

sem_close

//Returns 0 on success, or –1 on error
int sem_close(sem_t *sem); 

這個是關(guān)閉信號量,并沒有刪除它,如果要刪除,需要調(diào)用sem_unlink()函數(shù)

sem_wait

如果信號量當(dāng)前的值是大于0,sem_wait會立即返回。
如果信號量的值就是0,sem_wait會阻塞. 直到這個值增加超過0,然后sem_wait會立即返回,就是說,這個函數(shù)是檢測信號量的值,如果信號量為正整數(shù),表示這個資源可以被訪問,因此直接通過,如果為0則阻塞, 表示沒有資源可以被訪問。

sem_trywait

sem_trywait函數(shù)是sem_wait的非阻塞版本。

sem_post

信號量的值加1

sem_getvalue

int sem_getvalue(sem_t *sem, int *sval);
獲取當(dāng)前信號量


Unnamed Semaphores 非命名信號量

非命名信號量使用相同的函數(shù), sem_wait(), sem_post(), sem_getvalue()
兩個不一樣的函數(shù)sem_init 和 sem_destroy()

sem_init

//Returns 0 on success, or –1 on error, 這個和sem_open不一樣,sem_open函數(shù) 如果失敗返回的是SEM_FAILED,不是-1
int sem_init(sem_t *sem, int pshared, unsigned int value);
- If pshared is 0, then the semaphore is to be shared between the threads of the
calling process. In this case, sem is typically specified as the address of either a
global variable or a variable allocated on the heap. A thread-shared semaphore
has process persistence; it is destroyed when the process terminates.
- If pshared is nonzero, then the semaphore is to be shared between processes. In
this case, sem must be the address of a location in a region of shared memory (a
POSIX shared memory object, a shared mapping created using mmap(), or a
System V shared memory segment). The semaphore persists as long as the
shared memory in which it resides. (The shared memory regions created by
most of these techniques have kernel persistence. The exception is shared
anonymous mappings, which persist only as long as at least one process maintains
the mapping.) Since a child produced via fork() inherits its parent’s memory
mappings, process-shared semaphores are inherited by the child of a fork(), and
the parent and child can use these semaphores to synchronize their actions.

這里可知,

  1. pshared 這個參數(shù)如果是0,是被線程進(jìn)行共享。sem這個參數(shù)指向一個全局變量或者分配在heap(堆)上的一個變量,這里就說明,這個變量大概率是通過malloc分配的
    為非負(fù)數(shù),因為linux c里面沒有c++ new這樣的操作符。找了個例子.
#include <stdlib.h>
int main()
{
  int 1 = 1;
  int *pi = (int*)malloc(sizeof(int)); //this variable pi is allocated at heap region
  *pi = 2;
  //...
  free(pi);
}

并且這個信號量在進(jìn)程期間都會存在,只有當(dāng)進(jìn)程結(jié)束才會被釋放。

  1. pshared 為非負(fù)整數(shù),信號量被進(jìn)程共享。sem指向共享內(nèi)存(POSIX shared memory object, a shared mapping created using mmap(), or a
    System V shared memory segment)。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容