国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發(fā)設計 > 正文

進程和線程編程

2019-11-17 05:33:22
字體:
來源:轉載
供稿:網(wǎng)友

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

[目錄]

--------------------------------------------------------------------------------


原始管道

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


[目錄]

--------------------------------------------------------------------------------


pipe()

系統(tǒng)調用:pipe();
原型:intpipe(intfd[2]);
返回值:假如系統(tǒng)調用成功,返回0
假如系統(tǒng)調用失敗返回-1:errno=EMFILE(沒有空閑的文件描述符)
EMFILE(系統(tǒng)文件表已滿)
EFAULT(fd數(shù)組無效)
注重fd[0]用于讀取管道,fd[1]用于寫入管道。
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
main()
{
intfd[2];
pipe(fd);
..
}
一旦創(chuàng)建了管道,我們就可以創(chuàng)建一個新的子進程:
#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);
}..
}
假如父進程希望從子進程中讀取數(shù)據(jù),那么它應該關閉fd1,同時子進程關閉fd0。反之,假如父進程希望向子進程中發(fā)送數(shù)據(jù),那么它應該關閉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]);
}..
}

正如前面提到的,一但創(chuàng)建了管道之后,管道所使用的文件描述符就和正常文件的文件描述符一樣了。

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
intmain(void)
{
intfd[2],nbytes;
pid_tchildpid;
charstring[]="Hello,world!
";
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()執(zhí)行另一個程序,此程序繼續(xù)了標準的數(shù)據(jù)流。




[目錄]

--------------------------------------------------------------------------------


dup()

系統(tǒng)調用:dup();
原型:intdup(intoldfd);
返回:假如系統(tǒng)調用成功,返回新的文件描述符
假如系統(tǒng)調用失敗,返回-1:errno=EBADF(oldfd不是有效的文件描述符)
EBADF(newfd超出范圍)
EMFILE(進程的文件描述符太多)
注重舊文件描述符oldfd沒有關閉。雖然舊文件描述符和新創(chuàng)建的文件描述符可以交換使用,但一般情況下需要首先關閉一個。系統(tǒng)調用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程序覆蓋子進程的正文段。因為新創(chuàng)建的程序從它的父進程中繼續(xù)了標準輸入/輸出流,所以它實際上繼續(xù)了管道的輸入端作為它的標準輸入端。現(xiàn)在,最初的父進程送往管道的任何數(shù)據(jù)都將會直接送往sort函數(shù)。




[目錄]

--------------------------------------------------------------------------------


dup2()

系統(tǒng)調用:dup2();
原型:intdup2(intoldfd,intnewfd);
返回值:假如調用成功,返回新的文件描述符
假如調用失敗,返回-1:errno=EBADF(oldfd不是有效的文件描述符)
EBADF(newfd超出范圍)
EMFILE(進程的文件描述符太多)
注重dup2()將關閉舊文件描述符。
使用此系統(tǒng)調用,可以將close操作和文件描述符復制操作集成到一個系統(tǒng)調用中。另外,此系統(tǒng)調用保證了操作的自動進行,也就是說操作不能被其他的信號中斷。這個操作將會在返回系統(tǒng)內核之前完成。假如使用前一個系統(tǒng)調用dup(),程序員不得不在此之前執(zhí)行一個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()

假如你認為上面創(chuàng)建和使用管道的方法過于繁瑣的話,你也可以使用下面的簡單的方法:
庫函數(shù):popen()和pclose();
原型:FILE*popen(char*command,char*type);
返回值:假如成功,返回一個新的文件流。
假如無法創(chuàng)建進程或者管道,返回NULL。
此標準的庫函數(shù)通過在系統(tǒng)內部調用pipe()來創(chuàng)建一個半雙工的管道,然后它創(chuàng)建一個子進程,啟動shell,最后在shell上執(zhí)行command參數(shù)中的命令。管道中數(shù)據(jù)流的方向是由第二個參數(shù)type控制的。此參數(shù)可以是r或者w,分別代表讀或寫。但不能同時為讀和寫。在linux系統(tǒng)下,管道將會以參數(shù)type中第一個字符代表的方式打開。所以,假如你在參數(shù)type中寫入rw,管道將會以讀的方式打開。

雖然此庫函數(shù)的用法很簡單,但也有一些不利的地方。例如它失去了使用系統(tǒng)調用pipe()時可以有的對系統(tǒng)的控制。盡管這樣,因為可以直接地使用shell命令,所以shell中的一些通配符和其他的一些擴展符號都可以在command參數(shù)中使用。
使用popen()創(chuàng)建的管道必須使用pclose()關閉。其實,popen/pclose和標準文件輸入/輸出流中的fopen()/fclose()十分相似。



庫函數(shù):pclose();
原型:intpclose(FILE*stream);
返回值:返回系統(tǒng)調用wait4()的狀態(tài)。
假如stream無效,或者系統(tǒng)調用wait4()失敗,則返回-1。
注重此庫函數(shù)等待管道進程運行結束,然后關閉文件流。庫函數(shù)pclose()在使用popen()創(chuàng)建的進程上執(zhí)行wait4()函數(shù)。當它返回時,它將破壞管道和文件系統(tǒng)。
在下面的例子中,用sort命令打開了一個管道,然后對一個字符數(shù)組排序:

#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('
',pipe_fp);
}
/*Closethepipe*/
pclose(pipe_fp);
return(0);
}
因為popen()使用shell執(zhí)行命令,所以所有的shell擴展符和通配符都可以使用。此外,它還可以和popen()一起使用重定向和輸出管道函數(shù)。再看下面的例子:
popen("ls~scottb","r");
popen("sort>/tmp/foo","w");
popen("sortuniqmore","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()的例子。此程序用于創(chuàng)建一個命令和文件之間的管道:
#include<stdio.h>
intmain(intargc,char*argv[])
{
FILE*pipe_fp,*infile;
charreadbuf[80];
if(argc!=3){
fprintf(stderr,"USAGE:popen3[command][filename]
");
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.cgrepmain




[目錄]

--------------------------------------------------------------------------------


命名管道

命名管道和一般的管道基本相同,但也有一些顯著的不同:
*命名管道是在文件系統(tǒng)中作為一個非凡的設備文件而存在的。
*不同祖先的進程之間可以通過管道共享數(shù)據(jù)。
*當共享管道的進程執(zhí)行完所有的I/O操作以后,命名管道將繼續(xù)保存在文件系統(tǒng)中以便以后使用。


一個管道必須既有讀取進程,也要有寫入進程。假如一個進程試圖寫入到一個沒有讀取進程的管道中,那么系統(tǒng)內核將會產生SIGPIPE信號。當兩個以上的進程同時使用管道時,這一點尤其重要。


[目錄]

--------------------------------------------------------------------------------


創(chuàng)建FIFO

可以有幾種方法創(chuàng)建一個命名管道。頭兩種方法可以使用shell。
mknodMYFIFOp
mkfifoa=rwMYFIFO
上面的兩個命名執(zhí)行同樣的操作,但其中有一點不同。命令mkfifo提供一個在創(chuàng)建之后直接改變FIFO文件存取權限的途徑,而命令mknod需要調用命令chmod。
一個物理文件系統(tǒng)可以通過p指示器十分輕易地分辨出一個FIFO文件。

$ls-lMYFIFO
prw-r--r--1rootroot0Dec1422:15MYFIFO

請注重在文件名后面的管道符號“”。
我們可以使用系統(tǒng)調用mknod()來創(chuàng)建一個FIFO管道:

庫函數(shù):mknod();
原型:intmknod(char*pathname,mode_tmode,dev_tdev);
返回值:假如成功,返回0
假如失敗,返回-1:errno=EFAULT(無效路徑名)
EACCES(無存取權限)
ENAMETOOLONG(路徑名太長)
ENOENT(無效路徑名)
ENOTDIR(無效路徑名)

下面看一個使用C語言創(chuàng)建FIFO管道的例子:

mknod("/tmp/MYFIFO",S_IFIFO0666,0);

在這個例子中,文件/tmp/MYFIFO是要創(chuàng)建的FIFO文件。它的存取權限是0666。存取權限
也可以使用umask修改:

final_umask=requested_permissions&~original_umask

一個常用的使用系統(tǒng)調用umask()的方法就是臨時地清除umask的值:
umask(0);
mknod("/tmp/MYFIFO",S_IFIFO0666,0);

另外,mknod()中的第三個參數(shù)只有在創(chuàng)建一個設備文件時才能用到。它包括設備文件的
主設備號和從設備號。
}
}






[目錄]

--------------------------------------------------------------------------------


操作FIFO

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


[目錄]

--------------------------------------------------------------------------------


阻塞FIFO

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




[目錄]

--------------------------------------------------------------------------------


消息隊列

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

[目錄]

--------------------------------------------------------------------------------


msgget()

系統(tǒng)調用msgget()
假如希望創(chuàng)建一個新的消息隊列,或者希望存取一個已經(jīng)存在的消息隊列,你可以使用系統(tǒng)調用msgget()。

系統(tǒng)調用:msgget();
原型:intmsgget(key_t key,int msgflg);
返回值:假如成功,返回消息隊列標識符
假如失敗,則返回-1:errno=Eaccess(權限不答應)
EEXIST(隊列已經(jīng)存在,無法創(chuàng)建)
EIDRM(隊列標志為刪除)
ENOENT(隊列不存在)
ENOMEM(創(chuàng)建隊列時內存不夠)
ENOSPC(超出最大隊列限制)

系統(tǒng)調用msgget()中的第一個參數(shù)是要害字值(通常是由ftok()返回的)。然后此要害字值將會和其他已經(jīng)存在于系統(tǒng)內核中的要害字值比較。這時,打開和存取操作是和參數(shù)msgflg中的內容相關的。
IPC_CREAT假如內核中沒有此隊列,則創(chuàng)建它。
IPC_EXCL當和IPC_CREAT一起使用時,假如隊列已經(jīng)存在,則失敗。

假如單獨使用IPC_CREAT,則msgget()要么返回一個新創(chuàng)建的消息隊列的標識符,要么返回具有相同要害字值的隊列的標識符。假如IPC_EXCL和IPC_CREAT一起使用,則msgget()要么創(chuàng)建一個新的消息隊列,要么假如隊列已經(jīng)存在則返回一個失敗值-1。IPC_EXCL單獨使用是沒有用處的。
下面看一個打開和創(chuàng)建一個消息隊列的例子:
intopen_queue(key_t keyval)
{
intqid;
if((qid=msgget(keyval,IPC_CREAT0660))==-1)
{
return(-1);
}
return(qid);
}




[目錄]

--------------------------------------------------------------------------------


msgsnd()

系統(tǒng)調用msgsnd()
一旦我們得到了隊列標識符,我們就可以在隊列上執(zhí)行我們希望的操作了。假如想要往隊列中發(fā)送一條消息,你可以使用系統(tǒng)調用msgsnd():

系統(tǒng)調用:msgsnd();
原型:intmsgsnd(int msqid,strUCt msgbuf*msgp,int msgsz,int msgflg);
返回值:假如成功,0。
假如失敗,-1:errno=EAGAIN(隊列已滿,并且使用了IPC_NOWAIT)
EACCES(沒有寫的權限)
EFAULT(msgp地址無效)
EIDRM(消息隊列已經(jīng)刪除)
EINTR(當?shù)却龑懖僮鲿r,收到一個信號)
EINVAL(無效的消息隊列標識符,非正數(shù)的消息類型,或
者無效的消息長度)
ENOMEM(沒有足夠的內存復制消息緩沖區(qū))

系統(tǒng)調用msgsnd()的第一個參數(shù)是消息隊列標識符,它是由系統(tǒng)調用msgget返回的。第二個參數(shù)是msgp,是指向消息緩沖區(qū)的指針。參數(shù)msgsz中包含的是消息的字節(jié)大小,但不包括消息類型的長度(4個字節(jié))。
參數(shù)msgflg可以設置為0(此時為忽略此參數(shù)),或者使用IPC_NOWAIT。

假如消息隊列已滿,那么此消息則不會寫入到消息隊列中,控制將返回到調用進程中。假如沒有指明,調用進程將會掛起,直到消息可以寫入到隊列中。
下面是一個發(fā)送消息的程序:

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);
}

這個小程序試圖將存儲在緩沖區(qū)qbuf中的消息發(fā)送到消息隊列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);
}
}
在創(chuàng)建和打開消息隊列以后,我們將測試數(shù)據(jù)裝入到消息緩沖區(qū)中。最后調用send_messag把消息發(fā)送到消息隊列中。現(xiàn)在在消息隊列中有了一條消息,我們可以使用ipcs命令來查看隊列的狀態(tài)。下面討論如何從隊列中獲取消息。可以使用系統(tǒng)調用msgrcv():



[目錄]

--------------------------------------------------------------------------------


msgrcv()

系統(tǒng)調用:msgrcv();
原型:intmsgrcv(intmsqid,structmsgbuf*msgp,intmsgsz,longmtype,intmsgflg);
返回值:假如成功,則返回復制到消息緩沖區(qū)的字節(jié)數(shù)。
假如失敗,則返回-1:errno=E2BIG(消息的長度大于msgsz,沒有MSG_NOERROR)
EACCES(沒有讀的權限)
EFAULT(msgp指向的地址是無效的)
EIDRM(隊列已經(jīng)被刪除)
EINTR(被信號中斷)
EINVAL(msgqid無效,或者msgsz小于0)
ENOMSG(使用IPC_NOWAIT,同時隊列中的消息無法滿足要求)
很明顯,第一個參數(shù)用來指定將要讀取消息的隊列。第二個參數(shù)代表要存儲消息的消息緩沖區(qū)的地址。第三個參數(shù)是消息緩沖區(qū)的長度,不包括mtype的長度,它可以按照如下的方法計算:
msgsz=sizeof(structmymsgbuf)-sizeof(long);
第四個參數(shù)是要從消息隊列中讀取的消息的類型。假如此參數(shù)的值為0,那么隊列中最長時間的一條消息將返回,而不論其類型是什么。
假如調用中使用了IPC_NOWAIT作為標志,那么當沒有數(shù)據(jù)可以使用時,調用將把ENOMSG返回到調用進程中。否則,調用進程將會掛起,直到隊列中的一條消息滿足msgrcv()的參數(shù)要求。假如當客戶端等待一條消息的時候隊列為空,將會返回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);
}
在成功地讀取了一條消息以后,隊列中的這條消息的入口將被刪除。
參數(shù)msgflg中的MSG_NOERROR位提供一種額外的用途。假如消息的實際長度大于msgsz,同時使用了MSG_NOERROR,那么消息將會被截斷,只有與msgsz長度相等的消息返回。一般情況下,系統(tǒng)調用msgrcv()會返回-1,而這條消息將會繼續(xù)保存在隊列中。我們可以利用這個特點編制一個程序,利用這個程序可以查看消息隊列的情況,看看符合我們條件的消息是否已經(jīng)到來:

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);
}
在上面的程序中,我們忽略了緩沖區(qū)的地址和長度。這樣,系統(tǒng)調用將會失敗。盡管如此,我們可以檢查返回的E2BIG值,它說明符合條件的消息確實存在。



[目錄]

--------------------------------------------------------------------------------


msgctl()

系統(tǒng)調用msgctl()
下面我們繼續(xù)討論如何使用一個給定的消息隊列的內部數(shù)據(jù)結構。我們可以使用系統(tǒng)調用msgctl ( )來控制對消息隊列的操作。


系統(tǒng)調用: 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
讀取消息隊列的數(shù)據(jù)結構msqid_ds,并將其存儲在b u f指定的地址中。
IPC_SET
設置消息隊列的數(shù)據(jù)結構msqid_ds中的ipc_perm元素的值。這個值取自buf參數(shù)。
IPC_RMID
從系統(tǒng)內核中移走消息隊列。
我們在前面討論過了消息隊列的數(shù)據(jù)結構(msqid_ds)。系統(tǒng)內核中為系統(tǒng)中的每一個消息隊列保存一個此數(shù)據(jù)結構的實例。通過使用IPC_STAT命令,我們可以得到一個此數(shù)據(jù)結構的副本。下面的程序就是實現(xiàn)此函數(shù)的過程:

int get_queue_ds( int qid, struct msgqid_ds *qbuf )
{
if( msgctl( qid, IPC_STAT, qbuf) == -1)
{
return(-1);
}
return(0);
}

假如不能復制內部緩沖區(qū),調用進程將返回-1。假如調用成功,則返回0。緩沖區(qū)中應該包括消息隊列中的數(shù)據(jù)結構。
消息隊列中的數(shù)據(jù)結構中唯一可以改動的元素就是ipc_perm。它包括隊列的存取權限和關于隊列創(chuàng)建者和擁有者的信息。你可以改變用戶的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來讀取隊列的內部數(shù)據(jù)結構。然后,我們調用sscanf( )修改數(shù)據(jù)結構msg_perm中的mode 成員的值。但直到調用msgctl()時,權限的改變才真正完成。在這里msgctl()使用的是IPC_SET命令。
最后,我們使用系統(tǒng)調用msgctl ( )中的IPC_RMID命令刪除消息隊列:

int remove_queue(int qid )
{
if( msgctl( qid, IPC_RMID, 0) == -1)
{
return(-1);
}
return(0);
}
};



[目錄]

--------------------------------------------------------------------------------


信號量

信號量是一個可以用來控制多個進程存取共享資源的計數(shù)器。它經(jīng)常作為一種鎖定機制來防止當一個進程正在存取共享資源時,另一個進程也存取同一資源。下面先簡要地介紹一下信號量中涉及到的數(shù)據(jù)結構。
1.內核中的數(shù)據(jù)結構semid_ds
和消息隊列一樣,系統(tǒng)內核為內核地址空間中的每一個信號量集都保存了一個內部的數(shù)據(jù)結構。數(shù)據(jù)結構的原型是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定義的數(shù)據(jù)結構ipc_perm的一個實例。它保存有信號量集的存取權限的信息,以及信號量集創(chuàng)建者的有關信息。
sem_otime最后一次semop()操作的時間。
sem_ctime最后一次改動此數(shù)據(jù)結構的時間。
sem_base指向數(shù)組中第一個信號量的指針。
sem_undo數(shù)組中沒有完成的請求的個數(shù)。
sem_nsems信號量集(數(shù)組)中的信號量的個數(shù)。

2.內核中的數(shù)據(jù)結構sem
在數(shù)據(jù)結構semid_ds中包含一個指向信號量數(shù)組的指針。此數(shù)組中的每一個元素都是一個

數(shù)據(jù)結構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等待資源的進程數(shù)目。
sem_semzcnt等待資源完全空閑的進程數(shù)目。


[目錄]

--------------------------------------------------------------------------------


semget()

我們可以使用系統(tǒng)調用semget()創(chuàng)建一個新的信號量集,或者存取一個已經(jīng)存在的信號量集:
系統(tǒng)調用:semget();
原型:intsemget(key_t key,int nsems,int semflg);
返回值:假如成功,則返回信號量集的IPC標識符。假如失敗,則返回-1:errno=EACCESS(沒有權限)
EEXIST(信號量集已經(jīng)存在,無法創(chuàng)建)
EIDRM(信號量集已經(jīng)刪除)
ENOENT(信號量集不存在,同時沒有使用IPC_CREAT)
ENOMEM(沒有足夠的內存創(chuàng)建新的信號量集)
ENOSPC(超出限制)

系統(tǒng)調用semget()的第一個參數(shù)是要害字值(一般是由系統(tǒng)調用ftok()返回的)。系統(tǒng)內核將此值和系統(tǒng)中存在的其他的信號量集的要害字值進行比較。打開和存取操作與參數(shù)semflg中的內容相關。IPC_CREAT假如信號量集在系統(tǒng)內核中不存在,則創(chuàng)建信號量集。IPC_EXCL當和IPC_CREAT一同使用時,假如信號量集已經(jīng)存在,則調用失敗。假如單獨使用IPC_CREAT,則semget()要么返回新創(chuàng)建的信號量集的標識符,要么返回系統(tǒng)中已經(jīng)存在的同樣的要害字值的信號量的標識符。假如IPC_EXCL和IPC_CREAT一同使用,則要么返回新創(chuàng)建的信號量集的標識符,要么返回-1。IPC_EXCL單獨使用沒有意義。參數(shù)nsems指出了一個新的信號量集中應該創(chuàng)建的信號量的個數(shù)。信號量集中最多的信號量的個數(shù)是在linux/sem.h中定義的:

#defineSEMMSL32/*<=512maxnumofsemaphoresperid*/
下面是一個打開和創(chuàng)建信號量集的程序:
intopen_semaphore_set(key_t keyval,int numsems)
{
intsid;
if(!numsems)
return(-1);
if((sid=semget(mykey,numsems,IPC_CREAT0660))==-1)
{
return(-1);
}
return(sid);
}
};






[目錄]

--------------------------------------------------------------------------------


semop()

系統(tǒng)調用:semop();
調用原型:int semop(int semid,struct sembuf*sops,unsign ednsops);
返回值:0,假如成功。-1,假如失敗:errno=E2BIG(nsops大于最大的ops數(shù)目)
EACCESS(權限不夠)
EAGAIN(使用了IPC_NOWAIT,但操作不能繼續(xù)進行)
EFAULT(sops指向的地址無效)
EIDRM(信號量集已經(jīng)刪除)
EINTR(當睡眠時接收到其他信號)
EINVAL(信號量集不存在,或者semid無效)
ENOMEM(使用了SEM_UNDO,但無足夠的內存創(chuàng)建所需的數(shù)據(jù)結構)
ERANGE(信號量值超出范圍)
第一個參數(shù)是要害字值。第二個參數(shù)是指向將要操作的數(shù)組的指針。第三個參數(shù)是數(shù)組中的操作的個數(shù)。參數(shù)sops指向由sembuf組成的數(shù)組。此數(shù)組是在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將要處理的信號量的個數(shù)。
sem_op要執(zhí)行的操作。
sem_flg操作標志。

假如sem_op是負數(shù),那么信號量將減去它的值。這和信號量控制的資源有關。假如沒有使用IPC_NOWAIT,那么調用進程將進入睡眠狀態(tài),直到信號量控制的資源可以使用為止。假如sem_op是正數(shù),則信號量加上它的值。這也就是進程釋放信號量控制的資源。最后,假如sem_op是0,那么調用進程將調用sleep(),直到信號量的值為0。這在一個進程等待完全空閑的資源時使用。






[目錄]

--------------------------------------------------------------------------------



semctl()

系統(tǒng)調用:semctl();
原型:int semctl(int semid,int semnum,int cmd,union semunarg);
返回值:假如成功,則為一個正數(shù)。
假如失敗,則為-1:errno=EACCESS(權限不夠)
EFAULT(arg指向的地址無效)
EIDRM(信號量集已經(jīng)刪除)
EINVAL(信號量集不存在,或者semid無效)
EPERM(EUID沒有cmd的權利)
ERANGE(信號量值超出范圍)
系統(tǒng)調用semctl用來執(zhí)行在信號量集上的控制操作。這和在消息隊列中的系統(tǒng)調用msgctl是十分相似的。但這兩個系統(tǒng)調用的參數(shù)略有不同。因為信號量一般是作為一個信號量集使用的,而不是一個單獨的信號量。所以在信號量集的操作中,不但要知道IPC要害字值,也要知道信號量集中的具體的信號量。這兩個系統(tǒng)調用都使用了參數(shù)cmd,它用來指出要操作的具體命令。兩個系統(tǒng)調用中的最后一個參數(shù)也不一樣。在系統(tǒng)調用msgctl中,最后一個參數(shù)是指向內核中使用的數(shù)據(jù)結構的指針。我們使用此數(shù)據(jù)結構來取得有關消息隊列的一些信息,以及設置或者改變隊列的存取權限和使用者。但在信號量中支持額外的可選的命令,這樣就要求有一個更為復雜的數(shù)據(jù)結構。
系統(tǒng)調用semctl()的第一個參數(shù)是要害字值。第二個參數(shù)是信號量數(shù)目。

參數(shù)cmd中可以使用的命令如下:
·IPC_STAT讀取一個信號量集的數(shù)據(jù)結構semid_ds,并將其存儲在semun中的buf參數(shù)中。
·IPC_SET設置信號量集的數(shù)據(jù)結構semid_ds中的元素ipc_perm,其值取自semun中的buf參數(shù)。
·IPC_RMID將信號量集從內存中刪除。
·GETALL用于讀取信號量集中的所有信號量的值。
·GETNCNT返回正在等待資源的進程數(shù)目。
·GETPID返回最后一個執(zhí)行semop操作的進程的PID。
·GETVAL返回信號量集中的一個單個的信號量的值。
·GETZCNT返回這在等待完全空閑的資源的進程數(shù)目。
·SETALL設置信號量集中的所有的信號量的值。
·SETVAL設置信號量集中的一個單獨的信號量的值。

參數(shù)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當執(zhí)行SETVAL命令時使用。buf在IPC_STAT/IPC_SET命令中使用。代表了內核中使用的信號量的數(shù)據(jù)結構。array在使用GETALL/SETALL命令時使用的指針。
下面的程序返回信號量的值。當使用GETVAL命令時,調用中的最后一個參數(shù)被忽略:

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
",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);
}

注重系統(tǒng)調用semctl中的最后一個參數(shù)是一個聯(lián)合類型的副本,而不是一個指向聯(lián)合類型的指針。




[目錄]

--------------------------------------------------------------------------------


共享內存

共享內存就是由幾個進程共享一段內存區(qū)域。這可以說是最快的IPC形式,因為它無須任何的中間操作(例如,管道、消息隊列等)。它只是把內存段直接映射到調用進程的地址空間中。這樣的內存段可以是由一個進程創(chuàng)建的,然后其他的進程可以讀寫此內存段。
每個系統(tǒng)的共享內存段在系統(tǒng)內核中也保持著一個內部的數(shù)據(jù)結構shmid_ds。此數(shù)據(jù)結構是在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 是數(shù)據(jù)結構ipc_perm的一個實例。這里保存的是內存段的存取權限,和其他的有關內存段創(chuàng)建者的信息。
shm_segsz 內存段的字節(jié)大小。
shm_atime 最后一個進程存取內存段的時間。
shm_dtime 最后一個進程離開內存段的時間。
shm_ctime 內存段最后改動的時間。
shm_cpid 內存段創(chuàng)建進程的P I D。
shm_lpid 最后一個使用內存段的進程的P I D。
shm_nattch 當前使用內存段的進程總數(shù)。


[目錄]

--------------------------------------------------------------------------------


shmget()

系統(tǒng)調用:shmget();
原型:int shmget(key_t key,int size,int shmflg);
返回值:假如成功,返回共享內存段標識符。假如失敗,則返回-1:errno=EINVAL(無效的內存段大小)
EEXIST(內存段已經(jīng)存在,無法創(chuàng)建)
EIDRM(內存段已經(jīng)被刪除)
ENOENT(內存段不存在)
EACCES(權限不夠)
ENOMEM(沒有足夠的內存來創(chuàng)建內存段)
系統(tǒng)調用shmget()中的第一個參數(shù)是要害字值(它是用系統(tǒng)調用ftok()返回的)。其他的操作都要依據(jù)shmflg中的命令進行。
·IPC_CREAT假如系統(tǒng)內核中沒有共享的內存段,則創(chuàng)建一個共享的內存段。
·IPC_EXCL當和IPC_CREAT一同使用時,假如共享內存段已經(jīng)存在,則調用失敗。
當IPC_CREAT單獨使用時,系統(tǒng)調用shmget()要么返回一個新創(chuàng)建的共享內存段的標識符,要么返回一個已經(jīng)存在的共享內存段的要害字值。假如IPC_EXCL和IPC_CREAT一同使用,則要么系統(tǒng)調用新創(chuàng)建一個共享的內存段,要么返回一個錯誤值-1。IPC_EXCL單獨使用沒有意義。

下面是一個定位和創(chuàng)建共享內存段的程序:

int open_segment(key_t keyval,int segsize)
{
int shmid;
if((shmid=shmget(keyval,segsize,IPC_CREAT0660))==-1)
{
return(-1);
}
return(shmid);
}

一旦一個進程擁有了一個給定的內存段的有效IPC標識符,它的下一步就是將共享的內存段映射到自己的地址空間中。





[目錄]

--------------------------------------------------------------------------------


shmat()

系統(tǒng)調用: shmat();
原型:int shmat ( int shmid, char *shmaddr, int shmflg);
返回值:假如成功,則返回共享內存段連接到進程中的地址。假如失敗,則返回- 1:errno = EINVAL (無效的IPC ID 值或者無效的地址)
ENOMEM (沒有足夠的內存)
EACCES (存取權限不夠)
假如參數(shù)a d d r的值為0,那么系統(tǒng)內核則試圖找出一個沒有映射的內存區(qū)域。我們推薦使用這種方法。你可以指定一個地址,但這通常是為了加快對硬件設備的存取,或者解決和其他程序的沖突。
下面的程序中的調用參數(shù)是一個內存段的I P C標識符,返回內存段連接的地址:

char *attach_segment(int shmid)
{
return(shmat(shmid, 0, 0));
}

一旦內存段正確地連接到進程以后,進程中就有了一個指向該內存段的指針。這樣,以后就可以使用指針來讀取此內存段了。但一定要注重不能丟失該指針的初值。




[目錄]

--------------------------------------------------------------------------------


shmctl()

系統(tǒng)調用: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 讀取一個內存段的數(shù)據(jù)結構shmid_ds,并將它存儲在buf參數(shù)指向的地址中。
IPC_SET 設置內存段的數(shù)據(jù)結構shmid_ds中的元素ipc_perm的值。從參數(shù)buf中得到要設置的值。

IPC_RMID 標志內存段為移走。
命令IPC_RMID并不真正從系統(tǒng)內核中移走共享的內存段,而是把內存段標記為可移除。進程調用系統(tǒng)調用shmdt()脫離一個共享的內存段。




[目錄]

--------------------------------------------------------------------------------


shmdt()

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




[目錄]

--------------------------------------------------------------------------------


線程

線程通常叫做輕型的進程。雖然這個叫法有些簡單化,但這有利于了解線程的概念。因為線程和進程比起來很小,所以相對來說,線程花費更少的CPU資源。進程往往需要它們自己的資源,但線程之間可以共享資源,所以線程更加節(jié)省內存。Mach的線程使得程序員可以編寫并發(fā)運行的程序,而這些程序既可以運行在單處理器的機器上,也可以運行在多處理器的機器中。另外,在單處理器環(huán)境中,當應用程序執(zhí)行輕易引起阻塞和延遲的操作時,線程可以提高效率。
用子函數(shù)pthread_create創(chuàng)建一個新的線程。它有四個參數(shù):一個用來保存線程的線程變量、一個線程屬性、當線程執(zhí)行時要調用的函數(shù)和一個此函數(shù)的參數(shù)。例如:
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);
線程屬性只指明了需要使用的最小的堆棧大小。在以后的程序中,線程的屬性可以指定其他的值,但現(xiàn)在大部分的程序可以使用缺省值。不像UNIX系統(tǒng)中使用fork系統(tǒng)調用創(chuàng)建的進程,它們和它們的父進程使用同一個執(zhí)行點,線程使用在pthread_create中的參數(shù)指明要開始執(zhí)行的函數(shù)。

現(xiàn)在我們可以編制第一個程序了。我們編制一個多線程的應用程序,在標準輸出中打印“Hello Wo r l d”。首先我們需要兩個線程變量,一個新線程開始執(zhí)行時可以調用的函數(shù)。我們還需要指明每一個線程應該打印的信息。一個做法是把要打印的字符串分開,給每一個線程一個字符串作為開始的參數(shù)。請看下面的代碼:
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創(chuàng)建第一個線程,并將“Hello”作為它的啟動參數(shù)。第二個線程的參數(shù)是“World”。當?shù)谝粋€線程開始執(zhí)行時,它使用參數(shù)“Hello”執(zhí)行函數(shù)print_message_function。它在標準輸出中打印“Hello”,然后結束對函數(shù)的調用。線程當離開它的初始化函數(shù)時就將終止,所以第一個線程在打印完“Hello”后終止。當?shù)诙€線程執(zhí)行時,它打印“World”然后終止。但這個程序有兩個主要的缺陷。
首先也是最重要的是線程是同時執(zhí)行的。這樣就無法保證第一個線程先執(zhí)行打印語句。所以你很可能在屏幕上看到“World Hello”,而不是“Hello World”。請注重對exit的調用是父線程在主程序中使用的。這樣,假如父線程在兩個子線程調用打印語句之前調用exit,那么將不會有任何的打印輸出。這是因為exit函數(shù)將會退出進程,同時釋放任務,所以結束了所有的線程。任何線程(不論是父線程或者子線程)調用exit 都會終止所有其他線程。假如希望線程分別終止,可以使用pthread_exit函數(shù)。
我們可以使用一個辦法彌補此缺陷。我們可以在父線程中插入一個延遲程序,給子線程足夠的時間完成打印的調用。同樣,在調用第二個之前也插入一個延遲程序保證第一個線程在第二個線程執(zhí)行之前完成任務。

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) ;
}

這樣是否達到了我們的要求了呢?不盡如此,因為依靠時間的延遲執(zhí)行同步是不可靠的。這里碰到的情形和一個分布程序和共享資源的情形一樣。共享的資源是標準的輸出設備,分布計算的程序是三個線程。
其實這里還有另外一個錯誤。函數(shù)sleep和函數(shù)e x i t一樣和進程有關。當線程調用sleep時,整個的進程都處于睡眠狀態(tài),也就是說,所有的三個線程都進入睡眠狀態(tài)。這樣我們實際上沒有解決任何的問題。希望使一個線程睡眠的函數(shù)是pthread_delay_np。例如讓一個線程睡眠2秒鐘,用如下程序:

struct timespec delay;
delay.tv_sec = 2;
delay.tv_nsec = 0;
pthread_delay_np( &delay );
}


[目錄]

--------------------------------------------------------------------------------


線程同步

POSIX提供兩種線程同步的方法,mutex和條件變量。mutex是一種簡單的加鎖的方法來控制對共享資源的存取。我們可以創(chuàng)建一個讀/寫程序,它們共用一個共享緩沖區(qū),使用mutex來控制對緩沖區(qū)的存取。
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 );
}
}
在上面的程序中,我們假定緩沖區(qū)只能保存一條信息,這樣緩沖區(qū)只有兩個狀態(tài),有一條信息或者沒有信息。使用延遲是為了避免一個線程永遠占有mutex。
但mutex的缺點在于它只有兩個狀態(tài),鎖定和非鎖定。POSIX的條件變量通過答應線程阻塞和等待另一個線程的信號方法,從而彌補了mutex的不足。當接受到一個信號時,阻塞線程將會被喚起,并試圖獲得相關的mutex的鎖。



[目錄]

--------------------------------------------------------------------------------


使用信號量協(xié)調程序

我們可以使用信號量重新看一下上面的讀/寫程序。涉及信號量的操作是semaphore_up、semaphore_down、semaphore_init、semaphore_destroy和semaphore_decrement。所有這些操作都只有一個參數(shù),一個指向信號量目標的指針。
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 );
}
}
這個例子也沒有完全地利用一般信號量的所有函數(shù)。我們可以使用信號量重新編寫“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用來強迫父線程阻塞,直到兩個子線程執(zhí)行完p r i n t f語句和其后的semaphore_up( &child_counter )語句才繼續(xù)執(zhí)行。
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);
}




[目錄]

--------------------------------------------------------------------------------

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 临洮县| 柳江县| 临江市| 孟州市| 封开县| 仁化县| 沂水县| 黄山市| 元阳县| 拜城县| 临江市| 迁西县| 襄汾县| 英超| 航空| 绩溪县| 萝北县| 阳朔县| 抚顺县| 民勤县| 汤原县| 江川县| 高邮市| 武汉市| 桑日县| 星座| 凤山市| 南川市| 岑巩县| 法库县| 平谷区| 白银市| 稷山县| 延寿县| 萝北县| 遂宁市| 南木林县| 浮梁县| 波密县| 封开县| 乳山市|