什么是信號(hào)量?

信號(hào)量的使用主要用于保護(hù)共享資源,資源一次只能擁有一個(gè)進(jìn)程(線程)。

信號(hào)量值為正數(shù)表示空閑。測試的線程可以鎖定和使用。如果為0,則它被占用,測試的線程必須進(jìn)入睡眠隊(duì)列,等待醒來。

為了避免多個(gè)程序同時(shí)訪問一個(gè)共享資源時(shí)出現(xiàn)的一系列問題,需要一種創(chuàng)建和使用令牌授予權(quán)限的方法。一次只能有一個(gè)執(zhí)行線程訪問代碼的臨界區(qū)域。

臨界區(qū)域意味著必須單獨(dú)運(yùn)行執(zhí)行數(shù)據(jù)更新的代碼。信號(hào)量提供了一種訪問機(jī)制,使臨界區(qū)域一次只能訪問一個(gè)線程。換句話說,信號(hào)量用于協(xié)調(diào)對共享資源的進(jìn)程訪問。

信號(hào)量是程序訪問的原子操作,是只允許大氣(即P(信號(hào)變量))和傳輸(即V(信號(hào)變量))信息操作的特殊變量。

最簡單的信號(hào)量是只能取0和1的變量,這是最常見的形式,稱為二進(jìn)制信號(hào)量。可以取多個(gè)正整數(shù)的信號(hào)量稱為通用信號(hào)量。這里主要討論二進(jìn)制信號(hào)量。

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

信號(hào)量只能執(zhí)行兩個(gè)操作等待和傳輸信號(hào)P(sv)和V(sv),因此行為如下:

P(sv):如果sv值大于0,則減去1。值為0表示暫停進(jìn)程的執(zhí)行V(sv)。如果在等待SV時(shí)其他進(jìn)程暫停,則繼續(xù)運(yùn)行;如果在等待SV時(shí)沒有暫停的進(jìn)程,則添加1。例如,如果兩個(gè)進(jìn)程共享信號(hào)量sv,一個(gè)進(jìn)程執(zhí)行P(sv)操作,則第二個(gè)進(jìn)程在嘗試執(zhí)行P(sv)時(shí)SV為零,第一個(gè)進(jìn)程超出臨界區(qū)域,V(sv)釋放信號(hào)量之前將暫停,從而防止進(jìn)入臨界區(qū)域。然后,第二個(gè)進(jìn)程可以恢復(fù)運(yùn)行。

信號(hào)量的分類

在學(xué)習(xí)信號(hào)量之前,我們首先要知道—— Linux提供了兩種信號(hào)量。

內(nèi)核信號(hào)量是內(nèi)核控制路徑中用戶狀態(tài)進(jìn)程使用的信號(hào)量,分為POSIX信號(hào)量和SYSTEM V信號(hào)量。POSIX信號(hào)量分為著名信號(hào)量和無名信號(hào)量。

具有文件中存儲(chǔ)的值的著名信號(hào)量,可用于線程或進(jìn)程之間的同步。無名信號(hào)量,其值存儲(chǔ)在內(nèi)存中。

POSIX信號(hào)量與SYSTEM V信號(hào)量的比較

對于POSIX,信號(hào)量為非負(fù)整數(shù)。經(jīng)常用于線程之間的同步。

SYSTEM V信號(hào)量是一個(gè)或多個(gè)信號(hào)量的集合,相當(dāng)于為SYSTEM V IPC服務(wù)的信號(hào)量結(jié)構(gòu),信號(hào)量只是其中的一小部分。經(jīng)常用于進(jìn)程間同步。POSIX信號(hào)量參考頭文件如下:系統(tǒng)v信號(hào)量引用頭文件為sy。從使用角度來看,System V信號(hào)量很復(fù)雜,但Posix信號(hào)量很簡單。例如,創(chuàng)建和初始化POSIX信號(hào)量或操作PV非常方便。內(nèi)核信號(hào)量

Linux內(nèi)核的信號(hào)量在概念上與用戶狀態(tài)下System V的IPC機(jī)制信號(hào)量相同,但不能在內(nèi)核外部使用。是睡眠鎖。

如果有任務(wù)想獲得已占用的信號(hào)量,信號(hào)量會(huì)放在等待隊(duì)列中(不是在外面發(fā)呆等,而是把自己的名字寫在工作隊(duì)列中),讓你睡覺。

如果持有信號(hào)量的進(jìn)程釋放信號(hào),則等待隊(duì)列中的一個(gè)作業(yè)將被喚醒(因?yàn)殛?duì)列中可能有多個(gè)作業(yè)),并獲得信號(hào)量。

這與自旋鎖不同,處理器可以執(zhí)行不同的代碼。

與自旋鎖的區(qū)別:爭奪信號(hào)量的過程可以在等待鎖再次可用時(shí)睡覺,所以信號(hào)量適用于鎖保持很長時(shí)間的情況。

相反,鎖定、保持等待隊(duì)列和喚醒開銷可能比鎖所占的總時(shí)間表更長,因此,在鎖定短期內(nèi)使用信號(hào)量是不合適的。

執(zhí)行線程在發(fā)生鎖爭用時(shí)睡覺,因此無法在中斷上下文中調(diào)試,因此只能在進(jìn)程上下文中獲取信號(hào)量鎖定。有信號(hào)量的進(jìn)行也可以睡覺,當(dāng)然也可以不睡覺。因?yàn)槿绻渌M(jìn)程爭奪信號(hào)量,就不會(huì)因此陷入僵局。旋轉(zhuǎn)鎖不能睡覺,信號(hào)量鎖可以睡覺,所以信號(hào)量和自旋鎖不能同時(shí)占據(jù)。相對來說,信號(hào)量比較簡單,不禁止內(nèi)核搶占,可以搶占持有信號(hào)量的代碼。

信號(hào)量的另一個(gè)特點(diǎn)是允許多個(gè)持有人,而自旋鎖在任何時(shí)候都只能允許一個(gè)持有人。

當(dāng)然,我們經(jīng)常只遇到一個(gè)持有者。這種信號(hào)量稱為二元信號(hào)量或互斥信號(hào)量。允許多個(gè)持有人的信號(hào)量稱為計(jì)數(shù)信號(hào)量,初始化時(shí)必須說明允許的最大持有人數(shù)(Count值)

信號(hào)量在創(chuàng)建時(shí)必須設(shè)置初始值。也就是說,可以同時(shí)在多個(gè)操作中訪問受該信號(hào)量保護(hù)的共享資源。初始值1將成為互斥體(Mutex)。也就是說,一次只能有一個(gè)作業(yè)訪問受信號(hào)量保護(hù)的共享資源。

任務(wù)訪問信號(hào)量保護(hù)的共享資源后,釋放信號(hào)量,信號(hào)量值加1即可。如果信號(hào)量值不是正數(shù),則意味著現(xiàn)在有等待信號(hào)量的任務(wù)。因此,所有等待信號(hào)量的任務(wù)也會(huì)被喚醒。(大衛(wèi)亞設(shè))。

需要C/C

Linux服務(wù)器架構(gòu)師學(xué)習(xí)資料私信“資料”(資料包括C/C++,Linux,golang技術(shù),Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協(xié)程,DPDK,ffmpeg等),免費(fèi)分享

內(nèi)核信號(hào)量的構(gòu)成


內(nèi)核信號(hào)量類似于自旋鎖,因?yàn)楫?dāng)鎖關(guān)閉著時(shí),它不允許內(nèi)核控制路徑繼續(xù)進(jìn)行。然而,當(dāng)內(nèi)核控制路徑試圖獲取內(nèi)核信號(hào)量鎖保護(hù)的忙資源時(shí),相應(yīng)的進(jìn)程就被掛起。只有在資源被釋放時(shí),進(jìn)程才再次變?yōu)榭蛇\(yùn)行。
只有可以睡眠的函數(shù)才能獲取內(nèi)核信號(hào)量;中斷處理程序和可延遲函數(shù)都不能使用內(nèi)核信號(hào)量。
內(nèi)核信號(hào)量是struct semaphore類型的對象,在內(nèi)核源碼中位于include\linux\文件

struct semaphore {    atomic_t count;    int sleepers;    wait_queue_head_t wait; }

內(nèi)核信號(hào)量中的等待隊(duì)列


上面已經(jīng)提到了內(nèi)核信號(hào)量使用了等待隊(duì)列wait_queue來實(shí)現(xiàn)阻塞操作。

當(dāng)某任務(wù)由于沒有某種條件沒有得到滿足時(shí),它就被掛到等待隊(duì)列中睡眠。當(dāng)條件得到滿足時(shí),該任務(wù)就被移出等待隊(duì)列,此時(shí)并不意味著該任務(wù)就被馬上執(zhí)行,因?yàn)樗直灰七M(jìn)工作隊(duì)列中等待CPU資源,在適當(dāng)?shù)臅r(shí)機(jī)被調(diào)度。

內(nèi)核信號(hào)量是在內(nèi)部使用等待隊(duì)列的,也就是說該等待隊(duì)列對用戶是隱藏的,無須用戶干涉。由用戶真正使用的等待隊(duì)列我們將在另外的篇章進(jìn)行詳解。

內(nèi)核信號(hào)量的相關(guān)函數(shù)


初始化


#define __SEMAPHORE_INITIALIZER(name, n) \ { \ .lock = __SPIN_LOCK_UNLOCKED((name).lock), \ .count = n, \ .wait_list = LIST_HEAD_INIT((name).wait_list), \ }

該宏聲明一個(gè)信號(hào)量name是直接將結(jié)構(gòu)體中count值設(shè)置成n,此時(shí)信號(hào)量可用于實(shí)現(xiàn)進(jìn)程間的互斥量。

#define DECLARE_MUTEX(name) \ struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)

該宏聲明一個(gè)互斥鎖name,但把它的初始值設(shè)置為1

void sema_init (struct semaphore *sem, int val);

該函用于數(shù)初始化設(shè)置信號(hào)量的初值,它設(shè)置信號(hào)量sem的值為val。

#define init_MUTEX(sem) sema_init(sem, 1)

該函數(shù)用于初始化一個(gè)互斥鎖,即它把信號(hào)量sem的值設(shè)置為1。

#define init_MUTEX_LOCKED(sem) sema_init(sem, 0)

該函數(shù)也用于初始化一個(gè)互斥鎖,但它把信號(hào)量sem的值設(shè)置為0,即一開始就處在已鎖狀態(tài)。

注意:對于信號(hào)量的初始化函數(shù)Linux最新版本存在變化,如init_MUTEX和init_MUTEX_LOCKED等初始化函數(shù)目前新的內(nèi)核中已經(jīng)沒有或者更換了了名字等

因此建議以后在編程中遇到需要使用信號(hào)量的時(shí)候盡量采用sema_init(struct semaphore *sem, int val)函數(shù),因?yàn)檫@個(gè)函數(shù)就目前為止從未發(fā)生變化。

獲取信號(hào)量–申請內(nèi)核信號(hào)量所保護(hù)的資源


void down(struct semaphore * sem);

該函數(shù)用于獲得信號(hào)量sem,它會(huì)導(dǎo)致睡眠,因此不能在中斷上下文(包括IRQ上下文和softirq上下文)使用該函數(shù)。該函數(shù)將把sem的值減1,如果信號(hào)量sem的值非負(fù),就直接返回,否則調(diào)用者將被掛起,直到別的任務(wù)釋放該信號(hào)量才能繼續(xù)運(yùn)行。

int down_interruptible(struct semaphore * sem);

該函數(shù)功能與down類似,不同之處為,down不會(huì)被信號(hào)(signal)打斷,但down_interruptible能被信號(hào)(比如Ctrl+C)打斷,因此該函數(shù)有返回值來區(qū)分是正常返回還是被信號(hào)中斷,如果返回0,表示獲得信號(hào)量正常返回,如果被信號(hào)打斷,返回-EINTR

int down_trylock(struct semaphore * sem);

該函數(shù)試著獲得信號(hào)量sem,如果能夠立刻獲得,它就獲得該信號(hào)量并返回0,否則,表示不能獲得信號(hào)量sem,返回值為非0值。因此,它不會(huì)導(dǎo)致調(diào)用者睡眠,可以在中斷上下文使用。

釋放內(nèi)核信號(hào)量所保護(hù)的資源


void up(struct semaphore * sem);

該函數(shù)釋放信號(hào)量sem,即把sem的值加1,如果sem的值為非正數(shù),表明有任務(wù)等待該信號(hào)量,因此喚醒這些等待者。

內(nèi)核信號(hào)量的使用例程


在驅(qū)動(dòng)程序中,當(dāng)多個(gè)線程同時(shí)訪問相同的資源時(shí)(驅(qū)動(dòng)中的全局變量時(shí)一種典型的
共享資源),可能會(huì)引發(fā)“競態(tài)“,因此我們必須對共享資源進(jìn)行并發(fā)控制。Linux內(nèi)核中
解決并發(fā)控制的最常用方法是自旋鎖與信號(hào)量(絕大多數(shù)時(shí)候作為互斥鎖使用)。

ssize_t globalvar_write(struct file *filp, const char *buf, size_t len, loff_t *off) {  //獲得信號(hào)量  if (down_interruptible(&sem))  {   return - ERESTARTSYS;  }  //將用戶空間的數(shù)據(jù)復(fù)制到內(nèi)核空間的global_var  if (copy_from_user(&global_var, buf, sizeof(int)))  {   up(&sem);   return - EFAULT;  }  //釋放信號(hào)量  up(&sem);  return sizeof(int); }

讀-寫信號(hào)量


跟自旋鎖一樣,信號(hào)量也有區(qū)分讀-寫信號(hào)量之分

如果一個(gè)讀寫信號(hào)量當(dāng)前沒有被寫者擁有并且也沒有寫者等待讀者釋放信號(hào)量,那么任何讀者都可以成功獲得該讀寫信號(hào)量;

否則,讀者必須被掛起直到寫者釋放該信號(hào)量。如果一個(gè)讀寫信號(hào)量當(dāng)前沒有被讀者或?qū)懻邠碛胁⑶乙矝]有寫者等待該信號(hào)量,那么一個(gè)寫者可以成功獲得該讀寫信號(hào)量,否則寫者將被掛起,直到?jīng)]有任何訪問者。因此,寫者是排他性的,獨(dú)占性的。

讀寫信號(hào)量有兩種實(shí)現(xiàn),一種是通用的,不依賴于硬件架構(gòu),因此,增加新的架構(gòu)不需要重新實(shí)現(xiàn)它,但缺點(diǎn)是性能低,獲得和釋放讀寫信號(hào)量的開銷大;另一種是架構(gòu)相關(guān)的,因此性能高,獲取和釋放讀寫信號(hào)量的開銷小,但增加新的架構(gòu)需要重新實(shí)現(xiàn)。在內(nèi)核配置時(shí),可以通過選項(xiàng)去控制使用哪一種實(shí)現(xiàn)。
讀寫信號(hào)量的相關(guān)API有:

DECLARE_RWSEM(name)

該宏聲明一個(gè)讀寫信號(hào)量name并對其進(jìn)行初始化。

void init_rwsem(struct rw_semaphore *sem);

該函數(shù)對讀寫信號(hào)量sem進(jìn)行初始化。

void down_read(struct rw_semaphore *sem);

讀者調(diào)用該函數(shù)來得到讀寫信號(hào)量sem。該函數(shù)會(huì)導(dǎo)致調(diào)用者睡眠,因此只能在進(jìn)程上下文使用。

int down_read_trylock(struct rw_semaphore *sem);

該函數(shù)類似于down_read,只是它不會(huì)導(dǎo)致調(diào)用者睡眠。它盡力得到讀寫信號(hào)量sem,如果能夠立即得到,它就得到該讀寫信號(hào)量,并且返回1,否則表示不能立刻得到該信號(hào)量,返回0。因此,它也可以在中斷上下文使用。

void down_write(struct rw_semaphore *sem);

寫者使用該函數(shù)來得到讀寫信號(hào)量sem,它也會(huì)導(dǎo)致調(diào)用者睡眠,因此只能在進(jìn)程上下文使用。

int down_write_trylock(struct rw_semaphore *sem);

該函數(shù)類似于down_write,只是它不會(huì)導(dǎo)致調(diào)用者睡眠。該函數(shù)盡力得到讀寫信號(hào)量,如果能夠立刻獲得,就獲得該讀寫信號(hào)量并且返回1,否則表示無法立刻獲得,返回0。它可以在中斷上下文使用。

void up_read(struct rw_semaphore *sem);

讀者使用該函數(shù)釋放讀寫信號(hào)量sem。它與down_read或down_read_trylock配對使用。如果down_read_trylock返回0,不需要調(diào)用up_read來釋放讀寫信號(hào)量,因?yàn)楦揪蜎]有獲得信號(hào)量。

void up_write(struct rw_semaphore *sem);

寫者調(diào)用該函數(shù)釋放信號(hào)量sem。它與down_write或down_write_trylock配對使用。如果down_write_trylock返回0,不需要調(diào)用up_write,因?yàn)榉祷?表示沒有獲得該讀寫信號(hào)量。

void downgrade_write(struct rw_semaphore *sem);

該函數(shù)用于把寫者降級(jí)為讀者,這有時(shí)是必要的。因?yàn)閷懻呤桥潘缘?,因此在寫者保持讀寫信號(hào)量期間,任何讀者或?qū)懻叨紝o法訪問該讀寫信號(hào)量保護(hù)的共享資源,對于那些當(dāng)前條件下不需要寫訪問的寫者,降級(jí)為讀者將,使得等待訪問的讀者能夠立刻訪問,從而增加了并發(fā)性,提高了效率。
讀寫信號(hào)量適于在讀多寫少的情況下使用,在linux內(nèi)核中對進(jìn)程的內(nèi)存映像描述結(jié)構(gòu)的訪問就使用了讀寫信號(hào)量進(jìn)行保護(hù)。
究竟什么時(shí)候使用自旋鎖什么時(shí)候使用信號(hào)量,下面給出建議的方案
當(dāng)對低開銷、短期、中斷上下文加鎖,優(yōu)先考慮自旋鎖;當(dāng)對長期、持有鎖需要休眠的任務(wù),優(yōu)先考慮信號(hào)量。

POSIX信號(hào)量詳解


無名信號(hào)量


無名信號(hào)量的創(chuàng)建就像聲明一般的變量一樣簡單,例如:sem_t sem_id。然后再初始化該無名信號(hào)量,之后就可以放心使用了。

無名信號(hào)量常用于多線程間的同步,同時(shí)也用于相關(guān)進(jìn)程間的同步。也就是說,無名信號(hào)量必須是多個(gè)進(jìn)程(線程)的共享變量,無名信號(hào)量要保護(hù)的變量也必須是多個(gè)進(jìn)程(線程)的共享變量,這兩個(gè)條件是缺一不可的。

常見的無名信號(hào)量相關(guān)函數(shù)


int sem_init(sem_t *sem, int pshared, unsigned int value);
  • pshared==0 用于同一多線程的同步;
  • 若pshared>0 用于多個(gè)相關(guān)進(jìn)程間的同步(即由fork產(chǎn)生的)
int sem_getvalue(sem_t *sem, int *sval);

取回信號(hào)量sem的當(dāng)前值,把該值保存到sval中。
若有1個(gè)或更多的線程或進(jìn)程調(diào)用sem_wait阻塞在該信號(hào)量上,該函數(shù)返回兩種值:

  • 返回0
  • 返回阻塞在該信號(hào)量上的進(jìn)程或線程數(shù)目

linux采用返回的第一種策略。

sem_wait(或sem_trywait)相當(dāng)于P操作,即申請資源。

int sem_wait(sem_t *sem); // 這是一個(gè)阻塞的函數(shù)

測試所指定信號(hào)量的值,它的操作是原子的。

  • 若sem>0,那么它減1并立即返回。
  • 若sem==0,則睡眠直到sem>0,此時(shí)立即減1,然后返回。
int sem_trywait(sem_t *sem); // 非阻塞的函數(shù)

其他的行為和sem_wait一樣,除了:
若sem==0,不是睡眠,而是返回一個(gè)錯(cuò)誤EAGAIN。

sem_post相當(dāng)于V操作,釋放資源。

int sem_post(sem_t *sem);

把指定的信號(hào)量sem的值加1;

呼醒正在等待該信號(hào)量的任意線程。
注意:在這些函數(shù)中,只有sem_post是信號(hào)安全的函數(shù),它是可重入函數(shù)

無名信號(hào)量在多線程間的同步


無名信號(hào)量的常見用法是將要保護(hù)的變量放在sem_wait和sem_post中間所形成的
臨界區(qū)內(nèi),這樣該變量就會(huì)被保護(hù)起來,例如:

#include <; #include <; #include <sy; #include <; #include <uni; int number; // 被保護(hù)的全局變量 sem_t sem_id; void* thread_one_fun(void *arg) { sem_wait(&sem_id); printf("thread_one have the semaphore\n"); number++; printf("thread_one : number = %d\n", number); sem_post(&sem_id); return NULL; } void* thread_two_fun(void *arg) { sem_wait(&sem_id); printf("thread_two have the semaphore \n"); number--; printf("thread_two : number = %d\n", number); sem_post(&sem_id); return NULL; } int main(int argc, char *argv[]) { number = 1; pthread_t id1, id2; sem_init(&sem_id, 0, 1); pthread_create(&id1, NULL, thread_one_fun, NULL); pthread_create(&id2, NULL, thread_two_fun, NULL); pthread_join(id1, NULL); pthread_join(id2, NULL); printf("main...\n"); return 0; }

上面的例程,到底哪個(gè)線程先申請到信號(hào)量資源,這是隨機(jī)的。

進(jìn)程1先執(zhí)行,進(jìn)城2后執(zhí)行

進(jìn)程2先執(zhí)行,進(jìn)城1后執(zhí)行

如果想要某個(gè)特定的順序的話,可以用2個(gè)信號(hào)量來實(shí)現(xiàn)。例如下面的例程是線程1先執(zhí)行完,然后線程2才繼續(xù)執(zhí)行,直至結(jié)束。

#include <; #include <; #include <; #include <; #include <sy; #include <; #include <uni; int number; // 被保護(hù)的全局變量 sem_t sem_id1, sem_id2; /* * 線程1, * 對sem_id1加鎖(P操作)以后 * 將number增加1 * 同時(shí)對sem_id2進(jìn)行釋放,V操作 * * */ void* thread_one_fun(void *arg) { sem_wait(&sem_id1); printf("thread_one have the semaphore\n"); number++; printf("number = %d\n",number); sem_post(&sem_id2); return NULL; } /* * 線程2, * 對sem_id2加鎖(P操作)以后 * 將number減少1 * 同時(shí)對sem_id1進(jìn)行釋放,V操作 * * */ void* thread_two_fun(void *arg) { sem_wait(&sem_id2); printf("thread_two have the semaphore \n"); number--; printf("number = %d\n",number); sem_post(&sem_id1); return NULL; } int main(int argc,char *argv[]) { number = 1; pthread_t id1, id2; /* * 由于程序初始時(shí), sem_id1可進(jìn)入, sem_id2不可進(jìn)入 * 兩個(gè)線程的動(dòng)作如下: * thread one P(id1) number++ V(id2) * thread two P(id2) number-- V(id1) * 而id1可進(jìn)入, id2不可進(jìn)入 * 因此thread one先執(zhí)行 * 如果將id1與id2的順序交換, 則執(zhí)行順序相反 * */ sem_init(&sem_id1, 0, 1); // 空閑的 sem_init(&sem_id2, 0, 0); // 忙的 pthread_create(&id1, NULL, thread_one_fun, NULL); pthread_create(&id2, NULL, thread_two_fun, NULL); pthread_join(id1, NULL); pthread_join(id2, NULL); printf("main...\n"); return EXIT_SUCCESS; }

無名信號(hào)量在相關(guān)進(jìn)程間的同步


說是相關(guān)進(jìn)程,是因?yàn)楸境绦蛑泄灿?個(gè)進(jìn)程,其中一個(gè)是另外一個(gè)的子進(jìn)程(由fork產(chǎn)生)的。

本來對于fork來說,子進(jìn)程只繼承了父進(jìn)程的代碼副本,mutex理應(yīng)在父子進(jìn)程中是相互獨(dú)立的兩個(gè)變量,但由于在初始化mutex的時(shí)候,由pshared = 1指定了mutex處于共享內(nèi)存區(qū)域,所以此時(shí)mutex變成了父子進(jìn)程共享的一個(gè)變量。此時(shí),mutex就可以用來同步相關(guān)進(jìn)程了。

#include <; #include <; #include <; #include <errno.h> #include <uni; #include <sy; #include <sy; #include <; #include <sy; int main(int argc, char **argv) { int fd, i; int nloop = 10, zero = 0; int *ptr; sem_t mutex; // open a file and map it into memory fd = open("log.txt", O_RDWR | O_CREAT, S_IRWXU); write(fd,&zero,sizeof(int)); ptr = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); close(fd); /* create, initialize semaphore */ if(sem_init(&mutex, 1, 1) < 0) // { perror("semaphore initilization"); exit(0); } if (fork() == 0) { /* child process*/ for (i = 0; i < nloop; i++) { sem_wait(&mutex); printf("child: %d\n", (*ptr)++); //sleep(1); sem_post(&mutex); } exit(0); } /* back to parent process */ for (i = 0; i < nloop; i++) { sem_wait(&mutex); printf("parent: %d\n", (*ptr)++); //sleep(1); sem_post(&mutex); } exit(0); }

有名信號(hào)量


有名信號(hào)量的特點(diǎn)是把信號(hào)量的值保存在文件中。

這決定了它的用途非常廣:既可以用于線程,也可以用于相關(guān)進(jìn)程間,甚至是不相關(guān)進(jìn)程。

有名信號(hào)量能在進(jìn)程間共享的原因


由于有名信號(hào)量的值是保存在文件中的,所以對于相關(guān)進(jìn)程來說,子進(jìn)程是繼承了父進(jìn)程的文件描述符,那么子進(jìn)程所繼承的文件描述符所指向的文件是和父進(jìn)程一樣的,當(dāng)然文件里面保存的有名信號(hào)量值就共享了。

有名信號(hào)量相關(guān)函數(shù)說明


有名信號(hào)量在使用的時(shí)候,和無名信號(hào)量共享sem_wait和sem_post函數(shù)。
區(qū)別是有名信號(hào)量使用sem_open代替sem_init,另外在結(jié)束的時(shí)候要像關(guān)閉文件一樣去關(guān)閉這個(gè)有名信號(hào)量。

  • 打開一個(gè)已存在的有名信號(hào)量,或創(chuàng)建并初始化一個(gè)有名信號(hào)量。一個(gè)單一的調(diào)用就完
    成了信號(hào)量的創(chuàng)建、初始化和權(quán)限的設(shè)置。
sem_t *sem_open(const char *name, int oflag, mode_t mode , int value);

參數(shù)描述name文件的路徑名;Oflag有O_CREAT或O_CREATmode_t控制新的信號(hào)量的訪問權(quán)限;Value指定信號(hào)量的初始化值。

注意:

這里的name不能寫成/tm這樣的格式,因?yàn)樵趌inux下,sem都是創(chuàng)建在/dev/shm目錄下。你可以將name寫成“/mysem”或“mysem”,創(chuàng)建出來的文件都是“/dev/shm”,千萬不要寫路徑。也千萬不要寫“/tmp/mysem”之類的。

當(dāng)oflag = O_CREAT時(shí),若name指定的信號(hào)量不存在時(shí),則會(huì)創(chuàng)建一個(gè),而且后面的mode和value參數(shù)必須有效。若name指定的信號(hào)量已存在,則直接打開該信號(hào)量,

同時(shí)忽略mode和value參數(shù)。

當(dāng)oflag = O_CREAT|O_EXCL時(shí),若name指定的信號(hào)量已存在,該函數(shù)會(huì)直接返回error。

  • 一旦你使用了一信號(hào)量,銷毀它們就變得很重要。
    在做這個(gè)之前,要確定所有對這個(gè)有名信號(hào)量的引用都已經(jīng)通過sem_close()函數(shù)關(guān)閉了,然后只需在退出或是退出處理函數(shù)中調(diào)用sem_unlink()去刪除系統(tǒng)中的信號(hào)量,
    注意如果有任何的處理器或是線程引用這個(gè)信號(hào)量,sem_unlink()函數(shù)不會(huì)起到任何的作用。

也就是說,必須是最后一個(gè)使用該信號(hào)量的進(jìn)程來執(zhí)行sem_unlick才有效。因?yàn)槊總€(gè)信號(hào)燈有一個(gè)引用計(jì)數(shù)器記錄當(dāng)前的打開次數(shù),sem_unlink必須等待這個(gè)數(shù)為0時(shí)才能把name所指的信號(hào)燈從文件系統(tǒng)中刪除。也就是要等待最后一個(gè)sem_close發(fā)生。

有名信號(hào)量在無相關(guān)進(jìn)程間的同步


前面已經(jīng)說過,有名信號(hào)量是位于共享內(nèi)存區(qū)的,那么它要保護(hù)的資源也必須是位于共享內(nèi)存區(qū),只有這樣才能被無相關(guān)的進(jìn)程所共享。
在下面這個(gè)例子中,服務(wù)進(jìn)程和客戶進(jìn)程都使用shmget和shmat來獲取得一塊共享內(nèi)存資源。然后利用有名信號(hào)量來對這塊共享內(nèi)存資源進(jìn)行互斥保護(hù)。

服務(wù)器程序

#include <; #include <; #include <uni; #include <sy; #include <sy; #include <sy; #include <; #include <sy; #include <sy; #include <; #define SHMSZ 27 char SEM_NAME[]= "vik"; int main() { char ch; int shmid; key_t key; char *shm,*s; sem_t *mutex; //name the shared memory segment key = 1000; //create & initialize semaphore mutex = sem_open(SEM_NAME, O_CREAT, 0644, 1); if(mutex == SEM_FAILED) { perror("unable to create semaphore"); sem_unlink(SEM_NAME); exit(-1); } //create the shared memory segment with this key shmid = shmget(key, SHMSZ, IPC_CREAT | 0666); if(shmid < 0) { perror("failure in shmget"); exit(-1); } //attach this segment to virtual memory shm = shmat(shmid, NULL, 0); //start writing into memory s = shm; for(ch = 'A'; ch <= 'Z'; ch++) { sem_wait(mutex); *s++ = ch; sem_post(mutex); } //the below loop could be replaced by binary semaphore while(*shm != '*') { sleep(1); } sem_close(mutex); sem_unlink(SEM_NAME); shmctl(shmid, IPC_RMID, 0); return EXIT_SUCCESS; }

客戶端程序

// client.c #include <; #include <; #include <sy; #include <sy; #include <sy; #include <; #include <; #include <sy; #include <sy; #include <; #define SHMSZ 27 char SEM_NAME[]= "vik"; int main() { int shmid; key_t key; char *shm, *s; sem_t *mutex; // name the shared memory segment key = 1000; // create & initialize existing semaphore mutex = sem_open(SEM_NAME, 0, 0644, 0); if(mutex == SEM_FAILED) { perror("reader:unable to execute semaphore"); sem_close(mutex); exit(-1); } // create the shared memory segment with this key shmid = shmget(key, SHMSZ, 0666); if(shmid < 0) { perror("reader:failure in shmget"); exit(-1); } // attach this segment to virtual memory shm = shmat(shmid, NULL, 0); // start reading s = shm; for(s = shm; *s != '\0'; s++) { sem_wait(mutex); putchar(*s); sem_post(mutex); } // once done signal exiting of reader:This can be replaced by another semaphore *shm = '*'; sem_close(mutex); shmctl(shmid, IPC_RMID, 0); return EXIT_SUCCESS; }

SYSTEM V信號(hào)量


這是信號(hào)量值的集合,而不是單個(gè)信號(hào)量。相關(guān)的信號(hào)量操作函數(shù)由<sy;引用。

ystem V 信號(hào)量在內(nèi)核中維護(hù),其中包括二值信號(hào)量 、計(jì)數(shù)信號(hào)量、計(jì)數(shù)信號(hào)量集。

  • 二值信號(hào)量 : 其值只有0、1 兩種選擇,0表示資源被鎖,1表示資源可用;
  • 計(jì)數(shù)信號(hào)量:其值在0 和某個(gè)限定值之間,不限定資源數(shù)只在0 1 之間;
  • 計(jì)數(shù)信號(hào)量集 :多個(gè)信號(hào)量的集合組成信號(hào)量集

信號(hào)量結(jié)構(gòu)體


內(nèi)核為每個(gè)信號(hào)量集維護(hù)一個(gè)信號(hào)量結(jié)構(gòu)體,可在

struct semid_ds { struct ipc_perm sem_perm; /* 信號(hào)量集的操作許可權(quán)限 */ struct sem *sem_base; /* 某個(gè)信號(hào)量sem結(jié)構(gòu)數(shù)組的指針,當(dāng)前信號(hào)量集中的每個(gè)信號(hào)量對應(yīng)其中一個(gè)數(shù)組元素 */ ushort sem_nsems; /* sem_base 數(shù)組的個(gè)數(shù) */ time_t sem_otime; /* 最后一次成功修改信號(hào)量數(shù)組的時(shí)間 */ time_t sem_ctime; /* 成功創(chuàng)建時(shí)間 */ };

其中ipc_perm 結(jié)構(gòu)是內(nèi)核給每個(gè)進(jìn)程間通信對象維護(hù)的一個(gè)信息結(jié)構(gòu),其成員包含所有者用戶id,所有者組id、創(chuàng)建者及其組id,以及訪問模式等;semid_ds結(jié)構(gòu)體中的sem結(jié)構(gòu)是內(nèi)核用于維護(hù)某個(gè)給定信號(hào)量的一組值的內(nèi)部結(jié)構(gòu),其結(jié)構(gòu)定義:

struct sem { ushort semval; /* 信號(hào)量的當(dāng)前值 */ short sempid; /* 最后一次返回該信號(hào)量的進(jìn)程ID 號(hào) */ ushort semncnt; /* 等待semval大于當(dāng)前值的進(jìn)程個(gè)數(shù) */ ushort semzcnt; /* 等待semval變成0的進(jìn)程個(gè)數(shù) */ };

常見的SYSTEM V信號(hào)量函數(shù)


關(guān)鍵字和描述符


SYSTEM V信號(hào)量是SYSTEM V IPC(即SYSTEM V進(jìn)程間通信)的組成部分,其他的有SYSTEM V消息隊(duì)列,SYSTEM V共享內(nèi)存。而關(guān)鍵字和IPC描述符無疑是它們的共同點(diǎn),也使用它們,就不得不先對它們進(jìn)行熟悉。這里只對SYSTEM V信號(hào)量進(jìn)行討論。

IPC描述符相當(dāng)于引用ID號(hào),要想使用SYSTEM V信號(hào)量(或MSG、SHM),就必須用IPC描述符來調(diào)用信號(hào)量。而IPC描述符是內(nèi)核動(dòng)態(tài)提供的(通過semget來獲?。?,用戶無法讓服務(wù)器和客戶事先認(rèn)可共同使用哪個(gè)描述符,所以有時(shí)候就需要到關(guān)鍵字KEY來定位描述符。

某個(gè)KEY只會(huì)固定對應(yīng)一個(gè)描述符(這項(xiàng)轉(zhuǎn)換工作由內(nèi)核完成),這樣假如服務(wù)器和

客戶事先認(rèn)可共同使用某個(gè)KEY,那么大家就都能定位到同一個(gè)描述符,也就能定位到同一個(gè)信號(hào)量,這樣就達(dá)到了SYSTEM V信號(hào)量在進(jìn)程間共享的目的。

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


創(chuàng)建一個(gè)信號(hào)量或訪問一個(gè)已經(jīng)存在的信號(hào)量集。

int semget(key_t key, int nsems, int oflag)

該函數(shù)執(zhí)行成功返回信號(hào)量標(biāo)示符,失敗返回-1

參數(shù)描述key通過調(diào)用ftok函數(shù)得到的鍵值nsems代表創(chuàng)建信號(hào)量的個(gè)數(shù),如果只是訪問而不創(chuàng)建則可以指定該參數(shù)為0,我們一旦創(chuàng)建了該信號(hào)量,就不能更改其信號(hào)量個(gè)數(shù),只要你不刪除該信號(hào)量,你就是重新調(diào)用該函數(shù)創(chuàng)建該鍵值的信號(hào)量,該函數(shù)只是返回以前創(chuàng)建的值,不會(huì)重新創(chuàng)建;semflg指定該信號(hào)量的讀寫權(quán)限,當(dāng)創(chuàng)建信號(hào)量時(shí)不許加IPC_CREAT ,若指定IPC_CREAT

semget函數(shù)執(zhí)行成功后,就產(chǎn)生了一個(gè)由內(nèi)核維持的類型為semid_ds結(jié)構(gòu)體的信號(hào)量集,返回semid就是指向該信號(hào)量集的引索。

  • nsems>0 : 創(chuàng)建一個(gè)信的信號(hào)量集,指定集合中信號(hào)量的數(shù)量,一旦創(chuàng)建就不能更改。
  • nsems==0 : 訪問一個(gè)已存在的集合
  • 返回的是一個(gè)稱為信號(hào)量標(biāo)識(shí)符的整數(shù),semop和semctl函數(shù)將使用它。
  • 創(chuàng)建成功后信號(hào)量結(jié)構(gòu)被設(shè)置:
.sem_perm 的uid和gid成員被設(shè)置成的調(diào)用進(jìn)程的有效用戶ID和有效組ID .oflag 參數(shù)中的讀寫權(quán)限位存入 .sem_otime 被置為0,sem_ctime被設(shè)置為當(dāng)前時(shí)間 .sem_nsems 被置為nsems參數(shù)的值1234

該集合中的每個(gè)信號(hào)量不初始化,這些結(jié)構(gòu)是在semctl,用參數(shù)SET_VAL,SETALL初始化的。

#include <; #include <; #include <uni; #include <sy; #include <sy; #define SEM_R 0400 //用戶(屬主)讀 #define SEM_A 0200 //用戶(屬主)寫 #define SVSEM_MODE (SEM_R | SEM_A | SEM_R>>3 | SEM_R>>6) int main(int argc,char *argv[]) { int c, oflag, semid, nsems; oflag = SVSEM_MODE | IPC_CREAT; //設(shè)置創(chuàng)建模式 //根據(jù)命令行參數(shù)e判斷是否制定了IPC_EXCL模式 while((c = getopt(argc,argv,"e")) != -1) { switch(c) { case 'e': oflag |= IPC_EXCL; break; } } //判斷命令行參數(shù)是否合法 if (optind != argc -2) { printf("usage: semcreate [-e] <pathname> <nsems>"); exit(-1); } //獲取信號(hào)量集合中的信號(hào)量個(gè)數(shù) nsems = atoi(argv[optind+1]); //創(chuàng)建信號(hào)量,通過ftok函數(shù)創(chuàng)建一個(gè)key,返回信號(hào)量 標(biāo)識(shí)符 semid = semget(ftok(argv[optind],0),nsems,oflag); return EXIT_SUCCESS; }

關(guān)鍵字的獲取


有多種方法使客戶機(jī)和服務(wù)器在同一IPC結(jié)構(gòu)上會(huì)合:
* 服務(wù)器可以指定關(guān)鍵字IPC_PRIVATE創(chuàng)建一個(gè)新IPC結(jié)構(gòu),將返回的標(biāo)識(shí)符存放在某處(例如一個(gè)文件)以便客戶機(jī)取用。關(guān)鍵字 IPC_PRIVATE保證服務(wù)器創(chuàng)建一個(gè)新IPC結(jié)構(gòu)。這種技術(shù)的缺點(diǎn)是:服務(wù)器要將整型標(biāo)識(shí)符寫到文件中,然后客戶機(jī)在此后又要讀文件取得此標(biāo)識(shí)符。

IPC_PRIVATE關(guān)鍵字也可用于父、子關(guān)系進(jìn)程。父進(jìn)程指定 IPC_PRIVATE創(chuàng)建一個(gè)新IPC結(jié)構(gòu),所返回的標(biāo)識(shí)符在fork后可由子進(jìn)程使用。子進(jìn)程可將此標(biāo)識(shí)符作為exec函數(shù)的一個(gè)參數(shù)傳給一個(gè)新程序。

  • 在一個(gè)公用頭文件中定義一個(gè)客戶機(jī)和服務(wù)器都認(rèn)可的關(guān)鍵字。然后服務(wù)器指定此關(guān)鍵字創(chuàng)建一個(gè)新的IPC結(jié)構(gòu)。這種方法的問題是該關(guān)鍵字可能已與一個(gè) IPC結(jié)構(gòu)相結(jié)合,在此情況下,get函數(shù)(msgget、semget或shmget)出錯(cuò)返回。服務(wù)器必須處理這一錯(cuò)誤,刪除已存在的IPC結(jié)構(gòu),然后試著再創(chuàng)建它。當(dāng)然,這個(gè)關(guān)鍵字不能被別的程序所占用。
  • 客戶機(jī)和服務(wù)器認(rèn)同一個(gè)路徑名和課題I D(課題I D是0 ~ 2 5 5之間的字符值) ,然后調(diào)用函數(shù)ftok將這兩個(gè)值變換為一個(gè)關(guān)鍵字。這樣就避免了使用一個(gè)已被占用的關(guān)鍵字的問題。
    使用ftok并非高枕無憂。有這樣一種例外:服務(wù)器使用ftok獲取得一個(gè)關(guān)鍵字后,該文件就被刪除了,然后重建。此時(shí)客戶端以此重建后的文件來ftok所獲取的關(guān)鍵字就和服務(wù)器的關(guān)鍵字不一樣了。所以一般商用的軟件都不怎么用ftok。
    一般來說,客戶機(jī)和服務(wù)器至少共享一個(gè)頭文件,所以一個(gè)比較簡單的方法是避免使用ftok,而只是在該頭文件中存放一個(gè)大家都知道的關(guān)鍵字。

設(shè)置信號(hào)量的值(PV操作)


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

參數(shù)描述semid是semget返回的semid信號(hào)量標(biāo)示符opsptr指向信號(hào)量操作結(jié)構(gòu)數(shù)組nopsopsptr所指向的數(shù)組中的sembuf結(jié)構(gòu)體的個(gè)數(shù)

該函數(shù)執(zhí)行成功返回0,失敗返回-1;

第二個(gè)參數(shù)sops為一個(gè)結(jié)構(gòu)體數(shù)組指針,結(jié)構(gòu)體定義在sy中,結(jié)構(gòu)體如下

struct sembuf { unsigned short sem_num; /* semaphore index in array */ short sem_op; /* semaphore operation */ short sem_flg; /* operation flags */ };

sem_num 操作信號(hào)的下標(biāo),其值可以為0 到nops
sem_flg為該信號(hào)操作的標(biāo)志:其值可以為0、IPC_NOWAIT 、 SEM_UNDO

sem_flg標(biāo)識(shí)描述0在對信號(hào)量的操作不能執(zhí)行的情況下,該操作阻塞到可以執(zhí)行為止;IPC_NOWAIT在對信號(hào)量的操作不能執(zhí)行的情況下,該操作立即返回;SEM_UNDO當(dāng)操作的進(jìn)程推出后,該進(jìn)程對sem進(jìn)行的操作將被取消;

sem_op取值描述>0>0則信號(hào)量加上它的值,等價(jià)于進(jìn)程釋放信號(hào)量控制的資源=0=0若沒有設(shè)置IPC_NOWAIT, 那么調(diào)用進(jìn)程將進(jìn)入睡眠狀態(tài),直到信號(hào)量的值為0,否則進(jìn)程直接返回<script type="math/tex" id="MathJax-Element-19">< 0</script>則信號(hào)量加上它的值,等價(jià)于進(jìn)程申請信號(hào)量控制的資源,若進(jìn)程設(shè)置IPC_NOWAIT則進(jìn)程再?zèng)]有可用資源情況下,進(jìn)程阻塞,否則直接返回。

例如,當(dāng)前semval為2,而sem_op = -3,那么怎么辦?

注意:semval是指semid_ds中的信號(hào)量集中的某個(gè)信號(hào)量的值

#include <; #include <; #include <uni; #include <sy; #include <sy; int main(int argc,char *argv[]) { int c,i,flag,semid,nops; struct sembuf *ptr; flag = 0; //根據(jù)命令行參數(shù)設(shè)置操作模式 while( ( c = getopt(argc,argv,"nu")) != -1) { switch(c) { case 'n': flag |= IPC_NOWAIT; //非阻塞 break; case 'u': flag |= SEM_UNDO; //不可恢復(fù) break; } } if(argc - optind < 2) { printf("usage: semops [-n] [-u] <pathname> operation..."); exit(0); } //打開一個(gè)已經(jīng)存在的信號(hào)量集合 if((semid = semget(ftok(argv[optind],0),0,0)) == -1) { perror("semget() error"); exit(-1); } optind++; //指向當(dāng)前第一個(gè)信號(hào)量的位置 nops = argc - optind; //信號(hào)量個(gè)數(shù) ptr = calloc(nops,sizeof(struct sembuf)); for(i=0;i<nops;++i) { ptr[i].sem_num = i; //信號(hào)量變換 ptr[i].sem_op = atoi(argv[optind+i]); //設(shè)置信號(hào)量的值 ptr[i].sem_flg = flag; //設(shè)置操作模式 } //對信號(hào)量執(zhí)行操作 if(semop(semid,ptr,nops) == -1) { perror("semop() error"); exit(-1); } return EXIT_SUCCESS; }

對信號(hào)集實(shí)行控制操作(semval的賦值等)


int semctl(int semid, int semum, int cmd, ../* union semun arg */);

參數(shù)描述semid是信號(hào)量集合;semnum是信號(hào)在集合中的序號(hào);semum是一個(gè)必須由用戶自定義的結(jié)構(gòu)體,在這里我們務(wù)必弄清楚該結(jié)構(gòu)體的組成:

union semun { int val; // cmd == SETVAL struct semid_ds *buf // cmd == IPC_SET或者 cmd == IPC_STAT ushort *array; // cmd == SETALL,或 cmd = GETALL };

值描述IPC_STAT讀取一個(gè)信號(hào)量集的數(shù)據(jù)結(jié)構(gòu)semid_ds,并將其存儲(chǔ)在semun中的buf參數(shù)中。IPC_SET設(shè)置信號(hào)量集的數(shù)據(jù)結(jié)構(gòu)semid_ds中的元素ipc_perm,其值取自semun中的buf參數(shù)。IPC_RMID將信號(hào)量集從系統(tǒng)中刪除GETALL用于讀取信號(hào)量集中的所有信號(hào)量的值,存于semnu的array中SETALL設(shè)置所指定的信號(hào)量集的每個(gè)成員semval的值GETPID返回最后一個(gè)執(zhí)行semop操作的進(jìn)程的PID。LSETVAL把的val數(shù)據(jù)成員設(shè)置為當(dāng)前資源數(shù)GETVAL把semval中的當(dāng)前值作為函數(shù)的返回,即現(xiàn)有的資源數(shù),返回值為非負(fù)數(shù)。

val只有cmd ==SETVAL時(shí)才有用,此時(shí)指定的semval = arg.val。

注意:當(dāng)cmd == GETVAL時(shí),semctl函數(shù)返回的值就是我們想要的semval。千萬不要以為指定的semval被返回到arg.val中。

array指向一個(gè)數(shù)組,

當(dāng)cmd==SETALL時(shí),就根據(jù)arg.array來將信號(hào)量集的所有值都賦值;

當(dāng)cmd ==GETALL時(shí),就將信號(hào)量集的所有值返回到arg.array指定的數(shù)組中。

buf 指針只在cmd==IPC_STAT 或IPC_SET 時(shí)有用,作用是semid 所指向的信號(hào)量集

(semid_ds機(jī)構(gòu)體)。一般情況下不常用,這里不做談?wù)摗?/p>

另外,cmd == IPC_RMID還是比較有用的。

示例程序


調(diào)用semctl函數(shù)設(shè)置信號(hào)量的值程序


#include <; #include <; #include <uni; #include <sy; #include <sy; //定義信號(hào)量操作共用體結(jié)構(gòu) union semun { int val; struct semid_ds *buf; unsigned short *array; }; int main(int argc,char *argv[]) { int semid,nsems,i; struct semid_ds seminfo; unsigned short *ptr; union semun arg; if(argc < 2) { printf("usage: semsetvalues <pathname>[values ...]"); exit(0); } //打開已經(jīng)存在的信號(hào)量集合 semid = semget(ftok(argv[1],0),0,0); arg.buf = &seminfo; //獲取信號(hào)量集的相關(guān)信息 semctl(semid,0,IPC_STAT,arg); nsems = arg.buf->sem_nsems; //信號(hào)量的個(gè)數(shù) if(argc != nsems + 2 ) { printf("%s semaphores in set,%d values specified",nsems,argc-2); exit(0); } //分配信號(hào)量 ptr = calloc(nsems,sizeof(unsigned short)); arg.array = ptr; //初始化信號(hào)量的值 for(i=0;i<nsems;i++) { ptr[i] = atoi(argv[i+2]); } //通過arg設(shè)置信號(hào)量集合 semctl(semid,0,SETALL,arg); return EXIT_SUCCESS; }

調(diào)用semctl獲取信號(hào)量的值


#include <; #include <; #include <uni; #include <sy; #include <sy; union semun { int val; struct semid_ds *buf; unsigned short *array; }; int main(int argc,char *argv[]) { int semid,nsems,i; struct semid_ds seminfo; unsigned short *ptr; union semun arg; if(argc != 2) { printf("usage: semgetvalues<pathname>"); exit(0); } //打開已經(jīng)存在的信號(hào)量 semid = semget(ftok(argv[1], 0), 0, 0); arg.buf = &seminfo; //獲取信號(hào)量集的屬性,返回semid_ds結(jié)構(gòu) semctl(semid, 0, IPC_STAT, arg); nsems = arg.buf->sem_nsems; //信號(hào)量的數(shù)目 ptr = calloc(nsems,sizeof(unsigned short)); arg.array = ptr; //獲取信號(hào)量的值 semctl(semid, 0, GETALL, arg); for(i = 0; i < nsems; i++) { printf("semval[%d] = %d\n", i, ptr[i]); } return EXIT_SUCCESS; }

通過semctl實(shí)現(xiàn)PV操作的函數(shù)庫

#include <; #include <sy; #include <; #include <; 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; = 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; } // 對sem_id執(zhí)行p操作 int sem_p(int sem_id) { struct sembuf sem_buf; = 0;//信號(hào)量編號(hào) = -1;//P操作 = 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; } // 對sem_id執(zhí)行V操作 int sem_v(int sem_id) { struct sembuf sem_buf; = 0; = 1;//V操作 = SEM_UNDO; if (semop(sem_id, &sem_buf, 1) == -1) { perror("Sem V operation"); exit(1); } return 0; }#include <sy; #include <sy; #include <sy; #include <; static int nsems; static int semflg; static int semid; int errno=0; union semun { int val; struct semid_ds *buf; unsigned short *array; }arg; int main() { struct sembuf sops[2]; //要用到兩個(gè)信號(hào)量,所以要定義兩個(gè)操作數(shù)組 int rslt; unsigned short argarray[80]; arg.array = argarray; semid = semget(IPC_PRIVATE, 2, 0666); if(semid < 0 ) { printf("semget failed. errno: %d\n", errno); exit(0); } //獲取0th信號(hào)量的原始值 rslt = semctl(semid, 0, GETVAL); printf("val = %d\n",rslt); //初始化0th信號(hào)量,然后再讀取,檢查初始化有沒有成功 arg.val = 1; // 同一時(shí)間只允許一個(gè)占有者 semctl(semid, 0, SETVAL, arg); rslt = semctl(semid, 0, GETVAL); printf("val = %d\n",rslt); sops[0].sem_num = 0; sops[0].sem_op = -1; sops[0].sem_flg = 0; sops[1].sem_num = 1; sops[1].sem_op = 1; sops[1].sem_flg = 0; rslt=semop(semid, sops, 1); //申請0th信號(hào)量,嘗試鎖定 if (rslt < 0 ) { printf("semop failed. errno: %d\n", errno); exit(0); } //可以在這里對資源進(jìn)行鎖定 sops[0].sem_op = 1; semop(semid, sops, 1); //釋放0th信號(hào)量 rslt = semctl(semid, 0, GETVAL); printf("val = %d\n",rslt); rslt=semctl(semid, 0, GETALL, arg); if (rslt < 0) { printf("semctl failed. errno: %d\n", errno); exit(0); } printf("val1:%d val2: %d\n",(unsigned int)argarray[0],(unsigned int)argarray[1]); if(semctl(semid, 1, IPC_RMID) == -1) { perror(“semctl failure while clearing reason”); } return(0); }

信號(hào)量的牛刀小試——生產(chǎn)者與消費(fèi)者問題


1.問題描述:
有一個(gè)長度為N的緩沖池為生產(chǎn)者和消費(fèi)者所共有,只要緩沖池未滿,生產(chǎn)者便可將
消息送入緩沖池;只要緩沖池未空,消費(fèi)者便可從緩沖池中取走一個(gè)消息。生產(chǎn)者往緩沖池
放信息的時(shí)候,消費(fèi)者不可操作緩沖池,反之亦然。

2.使用多線程和信號(hào)量解決該經(jīng)典問題的互斥

#include <; #include <; #include <; #include <; #include <; #define BUFF_SIZE 10 char buffer[BUFF_SIZE]; char count; // 緩沖池里的信息數(shù)目 sem_t sem_mutex; // 生產(chǎn)者和消費(fèi)者的互斥鎖 sem_t p_sem_mutex; // 空的時(shí)候,對消費(fèi)者不可進(jìn) sem_t c_sem_mutex; // 滿的時(shí)候,對生產(chǎn)者不可進(jìn) void * Producer() { while(1) { sem_wait(&p_sem_mutex); // 當(dāng)緩沖池未滿時(shí) sem_wait(&sem_mutex); // 等待緩沖池空閑 count++; sem_post(&sem_mutex); if(count < BUFF_SIZE) // 緩沖池未滿 { sem_post(&p_sem_mutex); } if(count > 0) // 緩沖池不為空 { sem_post(&c_sem_mutex); } } } void * Consumer() { while(1) { sem_wait(&c_sem_mutex); // 緩沖池未空時(shí) sem_wait(&sem_mutex); // 等待緩沖池空閑 count--; sem_post(&sem_mutex); if(count > 0) { sem_post(&c_sem_mutex); } } return NULL; } int main() { pthread_t ptid,ctid; // initialize the semaphores //sem_init(&empty_sem_mutex, 0, 1); //sem_init(&full_sem_mutex, 0, `0); //creating producer and consumer threads if(pthread_create(&ptid, NULL,Producer, NULL)) { printf("\n ERROR creating thread 1"); exit(1); } if(pthread_create(&ctid, NULL,Consumer, NULL)) { printf("\n ERROR creating thread 2"); exit(1); } if(pthread_join(ptid, NULL)) /* wait for the producer to finish */ { printf("\n ERROR joining thread"); exit(1); } if(pthread_join(ctid, NULL)) /* wait for consumer to finish */ { printf("\n ERROR joining thread"); exit(1); } //sem_destroy(&empty_sem_mutex); //sem_destroy(&full_sem_mutex); //exit the main thread pthread_exit(NULL); return EXIT_SUCCESS; }

1.《【重新隨機(jī)進(jìn)程】進(jìn)程間通信的信號(hào)量semaphore - linux內(nèi)核分析》援引自互聯(lián)網(wǎng),旨在傳遞更多網(wǎng)絡(luò)信息知識(shí),僅代表作者本人觀點(diǎn),與本網(wǎng)站無關(guān),侵刪請聯(lián)系頁腳下方聯(lián)系方式。

2.《【重新隨機(jī)進(jìn)程】進(jìn)程間通信的信號(hào)量semaphore - linux內(nèi)核分析》僅供讀者參考,本網(wǎng)站未對該內(nèi)容進(jìn)行證實(shí),對其原創(chuàng)性、真實(shí)性、完整性、及時(shí)性不作任何保證。

3.文章轉(zhuǎn)載時(shí)請保留本站內(nèi)容來源地址,http://f99ss.com/gl/2547701.html