什么是信號(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