轉載:進程和線程編程

 
 轉載他人:
 
進程和線程編程
目 錄
  1. 進程和線程編程
    1. 原始管道
      1. pipe()
      2. dup()
      3. dup2()
      4. popen()和pclose()
    2. 命名管道
      1. 創建FIFO
      2. 操作FIFO
      3. 阻塞FIFO
    3. 消息隊列
      1. msgget()
      2. msgsnd()
      3. msgrcv()
      4. msgctl()
    4. 信號量
      1. semget()
      2. semop()
      3. semctl()
    5. 共享內存
      1. shmget()
      2. shmat()
      3. shmctl()
      4. shmdt()
    6. 線程
      1. 線程同步
      2. 使用信號量協調程序
      3. 代碼例子
        1. newthread
        2. exitthead
        3. getchannel
        4. def
        5. release
        6. redezvous
        7. unbouded


進程和線程編程

   
看一下UNIX系統中的進程和Mach的任務和線程之間的關係。在UNIX系統中,一個進程包括一個可執行的程序和一系列的資源,例如文件描述符表和地址空間。在Mach中,一個任務僅包括一系列的資源;線程處理所有的可執行代碼。一個Mach的任務可以有任意數目的線程和它相關,同時每個線程必須和某個任務相關。和某一個給定的任務相關的所有線程都共享任務的資源。這樣,一個線程就是一個程序計數器、一個堆棧和一系列的寄存器。所有需要使用的數據結構都屬於任務。一個UNIX系統中的進程在Mach中對應於一個任務和一個單獨的線程。


原始管道

   
使用C語言創建管道要比在shell下使用管道複雜一些。如果要使用C語言創建一個簡單的管道,可以使用系統調用pipe()。它接受一個參數,也就是一個包括兩個整數的數組。如果系統調用成功,此數組將包括管道使用的兩個文件描述符。創建一個管道之後,一般情況下進程將產生一個新的進程。
    可以通過打開兩個管道來創建一個雙向的管道。但需要在子進程中正確地設置文件描述必須在系統調用fork()中調用pipe(),否則子進程將不會繼承文件描述符。當使用半雙工管道時,任何關聯的進程都必須共享一個相關的祖先進程。因爲管道存在於系統內核之中,所以任何不在創建管道的進程的祖先進程之中的進程都將無法尋址它。而在命名管道中卻不是這樣。


pipe()

系統調用:pipe();
原型:intpipe(intfd[2]);
返回值:如果系統調用成功,返回0
如果系統調用失敗返回-1:errno=EMFILE(沒有空閒的文件描述符)
EMFILE(系統文件表已滿)
EFAULT(fd數組無效)
注意fd[0]用於讀取管道,fd[1]用於寫入管道。
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
main()
{
intfd[2];
pipe(fd);
..
}
一旦創建了管道,我們就可以創建一個新的子進程:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
main()
{
intfd[2];
pid_t childpid;
pipe(fd);
if((childpid=fork())==-1)
{
perror("fork");
exit(1);
}..
}
    如果父進程希望從子進程中讀取數據,那麼它應該關閉fd1,同時子進程關閉fd0。反之,如果父進程希望向子進程中發送數據,那麼它應該關閉fd0,同時子進程關閉fd1。因爲文件描述符是在父進程和子進程之間共享,所以我們要及時地關閉不需要的管道的那一端。單從技術的角度來說,如果管道的一端沒有正確地關閉的話,你將無法得到一個EOF。
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
main()
{
intfd[2];
pid_t childpid;
pipe(fd);
if((childpid=fork())==-1)
{
perror("fork");
exit(1);
}
if(childpid==0)
{
/*Child process closes up in put side of pipe*/
close(fd[0]);
}
else
{
/*Parent process closes up out put side of pipe*/
close(fd[1]);
}..
}
    正如前面提到的,一但創建了管道之後,管道所使用的文件描述符就和正常文件的文件描述符一樣了。
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
intmain(void)
{
intfd[2],nbytes;
pid_tchildpid;
charstring[]="Hello,world!/n";
charreadbuffer[80];
pipe(fd);
if((childpid=fork())==-1)
{
perror("fork");
exit(1);
}
if(childpid==0)
{
/*Child process closes up in put side of pipe*/
close(fd[0]);
/*Send"string"through the out put side of pipe*/
write(fd[1],string,strlen(string));
exit(0);
}
else
{
/*Parent process closes up out put side of pipe*/
close(fd[1]);
/*Readinastringfromthepipe*/
nbytes=read(fd[0],readbuffer,sizeof(readbuffer));
printf("Receivedstring:%s",readbuffer);
}
return(0);
}
    一般情況下,子進程中的文件描述符將會複製到標準的輸入和輸出中。這樣子進程可以使用exec()執行另一個程序,此程序繼承了標準的數據流。


dup()

系統調用:dup();
原型:intdup(intoldfd);
返回:如果系統調用成功,返回新的文件描述符
如果系統調用失敗,返回-1:errno=EBADF(oldfd不是有效的文件描述符)
EBADF(newfd超出範圍)
EMFILE(進程的文件描述符太多)
    注意舊文件描述符oldfd沒有關閉。雖然舊文件描述符和新創建的文件描述符可以交換使用,但一般情況下需要首先關閉一個。系統調用dup()使用的是號碼最小的空閒的文件描述符。
再看下面的程序:
..
childpid=fork();
if(childpid==0)
{
/*Close up standard input of the child*/
close(0);
/*Dup licate the input side of pipe to stdin*/
dup(fd[0]);
execlp("sort","sort",NULL);
.
}
    因爲文件描述符0(stdin)被關閉,所以dup()把管道的輸入描述符複製到它的標準輸入中。這樣我們可以調用execlp(),使用sort程序覆蓋子進程的正文段。因爲新創建的程序從它的父進程中繼承了標準輸入/輸出流,所以它實際上繼承了管道的輸入端作爲它的標準輸入端。現在,最初的父進程送往管道的任何數據都將會直接送往sort函數。


dup2()

系統調用:dup2();
原型:intdup2(intoldfd,intnewfd);
返回值:如果調用成功,返回新的文件描述符
如果調用失敗,返回-1:errno=EBADF(oldfd不是有效的文件描述符)
EBADF(newfd超出範圍)
EMFILE(進程的文件描述符太多)
注意dup2()將關閉舊文件描述符。
    使用此係統調用,可以將close操作和文件描述符複製操作集成到一個系統調用中。另外,此係統調用保證了操作的自動進行,也就是說操作不能被其他的信號中斷。這個操作將會在返回系統內核之前完成。如果使用前一個系統調用dup(),程序員不得不在此之前執行一個close()操作。請看下面的程序:
..
childpid=fork();
if(childpid==0)
{
/*Close stdin,dup licate the input side of pipe to stdin*/
dup2(0,fd[0]);
execlp("sort","sort",NULL);
..
}


popen()和pclose()

如果你認爲上面創建和使用管道的方法過於繁瑣的話,你也可以使用下面的簡單的方法:
庫函數:popen()和pclose();
原型:FILE*popen(char*command,char*type);
返回值:如果成功,返回一個新的文件流。
如果無法創建進程或者管道,返回NULL。
    此標準的庫函數通過在系統內部調用pipe()來創建一個半雙工的管道,然後它創建一個子進程,啓動shell,最後在shell上執行command參數中的命令。管道中數據流的方向是由第二個參數type控制的。此參數可以是r或者w,分別代表讀或寫。但不能同時爲讀和寫。在Linux系統下,管道將會以參數type中第一個字符代表的方式打開。所以,如果你在參數type中寫入rw,管道將會以讀的方式打開。
    雖然此庫函數的用法很簡單,但也有一些不利的地方。例如它失去了使用系統調用pipe()時可以有的對系統的控制。儘管這樣,因爲可以直接地使用shell命令,所以shell中的一些通配符和其他的一些擴展符號都可以在command參數中使用。
使用popen()創建的管道必須使用pclose()關閉。其實,popen/pclose和標準文件輸入/輸出流中的fopen()/fclose()十分相似。

庫函數:pclose();
原型:intpclose(FILE*stream);
返回值:返回系統調用wait4()的狀態。
如果stream無效,或者系統調用wait4()失敗,則返回-1。
    注意此庫函數等待管道進程運行結束,然後關閉文件流。庫函數pclose()在使用popen()創建的進程上執行wait4()函數。當它返回時,它將破壞管道和文件系統。
    在下面的例子中,用sort命令打開了一個管道,然後對一個字符數組排序:
#include<stdio.h>
#defineMAXSTRS5
intmain(void)
{
intcntr;
FILE*pipe_fp;
char*strings[MAXSTRS]={"echo","bravo","alpha",
"charlie","delta"};
/*Createonewaypipelinewithcalltopopen()*/
if((pipe_fp=popen("sort","w"))==NULL)
{
perror("popen");
exit(1);
}
/*Processingloop*/
for(cntr=0;cntr<MAXSTRS;cntr++){
fputs(strings[cntr],pipe_fp);
fputc('/n',pipe_fp);
}
/*Closethepipe*/
pclose(pipe_fp);
return(0);
}
因爲popen()使用shell執行命令,所以所有的shell擴展符和通配符都可以使用。此外,它還可以和popen()一起使用重定向和輸出管道函數。再看下面的例子:
popen("ls~scottb","r");
popen("sort>/tmp/foo","w");
popen("sort|uniq|more","w");
下面的程序是另一個使用popen()的例子,它打開兩個管道(一個用於ls命令,另一個用於
sort命令):
#include<stdio.h>
intmain(void)
{
FILE*pipein_fp,*pipeout_fp;
charreadbuf[80];
/*Createonewaypipelinewithcalltopopen()*/
if((pipein_fp=popen("ls","r"))==NULL)
{
perror("popen");
exit(1);
}
/*Createonewaypipelinewithcalltopopen()*/
if((pipeout_fp=popen("sort","w"))==NULL)
{
perror("popen");
exit(1);
}
/*Processingloop*/
while(fgets(readbuf,80,pipein_fp))
fputs(readbuf,pipeout_fp);
/*Closethepipes*/
pclose(pipein_fp);
pclose(pipeout_fp);
return(0);
}
最後,我們再看一個使用popen()的例子。此程序用於創建一個命令和文件之間的管道:
#include<stdio.h>
intmain(intargc,char*argv[])
{
FILE*pipe_fp,*infile;
charreadbuf[80];
if(argc!=3){
fprintf(stderr,"USAGE:popen3[command][filename]/n");
exit(1);
}
/*Open up input file*/
if((infile=fopen(argv[2],"rt"))==NULL)
{
perror("fopen");
exit(1);
}
/*Create one way pipe line with call topopen()*/
if((pipe_fp=popen(argv[1],"w"))==NULL)
{
perror("popen");
exit(1);
}
/*Processingloop*/
do{
fgets(readbuf,80,infile);
if(feof(infile))break;
fputs(readbuf,pipe_fp);
}while(!feof(infile));
fclose(infile);
pclose(pipe_fp);
return(0);
}
下面是使用此程序的例子:
popen3sortpopen3.c
popen3catpopen3.c
popen3morepopen3.c
popen3catpopen3.c|grepmain


命名管道

   
命名管道和一般的管道基本相同,但也有一些顯著的不同:
*命名管道是在文件系統中作爲一個特殊的設備文件而存在的。
*不同祖先的進程之間可以通過管道共享數據。
*當共享管道的進程執行完所有的I/O操作以後,命名管道將繼續保存在文件系統中以便以後使用。
    一個管道必須既有讀取進程,也要有寫入進程。如果一個進程試圖寫入到一個沒有讀取進程的管道中,那麼系統內核將會產生SIGPIPE信號。當兩個以上的進程同時使用管道時,這一點尤其重要。


創建FIFO

   
可以有幾種方法創建一個命名管道。頭兩種方法可以使用shell。
mknodMYFIFOp
mkfifoa=rwMYFIFO
   
上面的兩個命名執行同樣的操作,但其中有一點不同。命令mkfifo提供一個在創建之後直接改變FIFO文件存取權限的途徑,而命令mknod需要調用命令chmod。
    一個物理文件系統可以通過p指示器十分容易地分辨出一個FIFO文件。
$ls-lMYFIFO
prw-r--r--1rootroot0Dec1422:15MYFIFO|
    請注意在文件名後面的管道符號“|”。
    我們可以使用系統調用mknod()來創建一個FIFO管道:
庫函數:mknod();
原型:intmknod(char*pathname,mode_tmode,dev_tdev);
返回值:如果成功,返回0
如果失敗,返回-1:errno=EFAULT(無效路徑名)
EACCES(無存取權限)
ENAMETOOLONG(路徑名太長)
ENOENT(無效路徑名)
ENOTDIR(無效路徑名)
    下面看一個使用C語言創建FIFO管道的例子:
mknod("/tmp/MYFIFO",S_IFIFO|0666,0);
    在這個例子中,文件/tmp/MYFIFO是要創建的FIFO文件。它的存取權限是0666。存取權限
也可以使用umask修改:
final_umask=requested_permissions&~original_umask
    一個常用的使用系統調用umask()的方法就是臨時地清除umask的值:
umask(0);
mknod("/tmp/MYFIFO",S_IFIFO|0666,0);
    另外,mknod()中的第三個參數只有在創建一個設備文件時才能用到。它包括設備文件的
主設備號和從設備號。
}
}
 


操作FIFO

    FIFO
上的I/O操作和正常管道上的I/O操作基本一樣,只有一個主要的不同。系統調用open用來在物理上打開一個管道。在半雙工的管道中,這是不必要的。因爲管道在系統內核中,而不是在一個物理的文件系統中。在我們的例子中,我們將像使用一個文件流一樣使用管道,也就是使用fopen()打開管道,使用fclose()關閉它。
    請看下面的簡單的服務程序進程:
#include<stdio.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<unistd.h>
#include<linux/stat.h>
#defineFIFO_FILE"MYFIFO"
intmain(void)
{
FILE*fp;
charreadbuf[80];
/*CreatetheFIFOifitdoesnotexist*/
umask(0);
mknod(FIFO_FILE,S_IFIFO|0666,0);
while(1)
{
fp=fopen(FIFO_FILE,"r");
fgets(readbuf,80,fp);
printf("Receivedstring:%s/n",readbuf);
fclose(fp);
return(0);
    因爲FIFO管道缺省時有阻塞的函數,所以你可以在後臺運行此程序:
$fifoserver&
    再來看一下下面的簡單的客戶端程序:
#include<stdio.h>
#include<stdlib.h>
#defineFIFO_FILE"MYFIFO"
intmain(int argc,char* argv[])
{
FILE*fp;
if(argc!=2){
printf("USAGE:fifoclient[string]/n");
exit(1);
}
if((fp=fopen(FIFO_FILE,"w"))==NULL){
perror("fopen");
exit(1);
}
fputs(argv[1],fp);
fclose(fp);
return(0);
}


阻塞FIFO

   
一般情況下,FIFO管道上將會有阻塞的情況發生。也就是說,如果一個FIFO管道打開供讀取的話,它將一直阻塞,直到其他的進程打開管道寫入信息。這種過程反過來也一樣。如果你不需要阻塞函數的話,你可以在系統調用open()中設置O_NONBLOCK標誌,這樣可以取消缺省的阻塞函數。


消息隊列

   
在UNIX的SystemV版本,AT&T引進了三種新形式的IPC功能(消息隊列、信號量、以及共享內存)。但BSD版本的UNIX使用套接口作爲主要的IPC形式。Linux系統同時支持這兩個版本。


msgget()

系統調用msgget()
    如果希望創建一個新的消息隊列,或者希望存取一個已經存在的消息隊列,你可以使用系統調用msgget()。
系統調用:msgget();
原型:intmsgget(key_t key,int msgflg);
返回值:如果成功,返回消息隊列標識符
如果失敗,則返回-1:errno=EACCESS(權限不允許)
EEXIST(隊列已經存在,無法創建)
EIDRM(隊列標誌爲刪除)
ENOENT(隊列不存在)
ENOMEM(創建隊列時內存不夠)
ENOSPC(超出最大隊列限制)
    系統調用msgget()中的第一個參數是關鍵字值(通常是由ftok()返回的)。然後此關鍵字值將會和其他已經存在於系統內核中的關鍵字值比較。這時,打開和存取操作是和參數msgflg中的內容相關的。
IPC_CREAT如果內核中沒有此隊列,則創建它。
IPC_EXCL當和IPC_CREAT一起使用時,如果隊列已經存在,則失敗。
    如果單獨使用IPC_CREAT,則msgget()要麼返回一個新創建的消息隊列的標識符,要麼返回具有相同關鍵字值的隊列的標識符。如果IPC_EXCL和IPC_CREAT一起使用,則msgget()要麼創建一個新的消息隊列,要麼如果隊列已經存在則返回一個失敗值-1。IPC_EXCL單獨使用是沒有用處的。
下面看一個打開和創建一個消息隊列的例子:
intopen_queue(key_t keyval)
{
intqid;
if((qid=msgget(keyval,IPC_CREAT|0660))==-1)
{
return(-1);
}
return(qid);
}


msgsnd()

系統調用msgsnd()
    一旦我們得到了隊列標識符,我們就可以在隊列上執行我們希望的操作了。如果想要往隊列中發送一條消息,你可以使用系統調用msgsnd():
系統調用:msgsnd();
原型:intmsgsnd(int msqid,struct msgbuf*msgp,int msgsz,int msgflg);
返回值:如果成功,0。
如果失敗,-1:errno=EAGAIN(隊列已滿,並且使用了IPC_NOWAIT)
EACCES(沒有寫的權限)
EFAULT(msgp地址無效)
EIDRM(消息隊列已經刪除)
EINTR(當等待寫操作時,收到一個信號)
EINVAL(無效的消息隊列標識符,非正數的消息類型,或
者無效的消息長度)
ENOMEM(沒有足夠的內存複製消息緩衝區)
    系統調用msgsnd()的第一個參數是消息隊列標識符,它是由系統調用msgget返回的。第二個參數是msgp,是指向消息緩衝區的指針。參數msgsz中包含的是消息的字節大小,但不包括消息類型的長度(4個字節)。
    參數msgflg可以設置爲0(此時爲忽略此參數),或者使用IPC_NOWAIT。
    如果消息隊列已滿,那麼此消息則不會寫入到消息隊列中,控制將返回到調用進程中。如果沒有指明,調用進程將會掛起,直到消息可以寫入到隊列中。
    下面是一個發送消息的程序:
intsend_message(int qid,struct mymsgbuf *qbuf)
{
intresult,length;
/*The length is essentially the size of the structure minus sizeof(mtype)*/
length=sizeof(structmymsgbuf)-sizeof(long);
if((result=msgsnd(qid,qbuf,length,0))==-1)
{
return(-1);
}
return(result);
}
    這個小程序試圖將存儲在緩衝區qbuf中的消息發送到消息隊列qid中。下面的程序是結合了上面兩個程序的一個完整程序:
#include<stdio.h>
#include<stdlib.h>
#include<linux/ipc.h>
#include<linux/msg.h>
main()
{
intqid;
key_t msgkey;
struct mymsgbuf{
longmtype;/*Message type*/
intrequest;/*Work request number*/
doublesalary;/*Employee's salary*/
}msg;
/*Generateour IPC key value*/
msgkey=ftok(".",'m');
/*Open/createthequeue*/
if((qid=open_queue(msgkey))==-1){
perror("open_queue");
exit(1);
}
/*Load up the message with a r bitrary test data*/
msg.mtype=1;/*Messagetypemustbeapositivenumber!*/
msg.request=1;/*Dataelement#1*/
msg.salary=1000.00;/*Data element #2(my yearly salary!)*/
/*Bombsaway!*/
if((send_message(qid,&msg))==-1){
perror("send_message");
exit(1);
}
}
   
在創建和打開消息隊列以後,我們將測試數據裝入到消息緩衝區中。最後調用send_messag把消息發送到消息隊列中。現在在消息隊列中有了一條消息,我們可以使用ipcs命令來查看隊列的狀態。下面討論如何從隊列中獲取消息。可以使用系統調用msgrcv():


msgrcv()

系統調用:msgrcv();
原型:intmsgrcv(intmsqid,structmsgbuf*msgp,intmsgsz,longmtype,intmsgflg);
返回值:如果成功,則返回複製到消息緩衝區的字節數。
如果失敗,則返回-1:errno=E2BIG(消息的長度大於msgsz,沒有MSG_NOERROR)
EACCES(沒有讀的權限)
EFAULT(msgp指向的地址是無效的)
EIDRM(隊列已經被刪除)
EINTR(被信號中斷)
EINVAL(msgqid無效,或者msgsz小於0)
ENOMSG(使用IPC_NOWAIT,同時隊列中的消息無法滿足要求)
    很明顯,第一個參數用來指定將要讀取消息的隊列。第二個參數代表要存儲消息的消息緩衝區的地址。第三個參數是消息緩衝區的長度,不包括mtype的長度,它可以按照如下的方法計算:
        msgsz=sizeof(structmymsgbuf)-sizeof(long);
    第四個參數是要從消息隊列中讀取的消息的類型。如果此參數的值爲0,那麼隊列中最長時間的一條消息將返回,而不論其類型是什麼。
如果調用中使用了IPC_NOWAIT作爲標誌,那麼當沒有數據可以使用時,調用將把ENOMSG返回到調用進程中。否則,調用進程將會掛起,直到隊列中的一條消息滿足msgrcv()的參數要求。如果當客戶端等待一條消息的時候隊列爲空,將會返回EIDRM。如果進程在等待消息的過程中捕捉到一個信號,則返回EINTR。
    下面就是一個從隊列中讀取消息的程序:
intread_message(int qid,long type,struct mymsgbuf*qbuf)
{
intresult,length;
/*The length is essentially the size of the structure minus sizeof(mtype)*/
length=sizeof(structmymsgbuf)-sizeof(long);
if((result=msgrcv(qid,qbuf,length,type,0))==-1)
{
return(-1);
}
return(result);
}
   
在成功地讀取了一條消息以後,隊列中的這條消息的入口將被刪除。
    參數msgflg中的MSG_NOERROR位提供一種額外的用途。如果消息的實際長度大於msgsz,同時使用了MSG_NOERROR,那麼消息將會被截斷,只有與msgsz長度相等的消息返回。一般情況下,系統調用msgrcv()會返回-1,而這條消息將會繼續保存在隊列中。我們可以利用這個特點編制一個程序,利用這個程序可以查看消息隊列的情況,看看符合我們條件的消息是否已經到來:
intpeek_message(int qid,long type)
{
intresult,length;
if((result=msgrcv(qid,NULL,0,type,IPC_NOWAIT))==-1)
{
if(errno==E2BIG)
return(TRUE);
}
return(FALSE);
}
   
在上面的程序中,我們忽略了緩衝區的地址和長度。這樣,系統調用將會失敗。儘管如此,我們可以檢查返回的E2BIG值,它說明符合條件的消息確實存在。


msgctl()

系統調用msgctl()
    下面我們繼續討論如何使用一個給定的消息隊列的內部數據結構。我們可以使用系統調用msgctl ( )來控制對消息隊列的操作。
系統調用: msgctl( ) ;
調用原型: int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );
返回值: 0 ,如果成功。
- 1,如果失敗:errno = EACCES (沒有讀的權限同時cmd 是IPC_STAT )
EFAULT (buf 指向的地址無效)
EIDRM (在讀取中隊列被刪除)
EINVAL (msgqid無效, 或者msgsz 小於0 )
EPERM (IPC_SET或者IPC_RMID 命令被使用,但調用程序沒有寫的權限)
下面我們看一下可以使用的幾個命令:
IPC_STAT
讀取消息隊列的數據結構msqid_ds,並將其存儲在b u f指定的地址中。
IPC_SET
設置消息隊列的數據結構msqid_ds中的ipc_perm元素的值。這個值取自buf參數。
IPC_RMID
從系統內核中移走消息隊列。
    我們在前面討論過了消息隊列的數據結構(msqid_ds)。系統內核中爲系統中的每一個消息隊列保存一個此數據結構的實例。通過使用IPC_STAT命令,我們可以得到一個此數據結構的副本。下面的程序就是實現此函數的過程:
int get_queue_ds( int qid, struct msgqid_ds *qbuf )
{
if( msgctl( qid, IPC_STAT, qbuf) == -1)
{
return(-1);
}
return(0);
}
    如果不能複製內部緩衝區,調用進程將返回-1。如果調用成功,則返回0。緩衝區中應該包括消息隊列中的數據結構。
    消息隊列中的數據結構中唯一可以改動的元素就是ipc_perm。它包括隊列的存取權限和關於隊列創建者和擁有者的信息。你可以改變用戶的id、用戶的組id以及消息隊列的存取權限。
    下面是一個修改隊列存取模式的程序:
int change_queue_mode(int qid, char *mode )
{
struct msqid_ds tmpbuf;
/* Retrieve a current copy of the internal data structure */
get_queue_ds( qid, &tmpbuf);
/* Change the permissions using an old trick */
sscanf(mode, "%ho", &tmpbuf.msg_perm.mode);
/* Update the internal data structure */
if( msgctl( qid, IPC_SET, &tmpbuf) == -1)
{
return(-1);
}
return(
}
    我們通過調用get_queue_ds來讀取隊列的內部數據結構。然後,我們調用sscanf( )修改數據結構msg_perm中的mode 成員的值。但直到調用msgctl()時,權限的改變才真正完成。在這裏msgctl()使用的是IPC_SET命令。
    最後,我們使用系統調用msgctl ( )中的IPC_RMID命令刪除消息隊列:
int remove_queue(int qid )
{
if( msgctl( qid, IPC_RMID, 0) == -1)
{
return(-1);
}
return(0);
}
};


信號量

   
信號量是一個可以用來控制多個進程存取共享資源的計數器。它經常作爲一種鎖定機制來防止當一個進程正在存取共享資源時,另一個進程也存取同一資源。下面先簡要地介紹一下信號量中涉及到的數據結構。
1.內核中的數據結構semid_ds
和消息隊列一樣,系統內核爲內核地址空間中的每一個信號量集都保存了一個內部的數據結構。數據結構的原型是semid_ds。它是在linux/sem.h中做如下定義的:
/*One semid data structure for each set of semaphores in the system.*/
structsemid_ds{
structipc_permsem_perm;/*permissions..seeipc.h*/
time_tsem_otime;/*last semop time*/
time_tsem_ctime;/*last change time*/
structsem*sem_base;/*ptr to first semaphore in array*/
structwait_queue*eventn;
structwait_queue*eventz;
structsem_undo*undo;/*undo requestson this array*/
ushortsem_nsems;/*no. of semaphores in array*/
};
sem_perm是在linux/ipc.h定義的數據結構ipc_perm的一個實例。它保存有信號量集的存取權限的信息,以及信號量集創建者的有關信息。
sem_otime最後一次semop()操作的時間。
sem_ctime最後一次改動此數據結構的時間。
sem_base指向數組中第一個信號量的指針。
sem_undo數組中沒有完成的請求的個數。
sem_nsems信號量集(數組)中的信號量的個數。
2.內核中的數據結構sem
在數據結構semid_ds中包含一個指向信號量數組的指針。此數組中的每一個元素都是一個
數據結構sem。它也是在linux/sem.h中定義的:
/*One semaphore structure for each semaphore in the system.*/
structsem{
shortsempid;/*pid of las toperation*/
ushortsemval;/*current value*/
ushortsemncnt;/*num procs awaiting increase in semval*/
ushortsemzcnt;/*num procs awaiting semval=0*/
};
sem_pid最後一個操作的PID(進程ID)。
sem_semval信號量的當前值。
sem_semncnt等待資源的進程數目。
sem_semzcnt等待資源完全空閒的進程數目。


semget()

   
我們可以使用系統調用semget()創建一個新的信號量集,或者存取一個已經存在的信號量集:
系統調用:semget();
原型:intsemget(key_t key,int nsems,int semflg);
返回值:如果成功,則返回信號量集的IPC標識符。如果失敗,則返回-1:errno=EACCESS(沒有權限)
EEXIST(信號量集已經存在,無法創建)
EIDRM(信號量集已經刪除)
ENOENT(信號量集不存在,同時沒有使用IPC_CREAT)
ENOMEM(沒有足夠的內存創建新的信號量集)
ENOSPC(超出限制)
    系統調用semget()的第一個參數是關鍵字值(一般是由系統調用ftok()返回的)。系統內核將此值和系統中存在的其他的信號量集的關鍵字值進行比較。打開和存取操作與參數semflg中的內容相關。IPC_CREAT如果信號量集在系統內核中不存在,則創建信號量集。IPC_EXCL當和IPC_CREAT一同使用時,如果信號量集已經存在,則調用失敗。如果單獨使用IPC_CREAT,則semget()要麼返回新創建的信號量集的標識符,要麼返回系統中已經存在的同樣的關鍵字值的信號量的標識符。如果IPC_EXCL和IPC_CREAT一同使用,則要麼返回新創建的信號量集的標識符,要麼返回-1。IPC_EXCL單獨使用沒有意義。參數nsems指出了一個新的信號量集中應該創建的信號量的個數。信號量集中最多的信號量的個數是在linux/sem.h中定義的:
#defineSEMMSL32/*<=512maxnumofsemaphoresperid*/
下面是一個打開和創建信號量集的程序:
intopen_semaphore_set(key_t keyval,int numsems)
{
intsid;
if(!numsems)
return(-1);
if((sid=semget(mykey,numsems,IPC_CREAT|0660))==-1)
{
return(-1);
}
return(sid);
}
};
 


semop()

系統調用:semop();
調用原型:int semop(int semid,struct sembuf*sops,unsign ednsops);
返回值:0,如果成功。-1,如果失敗:errno=E2BIG(nsops大於最大的ops數目)
EACCESS(權限不夠)
EAGAIN(使用了IPC_NOWAIT,但操作不能繼續進行)
EFAULT(sops指向的地址無效)
EIDRM(信號量集已經刪除)
EINTR(當睡眠時接收到其他信號)
EINVAL(信號量集不存在,或者semid無效)
ENOMEM(使用了SEM_UNDO,但無足夠的內存創建所需的數據結構)
ERANGE(信號量值超出範圍)
    第一個參數是關鍵字值。第二個參數是指向將要操作的數組的指針。第三個參數是數組中的操作的個數。參數sops指向由sembuf組成的數組。此數組是在linux/sem.h中定義的:
/*semop systemcall takes an array of these*/
structsembuf{
ushortsem_num;/*semaphore index in array*/
shortsem_op;/*semaphore operation*/
shortsem_flg;/*operation flags*/
sem_num
將要處理的信號量的個數。
sem_op要執行的操作。
sem_flg操作標誌。
    如果sem_op是負數,那麼信號量將減去它的值。這和信號量控制的資源有關。如果沒有使用IPC_NOWAIT,那麼調用進程將進入睡眠狀態,直到信號量控制的資源可以使用爲止。如果sem_op是正數,則信號量加上它的值。這也就是進程釋放信號量控制的資源。最後,如果sem_op是0,那麼調用進程將調用sleep(),直到信號量的值爲0。這在一個進程等待完全空閒的資源時使用。
 


semctl()

系統調用:semctl();
原型:int semctl(int semid,int semnum,int cmd,union semunarg);
返回值:如果成功,則爲一個正數。
如果失敗,則爲-1:errno=EACCESS(權限不夠)
EFAULT(arg指向的地址無效)
EIDRM(信號量集已經刪除)
EINVAL(信號量集不存在,或者semid無效)
EPERM(EUID沒有cmd的權利)
ERANGE(信號量值超出範圍)
    系統調用semctl用來執行在信號量集上的控制操作。這和在消息隊列中的系統調用msgctl是十分相似的。但這兩個系統調用的參數略有不同。因爲信號量一般是作爲一個信號量集使用的,而不是一個單獨的信號量。所以在信號量集的操作中,不但要知道IPC關鍵字值,也要知道信號量集中的具體的信號量。這兩個系統調用都使用了參數cmd,它用來指出要操作的具體命令。兩個系統調用中的最後一個參數也不一樣。在系統調用msgctl中,最後一個參數是指向內核中使用的數據結構的指針。我們使用此數據結構來取得有關消息隊列的一些信息,以及設置或者改變隊列的存取權限和使用者。但在信號量中支持額外的可選的命令,這樣就要求有一個更爲複雜的數據結構。
系統調用semctl()的第一個參數是關鍵字值。第二個參數是信號量數目。
    參數cmd中可以使用的命令如下:
    ·IPC_STAT讀取一個信號量集的數據結構semid_ds,並將其存儲在semun中的buf參數中。
    ·IPC_SET設置信號量集的數據結構semid_ds中的元素ipc_perm,其值取自semun中的buf參數。
    ·IPC_RMID將信號量集從內存中刪除。
    ·GETALL用於讀取信號量集中的所有信號量的值。
    ·GETNCNT返回正在等待資源的進程數目。
    ·GETPID返回最後一個執行semop操作的進程的PID。
    ·GETVAL返回信號量集中的一個單個的信號量的值。
    ·GETZCNT返回這在等待完全空閒的資源的進程數目。
    ·SETALL設置信號量集中的所有的信號量的值。
    ·SETVAL設置信號量集中的一個單獨的信號量的值。
    參數arg代表一個semun的實例。semun是在linux/sem.h中定義的:
/*arg for semctl systemcalls.*/
unionsemun{
intval;/*value for SETVAL*/
structsemid_ds*buf;/*buffer for IPC_STAT&IPC_SET*/
ushort*array;/*array for GETALL&SETALL*/
structseminfo*__buf;/*buffer for IPC_INFO*/
void*__pad;
    val當執行SETVAL命令時使用。buf在IPC_STAT/IPC_SET命令中使用。代表了內核中使用的信號量的數據結構。array在使用GETALL/SETALL命令時使用的指針。
    下面的程序返回信號量的值。當使用GETVAL命令時,調用中的最後一個參數被忽略:
intget_sem_val(intsid,intsemnum)
{
return(semctl(sid,semnum,GETVAL,0));
}
    下面是一個實際應用的例子:
#defineMAX_PRINTERS5
printer_usage()
{
int x;
for(x=0;x<MAX_PRINTERS;x++)
printf("Printer%d:%d/n/r",x,get_sem_val(sid,x));
}
    下面的程序可以用來初始化一個新的信號量值:
void init_semaphore(int sid,int semnum,int initval)
{
union semunsemopts;
semopts.val=initval;
semctl(sid,semnum,SETVAL,semopts);
}
    注意系統調用semctl中的最後一個參數是一個聯合類型的副本,而不是一個指向聯合類型的指針。


共享內存

   
共享內存就是由幾個進程共享一段內存區域。這可以說是最快的IPC形式,因爲它無須任何的中間操作(例如,管道、消息隊列等)。它只是把內存段直接映射到調用進程的地址空間中。這樣的內存段可以是由一個進程創建的,然後其他的進程可以讀寫此內存段。
    每個系統的共享內存段在系統內核中也保持着一個內部的數據結構shmid_ds。此數據結構是在linux/shm.h中定義的,如下所示:
/* One shmid data structure for each shared memory segment in the system. */
struct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment (bytes) */
time_t shm_atime; /* last attach time */
time_t shm_dtime; /* last detach time */
time_t shm_ctime; /* last change time */
unsigned short shm_cpid; /* pid of creator */
unsigned short shm_lpid; /* pid of last operator */
short shm_nattch; /* no. of current attaches */
/* the following are private */
unsigned short shm_npages; /* size of segment (pages) */
unsigned long *shm_pages; /* array of ptrs to frames -> SHMMAX */
struct vm_area_struct *attaches; /* descriptors for attaches */
};
shm_perm 是數據結構ipc_perm的一個實例。這裏保存的是內存段的存取權限,和其他的有關內存段創建者的信息。
shm_segsz 內存段的字節大小。
shm_atime 最後一個進程存取內存段的時間。
shm_dtime 最後一個進程離開內存段的時間。
shm_ctime 內存段最後改動的時間。
shm_cpid 內存段創建進程的P I D。
shm_lpid 最後一個使用內存段的進程的P I D。
shm_nattch 當前使用內存段的進程總數。


shmget()

系統調用:shmget();
原型:int shmget(key_t key,int size,int shmflg);
返回值:如果成功,返回共享內存段標識符。如果失敗,則返回-1:errno=EINVAL(無效的內存段大小)
EEXIST(內存段已經存在,無法創建)
EIDRM(內存段已經被刪除)
ENOENT(內存段不存在)
EACCES(權限不夠)
ENOMEM(沒有足夠的內存來創建內存段)
    系統調用shmget()中的第一個參數是關鍵字值(它是用系統調用ftok()返回的)。其他的操作都要依據shmflg中的命令進行。
    ·IPC_CREAT如果系統內核中沒有共享的內存段,則創建一個共享的內存段。
    ·IPC_EXCL當和IPC_CREAT一同使用時,如果共享內存段已經存在,則調用失敗。
    當IPC_CREAT單獨使用時,系統調用shmget()要麼返回一個新創建的共享內存段的標識符,要麼返回一個已經存在的共享內存段的關鍵字值。如果IPC_EXCL和IPC_CREAT一同使用,則要麼系統調用新創建一個共享的內存段,要麼返回一個錯誤值-1。IPC_EXCL單獨使用沒有意義。
    下面是一個定位和創建共享內存段的程序:
int open_segment(key_t keyval,int segsize)
{
int shmid;
if((shmid=shmget(keyval,segsize,IPC_CREAT|0660))==-1)
{
return(-1);
}
return(shmid);
}
    一旦一個進程擁有了一個給定的內存段的有效IPC標識符,它的下一步就是將共享的內存段映射到自己的地址空間中。


shmat()

系統調用: shmat();
原型:int shmat ( int shmid, char *shmaddr, int shmflg);
返回值:如果成功,則返回共享內存段連接到進程中的地址。如果失敗,則返回- 1:errno = EINVAL (無效的IPC ID 值或者無效的地址)
ENOMEM (沒有足夠的內存)
EACCES (存取權限不夠)
    如果參數a d d r的值爲0,那麼系統內核則試圖找出一個沒有映射的內存區域。我們推薦使用這種方法。你可以指定一個地址,但這通常是爲了加快對硬件設備的存取,或者解決和其他程序的衝突。
    下面的程序中的調用參數是一個內存段的I P C標識符,返回內存段連接的地址:
char *attach_segment(int shmid)
{
return(shmat(shmid, 0, 0));
}
    一旦內存段正確地連接到進程以後,進程中就有了一個指向該內存段的指針。這樣,以後就可以使用指針來讀取此內存段了。但一定要注意不能丟失該指針的初值。


shmctl()

系統調用:shmctl ( ) ;
原型:int shmctl( int shmqid, int cmd, struct shmid_ds *buf );
返回值: 0 ,如果成功。
-1,如果失敗:errno = EACCES (沒有讀的權限,同時命令是IPC_STAT)
EFAULT(buf指向的地址無效,同時命令是IPC_SET和IPC_STAT )
EIDRM (內存段被移走)
EINVAL (shmqid 無效)
EPERM (使用IPC_SET 或者IPC_RMID 命令,但調用進程沒有寫的權限)
IPC_STAT 讀取一個內存段的數據結構shmid_ds,並將它存儲在buf參數指向的地址中。
IPC_SET 設置內存段的數據結構shmid_ds中的元素ipc_perm的值。從參數buf中得到要設置的值。
IPC_RMID 標誌內存段爲移走。
    命令IPC_RMID並不真正從系統內核中移走共享的內存段,而是把內存段標記爲可移除。進程調用系統調用shmdt()脫離一個共享的內存段。


shmdt()

系統調用:shmdt();
調用原型:int shmdt ( char *shmaddr );
返回值:如果失敗,則返回- 1:errno = EINVAL (無效的連接地址)
    當一個進程不在需要共享的內存段時,它將會把內存段從其地址空間中脫離。但這不等於將共享內存段從系統內核中移走。當進程脫離成功後,數據結構shmid_ds中元素shm_nattch將減1。當此數值減爲0以後,系統內核將物理上把內存段從系統內核中移走。


線程

   
線程通常叫做輕型的進程。雖然這個叫法有些簡單化,但這有利於瞭解線程的概念。因爲線程和進程比起來很小,所以相對來說,線程花費更少的CPU資源。進程往往需要它們自己的資源,但線程之間可以共享資源,所以線程更加節省內存。Mach的線程使得程序員可以編寫併發運行的程序,而這些程序既可以運行在單處理器的機器上,也可以運行在多處理器的機器中。另外,在單處理器環境中,當應用程序執行容易引起阻塞和延遲的操作時,線程可以提高效率。
    用子函數pthread_create創建一個新的線程。它有四個參數:一個用來保存線程的線程變量、一個線程屬性、當線程執行時要調用的函數和一個此函數的參數。例如:
pthread_ta_thread ;
pthread_attr_ta_thread_attribute ;
void thread_function(void *argument);
char * some_argument;
pthread_create( &a_thread, a_thread_attribute, (void *)&thread_function,
(void *) &some_argument);
    線程屬性只指明瞭需要使用的最小的堆棧大小。在以後的程序中,線程的屬性可以指定其他的值,但現在大部分的程序可以使用缺省值。不像UNIX系統中使用fork系統調用創建的進程,它們和它們的父進程使用同一個執行點,線程使用在pthread_create中的參數指明要開始執行的函數。
    現在我們可以編制第一個程序了。我們編制一個多線程的應用程序,在標準輸出中打印“Hello Wo r l d”。首先我們需要兩個線程變量,一個新線程開始執行時可以調用的函數。我們還需要指明每一個線程應該打印的信息。一個做法是把要打印的字符串分開,給每一個線程一個字符串作爲開始的參數。請看下面的代碼:
void print_message_function( void *ptr );
main( )
{
pthread_t thread1, thread2;
char *message1 = "Hello";
char *message2 = "Wo r l d " ;
pthread_create( &thread1, pthread_attr_default,
(void*)&print_message_function, (void*) message1);
pthread_create(&thread2, pthread_attr_default,
(void*)&print_message_function, (void*) message2);
exit( 0 ) ;
}
void print_message_function( void *ptr )
{
char *message;
message = (char *) ptr;
printf("%s ", message);
}
    程序通過調用pthread_create創建第一個線程,並將“Hello”作爲它的啓動參數。第二個線程的參數是“World”。當第一個線程開始執行時,它使用參數“Hello”執行函數print_message_function。它在標準輸出中打印“Hello”,然後結束對函數的調用。線程當離開它的初始化函數時就將終止,所以第一個線程在打印完“Hello”後終止。當第二個線程執行時,它打印“World”然後終止。但這個程序有兩個主要的缺陷。
    首先也是最重要的是線程是同時執行的。這樣就無法保證第一個線程先執行打印語句。所以你很可能在屏幕上看到“World Hello”,而不是“Hello World”。請注意對exit的調用是父線程在主程序中使用的。這樣,如果父線程在兩個子線程調用打印語句之前調用exit,那麼將不會有任何的打印輸出。這是因爲exit函數將會退出進程,同時釋放任務,所以結束了所有的線程。任何線程(不論是父線程或者子線程)調用exit 都會終止所有其他線程。如果希望線程分別終止,可以使用pthread_exit函數。
我們可以使用一個辦法彌補此缺陷。我們可以在父線程中插入一個延遲程序,給子線程足夠的時間完成打印的調用。同樣,在調用第二個之前也插入一個延遲程序保證第一個線程在第二個線程執行之前完成任務。
void print_message_function( void *ptr );
main ( )
{
pthread_t thread1, thread2;
char *message1 = "Hello
”;
char *message2 = "Wo r l d " ;
pthread_create( &thread1, pthread_attr_default,
(void *) &print_message_function, (void *) message1);
sleep (10) ;
pthread_create(&thread2, pthread_attr_default,
(void *) &print_message_function, (void *) message2);
sleep ( 10 ) ;
exit (0) ;
}
void print_message_function( void *ptr )
{
char *message;
message = (char *) ptr;
printf("%s", message);
pthread_exit(0) ;
}
    這樣是否達到了我們的要求了呢?不盡如此,因爲依靠時間的延遲執行同步是不可靠的。這裏遇到的情形和一個分佈程序和共享資源的情形一樣。共享的資源是標準的輸出設備,分佈計算的程序是三個線程。
其實這裏還有另外一個錯誤。函數sleep和函數e x i t一樣和進程有關。當線程調用sleep時,整個的進程都處於睡眠狀態,也就是說,所有的三個線程都進入睡眠狀態。這樣我們實際上沒有解決任何的問題。希望使一個線程睡眠的函數是pthread_delay_np。例如讓一個線程睡眠2秒鐘,用如下程序:
struct timespec delay;
delay.tv_sec = 2;
delay.tv_nsec = 0;
pthread_delay_np( &delay );
}


線程同步

POSIX
提供兩種線程同步的方法,mutex和條件變量。mutex是一種簡單的加鎖的方法來控制對共享資源的存取。我們可以創建一個讀/寫程序,它們共用一個共享緩衝區,使用mutex來控制對緩衝區的存取。
void reader_function(void);
void writer_function(void);
char buf f e r ;
int buffer_has_item = 0;
pthread_mutex_t mutex;
struct timespec delay;
main( )
{
pthread_t reader;
delay.tv_sec = 2;
delay.tv_nsec = 0;
pthread_mutex_init(&mutex, pthread_mutexattr_default);
pthread_create( &reader, pthread_attr_default, (void*)&reader_function,
N U L L ) ;
writer_function( )
void writer_function(void)
{
while( 1 )
{
pthread_mutex_lock( &mutex );
if ( buffer_has_item == 0 )
{
buffer = make_new_item();
buffer_has_item = 1;
}
pthread_mutex_unlock( &mutex );
pthread_delay_np( &delay );
}
}
void reader_function(void)
{
while( 1 )
{
pthread_mutex_lock( &mutex );
if ( buffer_has_item == 1)
{
consume_item( buffer );
buffer_has_item = 0;
}
pthread_mutex_unlock( &mutex );
pthread_delay_np( &delay );
}
}
在上面的程序中,我們假定緩衝區只能保存一條信息,這樣緩衝區只有兩個狀態,有一條信息或者沒有信息。使用延遲是爲了避免一個線程永遠佔有mutex。
但mutex的缺點在於它只有兩個狀態,鎖定和非鎖定。POSIX的條件變量通過允許線程阻塞和等待另一個線程的信號方法,從而彌補了mutex的不足。當接受到一個信號時,阻塞線程將會被喚起,並試圖獲得相關的mutex的鎖。


使用信號量協調程序

我們可以使用信號量重新看一下上面的讀/寫程序。涉及信號量的操作是semaphore_up、semaphore_down、semaphore_init、semaphore_destroy和semaphore_decrement。所有這些操作都只有一個參數,一個指向信號量目標的指針。
void reader_function(void);
void writer_function(void);
char buffer ;
Semaphore writers_turn;
Semaphore readers_turn;
main( )
{
pthread_t reader;
semaphore_init( &readers_turn );
semaphore_init( &writers_turn );
/* writer must go first */
semaphore_down( &readers_turn );
pthread_create( &reader, pthread_attr_default,
(void *)&reader_function, NULL);
w r i t e r _ f u n c t i o n ( ) ;
}
void writer_function(void)
{
w h i l e ( 1 )
{
semaphore_down( &writers_turn );
b u ffer = make_new_item();
semaphore_up( &readers_turn );
}
}
void reader_function(void)
{
w h i l e ( 1 )
{
semaphore_down( &readers_turn );
consume_item( buffer );
semaphore_up( &writers_turn );
}
}
這個例子也沒有完全地利用一般信號量的所有函數。我們可以使用信號量重新編寫“Hello world” 的程序:
void print_message_function( void *ptr );
Semaphore child_counter;
Semaphore worlds_turn;
main( )
{
pthread_t thread1, thread2;
char *message1 = "Hello";
char *message2 = "Wo r l d " ;
semaphore_init( &child_counter );
semaphore_init( &worlds_turn );
semaphore_down( &worlds_turn ); /* world goes second */
semaphore_decrement( &child_counter ); /* value now 0 */
semaphore_decrement( &child_counter ); /* value now -1 */
/*
* child_counter now must be up-ed 2 times for a thread blocked on it
* to be released
*
* /
pthread_create( &thread1, pthread_attr_default,
(void *) &print_message_function, (void *) message1);
semaphore_down( &worlds_turn );
pthread_create(&thread2, pthread_attr_default,
(void *) &print_message_function, (void *) message2);
semaphore_down( &child_counter );
/* not really necessary to destroy since we are exiting anyway */
semaphore_destroy ( &child_counter );
semaphore_destroy ( &worlds_turn );
e x i t ( 0 ) ;
}
void print_message_function( void *ptr )
{
char *message;
message = (char *) ptr;
printf("%s ", message);
fflush(stdout);
semaphore_up( &worlds_turn );
semaphore_up( &child_counter );
p t h r e a d _ e x i t ( 0 ) ;
}
信號量c h i l d _ c o u n t e r用來強迫父線程阻塞,直到兩個子線程執行完p r i n t f語句和其後的semaphore_up( &child_counter )語句才繼續執行。
Semaphore.h
#ifndef SEMAPHORES
#define SEMAPHORES
#include
#include
typedef struct Semaphore
{
int v;
pthread_mutex_t mutex;
pthread_cond_t cond;
}
S e m a p h o r e ;
int semaphore_down (Semaphore * s);
int semaphore_decrement (Semaphore * s);
int semaphore_up (Semaphore * s);
void semaphore_destroy (Semaphore * s);
void semaphore_init (Semaphore * s);
int semaphore_value (Semaphore * s);
int tw_pthread_cond_signal (pthread_cond_t * c);
int tw_pthread_cond_wait (pthread_cond_t * c, pthread_mutex_t * m);
int tw_pthread_mutex_unlock (pthread_mutex_t * m);
int tw_pthread_mutex_lock (pthread_mutex_t * m);
void do_error (char *msg);
# e n d i f
Semaphore.c
#include "semaphore.h"
/ *
* function must be called prior to semaphore use.
*
* /
v o i d
semaphore_init (Semaphore * s)
{
s->v = 1;
if (pthread_mutex_init (&(s->mutex), pthread_mutexattr_default) == -1)
do_error ("Error setting up semaphore mutex");
if (pthread_cond_init (&(s->cond), pthread_condattr_default) == -1)
do_error ("Error setting up semaphore condition signal");
* function should be called when there is no longer a need for
* the semaphore.
*
* /
v o i d
semaphore_destroy (Semaphore * s)
{
if (pthread_mutex_destroy (&(s->mutex)) == -1)
do_error ("Error destroying semaphore mutex");
if (pthread_cond_destroy (&(s->cond)) == -1)
do_error ("Error destroying semaphore condition signal");
}
/ *
* function increments the semaphore and signals any threads that
* are blocked waiting a change in the semaphore.
*
* /
i n t
semaphore_up (Semaphore * s)
{
int value_after_op;
tw_pthread_mutex_lock (&(s->mutex));
( s - > v ) + + ;
value_after_op = s->v;
tw_pthread_mutex_unlock (&(s->mutex));
tw_pthread_cond_signal (&(s->cond));
return (value_after_op);
}
/ *
* function decrements the semaphore and blocks if the semaphore is
* <= 0 until another thread signals a change.
*
* /
i n t
semaphore_down (Semaphore * s)
{
int value_after_op;
tw_pthread_mutex_lock (&(s->mutex));
while (s->v <= 0)
{
tw_pthread_cond_wait (&(s->cond), &(s->mutex));
}
( s - > v ) - - ;
value_after_op = s->v;
tw_pthread_mutex_unlock (&(s->mutex));
return (value_after_op);
}
/ *
* function does NOT block but simply decrements the semaphore.
* should not be used instead of down -- only for programs where
* multiple threads must up on a semaphore before another thread
* can go down, i.e., allows programmer to set the semaphore to
* a negative value prior to using it for synchronization.
*
* /
i n t
semaphore_decrement (Semaphore * s)
{
int value_after_op;
tw_pthread_mutex_lock (&(s->mutex));
s - > v - - ;
value_after_op = s->v;
tw_pthread_mutex_unlock (&(s->mutex));
return (value_after_op);
}
/ *
* function returns the value of the semaphore at the time the
* critical section is accessed. obviously the value is not guarenteed
* after the function unlocks the critical section. provided only
* for casual debugging, a better approach is for the programmar to
* protect one semaphore with another and then check its value.
* an alternative is to simply record the value returned by semaphore_up
* or semaphore_down.
*
* /
i n t
semaphore_value (Semaphore * s)
{
/* not for sync */
int value_after_op;
tw_pthread_mutex_lock (&(s->mutex));
value_after_op = s->v;
tw_pthread_mutex_unlock (&(s->mutex));
return (value_after_op);
}
/* -------------------------------------------------------------------- */
/* The following functions replace standard library functions in that */
/* they exit on any error returned from the system calls. Saves us */
/* from having to check each and every call above. */
/* -------------------------------------------------------------------- */
i n t
tw_pthread_mutex_unlock (pthread_mutex_t * m)
{
int return_value;
if ((return_value = pthread_mutex_unlock (m)) == -1)
do_error ("pthread_mutex_unlock");
return (return_value);
}
i n t
tw_pthread_mutex_lock (pthread_mutex_t * m)
{
int return_value;
if ((return_value = pthread_mutex_lock (m)) == -1)
do_error ("pthread_mutex_lock");
return (return_value);
}
i n t
tw_pthread_cond_wait (pthread_cond_t * c, pthread_mutex_t * m)
{
int return_value;
if ((return_value = pthread_cond_wait (c, m)) == -1)
do_error ("pthread_cond_wait");
return (return_value);
}
i n t
tw_pthread_cond_signal (pthread_cond_t * c)
{
int return_value;
if ((return_value = pthread_cond_signal (c)) == -1)
do_error ("pthread_cond_signal");
return (return_value);
}
/ *
* function just prints an error message and exits
*
* /
v o i d
do_error (char *msg)
{
perror (msg);
exit (1);
}


代碼例子
 


newthread

/***********************************************************************
    Case study source code from the book `The Linux A to Z'
    by Phil Cornes. Published by Prentice Hall, 1996.
    Copyright (C) 1996 Phil Cornes
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
***********************************************************************/
new_thread(int (*start_addr)(void), int stack_size)
{
    struct context *ptr;
    int esp;
    /* 1 */
    if (!(ptr = (struct context *)malloc(sizeof(struct context))))
        return 0;
    /* 2 */
    if (!(ptr->stack = (char *)malloc(stack_size)))
        return 0;
    /* 3 */
    esp = (int)(ptr->stack+(stack_size-4));
    *(int *)esp = (int)exit_thread;
    *(int *)(esp-4) = (int)start_addr;
    *(int *)(esp-8) = esp-4;
    ptr->ebp = esp-8;
    /* 4 */
    if (thread_count++)
    {
        /* 5 */
        ptr->next = current->next;
        ptr->prev = current;
        current->next->prev = ptr;
        current->next = ptr;
    }
    else
    {
        /* 6 */
        ptr->next = ptr;
        ptr->prev = ptr;
        current = ptr;
        switch_context(&main_thread, current);
    }
    return 1;
}


exitthead

/***********************************************************************
    Case study source code from the book `The Linux A to Z'
    by Phil Cornes. Published by Prentice Hall, 1996.
    Copyright (C) 1996 Phil Cornes
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
***********************************************************************/
static exit_thread(void)
{
    struct context dump, *ptr;
    /* 1 */
    if (--thread_count)
    {
        /* 2 */
        ptr = current;
        current->prev->next = current->next;
        current->next->prev = current->prev;
        current = current->next;
        free(ptr->stack);
        free(ptr);
        switch_context(&dump, current);
    }
    else
    {
        /* 3 */
        free(current->stack);
        free(current);
        switch_context(&dump, &main_thread);
    }
}


getchannel

/***********************************************************************
    Case study source code from the book `The Linux A to Z'
    by Phil Cornes. Published by Prentice Hall, 1996.
    Copyright (C) 1996 Phil Cornes
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
***********************************************************************/
get_channel(int number)
{
    struct channel *ptr;
    /* 1 */
    for (ptr = channel_list; ptr; ptr = ptr->link)
        if (ptr->number==number)
            return((int)ptr);
    /* 2 */
    if (!(ptr = (struct channel *)malloc(sizeof(struct channel))))
        return 0;
    /* 3 */
    ptr->number = number;
    ptr->message_list = 0;
    ptr->message_tail = 0;
    ptr->sr_flag = 0;
    ptr->link = channel_list;
    channel_list = ptr;
    return((int)ptr);
}


def

/***********************************************************************
    Case study source code from the book `The Linux A to Z'
    by Phil Cornes. Published by Prentice Hall, 1996.
    Copyright (C) 1996 Phil Cornes
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
***********************************************************************/
#include <string.h>
struct context                     /* One structure for each thread */
{
    int ebp;            /* Base pointer (stack frame pointer) store */
    char *stack;        /* Pointer to memory block for thread stack */
    struct context *next;      /* Round robin circular list pointer */
    struct context *prev;      /* Round robin circular list pointer */
};
struct channel      /* One structure for each communication channel */
{
    int number;                                   /* Channel number */
    int sr_flag;     /* 0=no queue, 1=send queued, 2=receive queued */
    struct channel *link;           /* Link to next channel in list */
    struct message *message_list;          /* Head of message queue */
    struct message *message_tail;          /* Tail of message queue */
};
struct message       /* One structure for each pending send/receive */
{
    int size;                           /* Size of message in bytes */
    char *addr;                      /* Pointer to start of message */
    struct message *link;          /* Link to next message in queue */
    struct context *thread;   /* Which thread blocks on this struct */
};
static struct context main_thread;    /* Storage for main() details */
static struct context *current;       /* Currently executing thread */
static int thread_count = 0;       /* Number of threads to schedule */
static struct channel *channel_list = 0;    /* List of all channels */
static int switch_context(struct context *, struct context *);
static int exit_thread(void);
static int rendezvous(struct channel *, char *, int, int);


release

/***********************************************************************
    Case study source code from the book `The Linux A to Z'
    by Phil Cornes. Published by Prentice Hall, 1996.
    Copyright (C) 1996 Phil Cornes
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
***********************************************************************/
release(void)
{
    /* 1 */
    if (thread_count<=1)
        return 0;
    /* 2 */
    current = current->next;
    switch_context(current->prev, current);
    return 1;
}

static switch_context(struct context *from, struct context *to)
{
    /* 3 */
    __asm__
    (
        "movl 8(%ebp),%eax/n/t"
        "movl %ebp,(%eax)/n/t"
        "movl 12(%ebp),%eax/n/t"
        "movl (%eax),%ebp/n/t"
    );
}


redezvous

/***********************************************************************
    Case study source code from the book `The Linux A to Z'
    by Phil Cornes. Published by Prentice Hall, 1996.
    Copyright (C) 1996 Phil Cornes
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
***********************************************************************/
send(int chan, char *addr, int size)
{
    /* 1 */
    return(rendezvous((struct channel *)chan, addr, size, 1));
}

receive(int chan, char *addr, int size)
{
    /* 2 */
    return(rendezvous((struct channel *)chan, addr, size, 2));
}

static int rendezvous(struct channel *chan, char *addr,
int size, int sr_flag)
{
    struct message *ptr;
    int nbytes;
    /* 3 */
    if (sr_flag==3-chan->sr_flag)
    {
        /* 4 */
        ptr = chan->message_list;
        chan->message_list = ptr->link;
        ptr->thread->next = current->next;
        ptr->thread->prev = current;
        current->next->prev = ptr->thread;
        current->next = ptr->thread;
        ++thread_count;
        /* 5 */
        nbytes = (size<ptr->size)?size:ptr->size;
        ptr->size = nbytes;
        /* 6 */
        if (sr_flag==1)
            memcpy(ptr->addr, addr, nbytes);
        else
            memcpy(addr, ptr->addr, nbytes);
        /* 7 */
        if (!chan->message_list)
            chan->sr_flag = 0;
        return(nbytes);
    }
    else
    {
        /* 8 */
        ptr = (struct message *)malloc(sizeof(struct message));
        if (!chan->message_list)
            chan->message_list = ptr;
        else
            chan->message_tail->link = ptr;
        chan->message_tail = ptr;
        ptr->link = 0;
        ptr->size = size;
        ptr->addr = addr;
        chan->sr_flag = sr_flag;
        ptr->thread = current;
        current->prev->next = current->next;
        current->next->prev = current->prev;
        /* 9 */
        if (--thread_count)
        {
            current = current->next;
            switch_context(ptr->thread, current);
        }
        else
            switch_context(ptr->thread, &main_thread);
        /* 10 */
        nbytes = ptr->size;
        free(ptr);
        return(nbytes);
    }
}


unbouded

/***********************************************************************
    Case study source code from the book `The Linux A to Z'
    by Phil Cornes. Published by Prentice Hall, 1996.
    Copyright (C) 1996 Phil Cornes
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
***********************************************************************/
int buffer(void);
int start(void);
int ch_desc1, ch_desc2;
main(void)
{
    ch_desc1 = get_channel(1);
    ch_desc2 = get_channel(2);
    new_thread(start, 1024);
}
start(void)
{
    int i, n;
    new_thread(buffer, 1024);
    for (i = 0; i<10, ++i)
    {
        send(ch_desc1, &i, sizeof(int));
        release();
    }
    for (i = 0; i<10, ++i)
    {
        receive(ch_desc2, &n, sizeof(int));
        printf("i=%d n=%d/n", i, n);
        release();
    }
}
buffer(void)
{
    int i;
    receive(ch_desc1, &i, sizeof(int));
    new_thread(buffer, 1024);
    send(ch_desc2, &i, sizeof(int));
}
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章