System V信號燈(節選自unix網絡編程卷2-進程間通信)
System V信號燈(節選自unix網絡編程卷2-進程間通信)
System V信號燈通過定義如下概念增加了另外一級復雜度:
計數信號燈集(set of counting semaphores): 一個或者多個信號燈(構成一個集合),其中每個都
是計算信號燈. 每個集合的信號燈數存在一個限制, 一般在25個的數量級上. 當我們談論"System V
信號燈"時, 所指的是計數信號燈集. 當我們談論"Posix 信號燈"時, 所指的是單個計數信號燈.
對于系統中的每個信號燈集, 內核維護一個如下的信息結構, 它定義在<sys/sem.h>頭文件中:
struct semid_ds{
struct ipc_perm sem_perm; //operation permission struct
struct sem *sem_base; //ptr to array of semaphores in set
ushort sem_nsems; //of semaphores in set
time_t sem_otime; //time of last semop()
time_t sem_ctime; //time of creation or last IPC_SET
};
其中的ipc_perm結構描述對于當前這個特定的信號燈的訪問權限.
struct ipc_perm{
uid_t uid; // owner's effective user id
gid_t gid; // owner's effective group id
uid_t cuid; // creator's effective user id
gid_t cgid; // creator's effective group id
mode_t mode; // access modes
ulong seq; // slot usage sequence number
key_t key; // key
};
sem結構是內核用于維護某個給定信號燈的一組值的內部數據結構.一個信號燈集的每個成員由如
下結構描述:
struct sem{
ushort_t semval; //semaphore value, nonnegative
short sempid; //PID of last successful semop(), SETVAL, SETALL
ushort_t semncnt; //awalting semval > current value
ushort_t semzcnt; //awalting semval =0
};
注意sem_base含有指向某個sem_base含有指向某個sem結構數組的指針, 當前信號燈集中的每個信
號燈對應其中一個數組元素.
除了維護一個信號燈集中每個信號燈的實際值之外, 內核還給該集合中每個信號燈維護另外三個
信息, 對其值執行最后一次操作的進程的pid, 等待其值增長的進程數計數以及等待其值變為0的pid計
數.
///////////////////////////////////////////////////////////
semget 函數
semget函數創建一個信號燈集或訪問一個已存在的信號燈集
#include <sys/sem.h>
int semget(key_t key, int nsems, int oflag);
@return int 成功時返回非負數標識符, 出錯時返回-1
返回值是一個稱為信號燈標識符(semaphore identifier)的整數, semop和semctl函數將使用它.
指定key為IPC_PRIVATE保證創建一個唯一的IPC對象. 沒有一對pahtname和id的組合會導致ftok產
生IPC_PRIVATE這個鍵值.
nsems 指定集合中的信號燈數. 如果我們不創建一個新信號燈集, 而只是訪問一個已存在的信號
燈集合, 那就可以指定該參數為0, 一旦創建完畢, 就不能改變其中的信號燈值.
oflag SEM_R和SEM_A常值的組合. 其中R代表read, A代表alter. 他們還可以與
IPC_CREAT|IPC_EXCL按位或.
設置oflag參數的IPC_CREAT位但不設置它的IPC_EXCL位時, 如果所指定的IPC對象不存在, 那就創
建一個新對象, 否則返回該對象.
同時設置oflag的IPC_CREAT和IPC_EXCL位時, 如果所指定鍵的IPC對象不存在, 那么就創建一個新
的對象, 否則, 返回一個EEXIST錯誤, 因為該對象已存在.
設置IPC_EXCL位, 但不設置IPC_CREAT位是沒有意義的.
當實際操作為創建一個新的信號燈集時, 相應的semid_ds結構的以下成員將被初始化:
.sem_perm結構中的uid和cuid成員被置為調用進程的有效uid, gid和cgid成員被置為調用進程的
有效gid
.oflag參數中的讀寫權限位存入sem_perm.mode
.sem_otime 被置為0. sem_ctime則被置為當前時間.
.sem_nsems 被置為nsems參數的值
.與該集合中每個信號燈關聯的哥哥sem結構并不初始化. 這些結構是在SETVAL或SETALL命令調用
semctl時初始化的.
System V信號燈設計中, 創建一個信號燈集(semget)并將它初始化(semctl)需兩次函數調用是一
個致命的缺陷. 一個不完備的解決方案是: 在調用semget時指定IPC_CREATE|IPC_EXCL標志, 只有一個
進程(首先調用semget的那個進程)創建所需信號燈. 該進程隨后初始化該信號燈, 其他進程會收到來
自semget的一個EEXIST錯誤, 于是再次調用semget, 不過這次調用既不指定IPC_CREATE, 也不指定
IPC_EXCL.
然而競爭狀態依然存在, 假設有倆個進程幾乎同時嘗試創建并初始化一個只有單成員信號燈集,
兩者都執行如下幾行標了號的代碼:
1 oflag = IPC_CREAT | IPC_EXCL | SVSEM_MODE;
2 if( (semid = semget(key, 1, oflag))>=0 ) { // success, we are the first, so initialze
3 arg.val=1;
4 semctl(semid, 0, SETVAL, arg);
5 } else if(errno == EEXIST){
//already exists, just open
6 semid = semget(key, 1, SVSEM_MODE);
7 } else
8 err_sys("semget error");
9 semop(semid, ....); // decrement the semaphore by 1
那么如下情況可能發生:
1.第一個進程執行1-3行, 然后被內核阻止執行.
2.內核啟動第二個進程, 執行1 2 5 6 9行
盡管成功創建該信號燈的第一個進程將是初始化該信號燈的唯一進程, 但是由于它完成創建和初
始化操作需要兩個步驟, 因此, 內核有可能在倆步驟之間把上下文切換到另一個進程. 這個新切換來
運行的進程可以隨后使用該信號燈(第9行), 但是該信號燈的值尚未由第一個進程初始化, 當第二個進
程執行第9行時, 該信號燈的值是不確定的.
幸運的是存在繞過這個競爭狀態的方法. 當semget創建一個新的信號燈集時, 其semid_ds結構的
sem_otime成員保證被置為0. (System V手冊, XPG3和Unix98標準也這么說) 該成員只是在semop調用
成功時才被置為當前值. 因此, 上面的例子中的第二個進程成功的再次調用semget(第6行)后, 必須以
ICP_STAT命令調用semctl. 然后等待sem_otime變為非零值, 到時就可以判定該信號燈已被初始化, 而
且對他初始化的那個進程已經成功調用semop. 這意味著創建該信號燈的那個進程必須初始化它的值,
而且必須在任何其他進程可以使用該信號燈之前調用semop.
#include "unpipc.h"
#include "semaphore.h"
#include <stdarg.h>
#define MAX_TRIES 10
union semun {
int val; /* for SETVAL */
struct semid_ds *buf; /* for IPC_STAT and IPC_SET */
unsigned short *array; /* for GETALL and SETALL */
};
sem_t* sem_open( const char *pathname, int oflag, ...){
int i, fd, semflag, semid, save_errno;
key_t key;
mode_t mode;
va_list ap
sem_t *sem;
union semun arg;
unsigned int value;
struct semid_ds seminfo;
struct sembuf initop;
// no mode for sem_open() w/out O_CREAT; guess
semflag = SVSEM_MODE;
semid = -1;
if(oflag & CREAT){
va_start(ap, oflag); // init ap to final named argument
mode = va_arg(ap, va_mode_t);
value = va_arg(ap, unsigned int);
va_end(ap);
// convert to key that will identify System V semaphore
if( (fd = open(pathname, oflag, mode)) == -1 )
return(SEM_FAILED);
close(fd);
if( (key = ftok(pathname, 0)) ==(key_t)-1 )
return(SEM_FAILED);
semflag = IPC_CREATE | (mode & 0777);
if(oflag & O_EXCL)
semflag = semflag | IPC_EXCL;
//create the System V semaphore with IPC_EXCL
if( (semid = semget(key, 1, semflag|IPC_EXCL)) >= 0 ){
//success, we are the first so initialize to 0
arg.val = 0;
if( semctl(semid, 0, SETVAL, arg) == -1)
goto err;
// then increment by value set sem_otime nonzore
if(value > SEMVMX){
errno = EINVAL;
got err;
}
initop.sem_num = 0;
initop.sem_op = value;
initop.sem_flag = 0;
if( semop(semid, &initop, 1) == -1 )
goto err;
goto finish;
} else if( errno != EEXIST || (semflag & IPC_EXCL) != 0 )
goto err;
// else fall through
}
.....
}
// sem_open函數, 前半部分[my_pxsem_svsem/sem_open.c]
/////////////////////////////////////////////////////
semop 函數
使用semget打開一個信號燈集后, 對其中一個或多個信號燈的操作就使用semop函數來執行
#include <sys/sem.h>
int semop(int semid, struct sembuf *opsptr, size_t nops);
@return int 成功時返回0, 出錯時返回-1
opsstr指向一個如下結構的數組:
struct sembuf{
short sem_num; // semaphore number : 0, 1..., nsems-1
short sem_op; // semaphore operation <0,0>0
short sem_flg; // operation flags: 0, IPC_NOWAIT, SEM_UNDO
}
nops 指出由opsptr指向的sembuf結構數組中元素的數目. 該數組中的每個元素給目標信號燈集中某
個特定的信號燈指定一個操作. 這個特定的信號燈由sembuf.sem_num指定, 0代表第一個元素, 1代表
第二個元素, 依次類推, 一直到nsems-1, 其中nsems是目標信號燈集中成員信號燈的數目(創建時
semget的第二個參數).
我們僅僅保證sembuf結構含有所給出的三個成員. 它可能還含有其他成員, 而哥哥成員并不保證
以我們給出的順序排序. 這意味著我們不能靜態初始化這種結構, 例如:
struct sembuf ops = {0, 1, SEM_UNDO}; //error
而是必須使用運行時初始化的方法, 給每個成員賦值.
傳遞給semop函數的操作數組由內核保證原子的執行, 內核或者完成所有指定操作, 或者什么操作
都不做.
每個特定的操作是由sem_op的值確定的, 它可以是負數, 0 或者正數, 在稍后的討論中, 我們將
使用如下術語:
.semval : 信號燈的當前值
.semncnt: 等待semval變為大于其當前值的線程數
.semzcnt: 等待semval變為0的線程數
.semadj : 所指定信號燈針對調用進程的調整值. 只有在對應本操作的sembuf結構的sem_flg成員中
指定SEM_UNDO標志后, semadj才會更新. 這是一個概念性的變量, 它由內核為再其某個信號燈操作中
指定了SEM_UNDO標志的每個進程維護;具有semadj這個名字的結構成員不必存在(調用進程終止時,
semadj加到相應信號燈的semval上, 要是調用進程對某個信號燈的全部操作都指定SEM_UNDO標志, 那
么該進程終止后, 該信號燈的值就會變得像根本沒有運行過進程一樣, 這就是UNDO的本意)
.使得一個給定信號燈操作非阻塞的方法是, 再對應的sembuf.sem_flg成員中指定IPC_NOWAIT標志.
指定了該標志的情況下, 如果不把調用線程投入睡眠, 就完成不了這個給定操作, 那么semop將返回一
個EAGAIN錯誤.
.當一個線程被投入睡眠以等待某個信號燈操作的完成時(我們將看到該線程既可等待這個信號燈值
變為0, 也可等待它變為大于0), 如果它捕獲了一個信號, 那么其信號處理程序的返回將中斷引起睡眠
的semop函數, 該函數于是返回了一個EINTR錯誤, semop是需被捕獲的信號中斷的慢系統調用(slow
system call).
.當一個線程被投入睡眠以等待某個信號燈操作的完成時, 如果該信號燈被另外某個線程或進程從進
程從系統中刪除, 那么引起睡眠的semop函數將返回一個EIDRM錯誤, 表示identifier removed(標識符
已刪除)"
現在描述semop的操作, 它基于每個具體指定的sem_op操作的三個可能值: 正數, 0, 負數.
1.如果sem_op是正數, 其值就加到semval上, 這對應于釋放由某個信號燈控制的資源
如果指定了SEM_UNDO標志, 那就從相應信號燈的semadj值中減掉sem_op的值
2.如果sem_op是0, 那么調用者希望等待到semval變為0. 如果semval已經是0, 那就立即返回
如果semval不為0, 相應信號燈的semzcnt值就加1, 調用線程則阻塞到semval變為0(到那時, 相應
信號燈的semzcnt值再減1). 前面已經提到, 如果指定了IPC_NOWAIT標志, 調用線程就不會被投入睡眠
. 如果某個被捕獲的信號中斷了引起睡眠的semop函數, 或者相應的信號燈被刪除, 那么該函數將過早
的返回一個錯誤.
3.如果sem_op是負數, 那么調用者希望等待semval變為大于或等于sem_op的絕對值.這對應于分配資
源.
如果semval大于或等于sem_op的絕對值, 那就從semval中減掉sem_op的絕對值.
如果指定了SEM_UNDO標志, 那么sem_op的絕對值就加到相應信號燈的semadj值上.
如果semval小于sem_op的絕對值, 相應信號燈的semncnt值就加1, 調用線程則被阻塞到semval變
為大于或等于sem_op的絕對值. 到那時,該線程將被解阻塞, semval中減掉sem_op的絕對值, 相應信號
燈的semncnt值將減1. 如果指定了SEM_UNDO標志, 那么sem_op的絕對值將加到相應信號燈的semadj值
上. 前面已經提到, 如果指定了IPC_NOWAIT標志, 調用線程就不會投入睡眠. 另外, 如果某個被捕獲
的信號中斷了引起睡眠的sem_op函數, 或者相應的信號燈被刪除, 那么該函數將過早的返回一個錯誤.
/////////////////////////////////////////////////////
semctl 函數
semctl函數對一個信號燈執行各種控制操作.
#include <sys/sem.h>
int semctl( int semid, int semnum, int cmd, .../* union semun arg*/ );
@return int 成功時為非負值, 出錯時為-1
semid 標識需控制其操作的信號燈集
semnum 標示該信號燈集中某個成員(0, 1, ....nsems-1). semnum值僅用于GETVAL,
SETVAL,GETNCNT,GETZCNT,SETPID命令
第四個參數為可選, 取決于第三個參數cmd(參見下面給出的聯合中的注釋).
union semun {
int val; /* used for SETVAL only*/
struct semid_ds *buf; /* used for IPC_STAT and IPC_SET */
unsigned short *array; /* used for GETALL and SETALL */
};
這個union并沒有出現再任何系統頭文件中, 因而必須由應用程序聲明.它是以值傳遞的, 而不是
以引用傳遞. 也就是說作為參數的是這個union的值, 而不是指向它的指針.
(不幸的是, 有些系統(FreeBSD和Linux)在<sys/sem.h>頭文件中定義了這個union, 從而造成編寫
可移植性代碼的困難. 盡管由這個系統頭文件來聲明union semun確實有足夠的理由, Unix 98還是聲
稱它必須由應用程序顯式聲明).
System V支持下列cmd值, 除非另外聲明, 否則返回值為0表示成功, 返回值為-1 表示失敗.
GETVAL 把semval的當前值作為函數返回值返回, 既然信號燈絕不會是負數(semval被聲明為
unsigned short), 因此成功的返回值總是非負數.
SETVAL 把semval值設置為arg.val. 如果操作成功, 那么相應信號燈在所在進程中的調整值
(semadj)將被置為0;
GETPID 把sempid的當前值作為函數返回值返回.
GETNCNT 把semncnt的當前值作為函數返回值返回.
GETZCNT 把semzcnt的當前值作為函數返回值返回.
GETALL 返回指定信號燈集中每個成員的semval值. 這些值通過arg.arry指針返回, 函數本身的返
回值為0, 注意, 調用者必須分配足夠容納所指定信號燈集的所有成員的semval值的一個unsigned
short[], 然后把arg.array設置成指向這個數組.
SETALL 設置所指定信號燈集中每個成員的semval值. 這些值是通過arg.array數組指定的
IPC_RMID 把由semid指定的信號燈集從系統中刪除掉.
IPC_SET 設置所指定信號燈集的semid_ds結構中的以下三個成員: sem_perm.uid, sem_perm.gid,
sem_perm.mode, 這些值由arg.buf參數指向的結構中的相應成員. semid_ds.sem_ctime也被設置成當
前值.
IPC_STAT (通過arg.buf參數) 返回所指定信號燈集的當前semid_ds結構. 注意, 調用者必須首先分
配一個semid_ds結構, 并把arg.buf設置成指向這個結構.
System V信號燈通過定義如下概念增加了另外一級復雜度:
計數信號燈集(set of counting semaphores): 一個或者多個信號燈(構成一個集合),其中每個都
是計算信號燈. 每個集合的信號燈數存在一個限制, 一般在25個的數量級上. 當我們談論"System V
信號燈"時, 所指的是計數信號燈集. 當我們談論"Posix 信號燈"時, 所指的是單個計數信號燈.
對于系統中的每個信號燈集, 內核維護一個如下的信息結構, 它定義在<sys/sem.h>頭文件中:
struct semid_ds{
struct ipc_perm sem_perm; //operation permission struct
struct sem *sem_base; //ptr to array of semaphores in set
ushort sem_nsems; //of semaphores in set
time_t sem_otime; //time of last semop()
time_t sem_ctime; //time of creation or last IPC_SET
};
其中的ipc_perm結構描述對于當前這個特定的信號燈的訪問權限.
struct ipc_perm{
uid_t uid; // owner's effective user id
gid_t gid; // owner's effective group id
uid_t cuid; // creator's effective user id
gid_t cgid; // creator's effective group id
mode_t mode; // access modes
ulong seq; // slot usage sequence number
key_t key; // key
};
sem結構是內核用于維護某個給定信號燈的一組值的內部數據結構.一個信號燈集的每個成員由如
下結構描述:
struct sem{
ushort_t semval; //semaphore value, nonnegative
short sempid; //PID of last successful semop(), SETVAL, SETALL
ushort_t semncnt; //awalting semval > current value
ushort_t semzcnt; //awalting semval =0
};
注意sem_base含有指向某個sem_base含有指向某個sem結構數組的指針, 當前信號燈集中的每個信
號燈對應其中一個數組元素.
除了維護一個信號燈集中每個信號燈的實際值之外, 內核還給該集合中每個信號燈維護另外三個
信息, 對其值執行最后一次操作的進程的pid, 等待其值增長的進程數計數以及等待其值變為0的pid計
數.
///////////////////////////////////////////////////////////
semget 函數
semget函數創建一個信號燈集或訪問一個已存在的信號燈集
#include <sys/sem.h>
int semget(key_t key, int nsems, int oflag);
@return int 成功時返回非負數標識符, 出錯時返回-1
返回值是一個稱為信號燈標識符(semaphore identifier)的整數, semop和semctl函數將使用它.
指定key為IPC_PRIVATE保證創建一個唯一的IPC對象. 沒有一對pahtname和id的組合會導致ftok產
生IPC_PRIVATE這個鍵值.
nsems 指定集合中的信號燈數. 如果我們不創建一個新信號燈集, 而只是訪問一個已存在的信號
燈集合, 那就可以指定該參數為0, 一旦創建完畢, 就不能改變其中的信號燈值.
oflag SEM_R和SEM_A常值的組合. 其中R代表read, A代表alter. 他們還可以與
IPC_CREAT|IPC_EXCL按位或.
設置oflag參數的IPC_CREAT位但不設置它的IPC_EXCL位時, 如果所指定的IPC對象不存在, 那就創
建一個新對象, 否則返回該對象.
同時設置oflag的IPC_CREAT和IPC_EXCL位時, 如果所指定鍵的IPC對象不存在, 那么就創建一個新
的對象, 否則, 返回一個EEXIST錯誤, 因為該對象已存在.
設置IPC_EXCL位, 但不設置IPC_CREAT位是沒有意義的.
當實際操作為創建一個新的信號燈集時, 相應的semid_ds結構的以下成員將被初始化:
.sem_perm結構中的uid和cuid成員被置為調用進程的有效uid, gid和cgid成員被置為調用進程的
有效gid
.oflag參數中的讀寫權限位存入sem_perm.mode
.sem_otime 被置為0. sem_ctime則被置為當前時間.
.sem_nsems 被置為nsems參數的值
.與該集合中每個信號燈關聯的哥哥sem結構并不初始化. 這些結構是在SETVAL或SETALL命令調用
semctl時初始化的.
System V信號燈設計中, 創建一個信號燈集(semget)并將它初始化(semctl)需兩次函數調用是一
個致命的缺陷. 一個不完備的解決方案是: 在調用semget時指定IPC_CREATE|IPC_EXCL標志, 只有一個
進程(首先調用semget的那個進程)創建所需信號燈. 該進程隨后初始化該信號燈, 其他進程會收到來
自semget的一個EEXIST錯誤, 于是再次調用semget, 不過這次調用既不指定IPC_CREATE, 也不指定
IPC_EXCL.
然而競爭狀態依然存在, 假設有倆個進程幾乎同時嘗試創建并初始化一個只有單成員信號燈集,
兩者都執行如下幾行標了號的代碼:
1 oflag = IPC_CREAT | IPC_EXCL | SVSEM_MODE;
2 if( (semid = semget(key, 1, oflag))>=0 ) { // success, we are the first, so initialze
3 arg.val=1;
4 semctl(semid, 0, SETVAL, arg);
5 } else if(errno == EEXIST){
//already exists, just open
6 semid = semget(key, 1, SVSEM_MODE);
7 } else
8 err_sys("semget error");
9 semop(semid, ....); // decrement the semaphore by 1
那么如下情況可能發生:
1.第一個進程執行1-3行, 然后被內核阻止執行.
2.內核啟動第二個進程, 執行1 2 5 6 9行
盡管成功創建該信號燈的第一個進程將是初始化該信號燈的唯一進程, 但是由于它完成創建和初
始化操作需要兩個步驟, 因此, 內核有可能在倆步驟之間把上下文切換到另一個進程. 這個新切換來
運行的進程可以隨后使用該信號燈(第9行), 但是該信號燈的值尚未由第一個進程初始化, 當第二個進
程執行第9行時, 該信號燈的值是不確定的.
幸運的是存在繞過這個競爭狀態的方法. 當semget創建一個新的信號燈集時, 其semid_ds結構的
sem_otime成員保證被置為0. (System V手冊, XPG3和Unix98標準也這么說) 該成員只是在semop調用
成功時才被置為當前值. 因此, 上面的例子中的第二個進程成功的再次調用semget(第6行)后, 必須以
ICP_STAT命令調用semctl. 然后等待sem_otime變為非零值, 到時就可以判定該信號燈已被初始化, 而
且對他初始化的那個進程已經成功調用semop. 這意味著創建該信號燈的那個進程必須初始化它的值,
而且必須在任何其他進程可以使用該信號燈之前調用semop.
#include "unpipc.h"
#include "semaphore.h"
#include <stdarg.h>
#define MAX_TRIES 10
union semun {
int val; /* for SETVAL */
struct semid_ds *buf; /* for IPC_STAT and IPC_SET */
unsigned short *array; /* for GETALL and SETALL */
};
sem_t* sem_open( const char *pathname, int oflag, ...){
int i, fd, semflag, semid, save_errno;
key_t key;
mode_t mode;
va_list ap
sem_t *sem;
union semun arg;
unsigned int value;
struct semid_ds seminfo;
struct sembuf initop;
// no mode for sem_open() w/out O_CREAT; guess
semflag = SVSEM_MODE;
semid = -1;
if(oflag & CREAT){
va_start(ap, oflag); // init ap to final named argument
mode = va_arg(ap, va_mode_t);
value = va_arg(ap, unsigned int);
va_end(ap);
// convert to key that will identify System V semaphore
if( (fd = open(pathname, oflag, mode)) == -1 )
return(SEM_FAILED);
close(fd);
if( (key = ftok(pathname, 0)) ==(key_t)-1 )
return(SEM_FAILED);
semflag = IPC_CREATE | (mode & 0777);
if(oflag & O_EXCL)
semflag = semflag | IPC_EXCL;
//create the System V semaphore with IPC_EXCL
if( (semid = semget(key, 1, semflag|IPC_EXCL)) >= 0 ){
//success, we are the first so initialize to 0
arg.val = 0;
if( semctl(semid, 0, SETVAL, arg) == -1)
goto err;
// then increment by value set sem_otime nonzore
if(value > SEMVMX){
errno = EINVAL;
got err;
}
initop.sem_num = 0;
initop.sem_op = value;
initop.sem_flag = 0;
if( semop(semid, &initop, 1) == -1 )
goto err;
goto finish;
} else if( errno != EEXIST || (semflag & IPC_EXCL) != 0 )
goto err;
// else fall through
}
.....
}
// sem_open函數, 前半部分[my_pxsem_svsem/sem_open.c]
/////////////////////////////////////////////////////
semop 函數
使用semget打開一個信號燈集后, 對其中一個或多個信號燈的操作就使用semop函數來執行
#include <sys/sem.h>
int semop(int semid, struct sembuf *opsptr, size_t nops);
@return int 成功時返回0, 出錯時返回-1
opsstr指向一個如下結構的數組:
struct sembuf{
short sem_num; // semaphore number : 0, 1..., nsems-1
short sem_op; // semaphore operation <0,0>0
short sem_flg; // operation flags: 0, IPC_NOWAIT, SEM_UNDO
}
nops 指出由opsptr指向的sembuf結構數組中元素的數目. 該數組中的每個元素給目標信號燈集中某
個特定的信號燈指定一個操作. 這個特定的信號燈由sembuf.sem_num指定, 0代表第一個元素, 1代表
第二個元素, 依次類推, 一直到nsems-1, 其中nsems是目標信號燈集中成員信號燈的數目(創建時
semget的第二個參數).
我們僅僅保證sembuf結構含有所給出的三個成員. 它可能還含有其他成員, 而哥哥成員并不保證
以我們給出的順序排序. 這意味著我們不能靜態初始化這種結構, 例如:
struct sembuf ops = {0, 1, SEM_UNDO}; //error
而是必須使用運行時初始化的方法, 給每個成員賦值.
傳遞給semop函數的操作數組由內核保證原子的執行, 內核或者完成所有指定操作, 或者什么操作
都不做.
每個特定的操作是由sem_op的值確定的, 它可以是負數, 0 或者正數, 在稍后的討論中, 我們將
使用如下術語:
.semval : 信號燈的當前值
.semncnt: 等待semval變為大于其當前值的線程數
.semzcnt: 等待semval變為0的線程數
.semadj : 所指定信號燈針對調用進程的調整值. 只有在對應本操作的sembuf結構的sem_flg成員中
指定SEM_UNDO標志后, semadj才會更新. 這是一個概念性的變量, 它由內核為再其某個信號燈操作中
指定了SEM_UNDO標志的每個進程維護;具有semadj這個名字的結構成員不必存在(調用進程終止時,
semadj加到相應信號燈的semval上, 要是調用進程對某個信號燈的全部操作都指定SEM_UNDO標志, 那
么該進程終止后, 該信號燈的值就會變得像根本沒有運行過進程一樣, 這就是UNDO的本意)
.使得一個給定信號燈操作非阻塞的方法是, 再對應的sembuf.sem_flg成員中指定IPC_NOWAIT標志.
指定了該標志的情況下, 如果不把調用線程投入睡眠, 就完成不了這個給定操作, 那么semop將返回一
個EAGAIN錯誤.
.當一個線程被投入睡眠以等待某個信號燈操作的完成時(我們將看到該線程既可等待這個信號燈值
變為0, 也可等待它變為大于0), 如果它捕獲了一個信號, 那么其信號處理程序的返回將中斷引起睡眠
的semop函數, 該函數于是返回了一個EINTR錯誤, semop是需被捕獲的信號中斷的慢系統調用(slow
system call).
.當一個線程被投入睡眠以等待某個信號燈操作的完成時, 如果該信號燈被另外某個線程或進程從進
程從系統中刪除, 那么引起睡眠的semop函數將返回一個EIDRM錯誤, 表示identifier removed(標識符
已刪除)"
現在描述semop的操作, 它基于每個具體指定的sem_op操作的三個可能值: 正數, 0, 負數.
1.如果sem_op是正數, 其值就加到semval上, 這對應于釋放由某個信號燈控制的資源
如果指定了SEM_UNDO標志, 那就從相應信號燈的semadj值中減掉sem_op的值
2.如果sem_op是0, 那么調用者希望等待到semval變為0. 如果semval已經是0, 那就立即返回
如果semval不為0, 相應信號燈的semzcnt值就加1, 調用線程則阻塞到semval變為0(到那時, 相應
信號燈的semzcnt值再減1). 前面已經提到, 如果指定了IPC_NOWAIT標志, 調用線程就不會被投入睡眠
. 如果某個被捕獲的信號中斷了引起睡眠的semop函數, 或者相應的信號燈被刪除, 那么該函數將過早
的返回一個錯誤.
3.如果sem_op是負數, 那么調用者希望等待semval變為大于或等于sem_op的絕對值.這對應于分配資
源.
如果semval大于或等于sem_op的絕對值, 那就從semval中減掉sem_op的絕對值.
如果指定了SEM_UNDO標志, 那么sem_op的絕對值就加到相應信號燈的semadj值上.
如果semval小于sem_op的絕對值, 相應信號燈的semncnt值就加1, 調用線程則被阻塞到semval變
為大于或等于sem_op的絕對值. 到那時,該線程將被解阻塞, semval中減掉sem_op的絕對值, 相應信號
燈的semncnt值將減1. 如果指定了SEM_UNDO標志, 那么sem_op的絕對值將加到相應信號燈的semadj值
上. 前面已經提到, 如果指定了IPC_NOWAIT標志, 調用線程就不會投入睡眠. 另外, 如果某個被捕獲
的信號中斷了引起睡眠的sem_op函數, 或者相應的信號燈被刪除, 那么該函數將過早的返回一個錯誤.
/////////////////////////////////////////////////////
semctl 函數
semctl函數對一個信號燈執行各種控制操作.
#include <sys/sem.h>
int semctl( int semid, int semnum, int cmd, .../* union semun arg*/ );
@return int 成功時為非負值, 出錯時為-1
semid 標識需控制其操作的信號燈集
semnum 標示該信號燈集中某個成員(0, 1, ....nsems-1). semnum值僅用于GETVAL,
SETVAL,GETNCNT,GETZCNT,SETPID命令
第四個參數為可選, 取決于第三個參數cmd(參見下面給出的聯合中的注釋).
union semun {
int val; /* used for SETVAL only*/
struct semid_ds *buf; /* used for IPC_STAT and IPC_SET */
unsigned short *array; /* used for GETALL and SETALL */
};
這個union并沒有出現再任何系統頭文件中, 因而必須由應用程序聲明.它是以值傳遞的, 而不是
以引用傳遞. 也就是說作為參數的是這個union的值, 而不是指向它的指針.
(不幸的是, 有些系統(FreeBSD和Linux)在<sys/sem.h>頭文件中定義了這個union, 從而造成編寫
可移植性代碼的困難. 盡管由這個系統頭文件來聲明union semun確實有足夠的理由, Unix 98還是聲
稱它必須由應用程序顯式聲明).
System V支持下列cmd值, 除非另外聲明, 否則返回值為0表示成功, 返回值為-1 表示失敗.
GETVAL 把semval的當前值作為函數返回值返回, 既然信號燈絕不會是負數(semval被聲明為
unsigned short), 因此成功的返回值總是非負數.
SETVAL 把semval值設置為arg.val. 如果操作成功, 那么相應信號燈在所在進程中的調整值
(semadj)將被置為0;
GETPID 把sempid的當前值作為函數返回值返回.
GETNCNT 把semncnt的當前值作為函數返回值返回.
GETZCNT 把semzcnt的當前值作為函數返回值返回.
GETALL 返回指定信號燈集中每個成員的semval值. 這些值通過arg.arry指針返回, 函數本身的返
回值為0, 注意, 調用者必須分配足夠容納所指定信號燈集的所有成員的semval值的一個unsigned
short[], 然后把arg.array設置成指向這個數組.
SETALL 設置所指定信號燈集中每個成員的semval值. 這些值是通過arg.array數組指定的
IPC_RMID 把由semid指定的信號燈集從系統中刪除掉.
IPC_SET 設置所指定信號燈集的semid_ds結構中的以下三個成員: sem_perm.uid, sem_perm.gid,
sem_perm.mode, 這些值由arg.buf參數指向的結構中的相應成員. semid_ds.sem_ctime也被設置成當
前值.
IPC_STAT (通過arg.buf參數) 返回所指定信號燈集的當前semid_ds結構. 注意, 調用者必須首先分
配一個semid_ds結構, 并把arg.buf設置成指向這個結構.
posted on 2009-11-27 00:57 Khan 閱讀(1941) 評論(0) 編輯 收藏 引用 所屬分類: GCC/G++