《嵌入式Linux開發(fā)技術(shù)及實(shí)踐》課件第6章_第1頁(yè)
《嵌入式Linux開發(fā)技術(shù)及實(shí)踐》課件第6章_第2頁(yè)
《嵌入式Linux開發(fā)技術(shù)及實(shí)踐》課件第6章_第3頁(yè)
《嵌入式Linux開發(fā)技術(shù)及實(shí)踐》課件第6章_第4頁(yè)
《嵌入式Linux開發(fā)技術(shù)及實(shí)踐》課件第6章_第5頁(yè)
已閱讀5頁(yè),還剩327頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

第6章應(yīng)用編程6.1概述

6.2文件I/O編程6.3進(jìn)程6.4線程

6.5網(wǎng)絡(luò)編程

6.1概述

6.1.1應(yīng)用程序?yàn)榱吮Wo(hù)內(nèi)核不被破壞,Linux將整個(gè)地址空間分為用戶空間和內(nèi)核空間,用戶空間和內(nèi)核空間在邏輯上相互隔離,應(yīng)用程序只能在用戶空間操作用戶數(shù)據(jù)、調(diào)用用戶空間函數(shù),不允許訪問(wèn)內(nèi)核數(shù)據(jù),也無(wú)法使用內(nèi)核函數(shù)。

Linux通過(guò)API和系統(tǒng)調(diào)用使應(yīng)用程序獲得內(nèi)核的服務(wù)。它們的關(guān)系如圖6-1所示。從圖中可以看出:

用戶程序通過(guò)API來(lái)實(shí)現(xiàn)系統(tǒng)調(diào)用。

系統(tǒng)調(diào)用在內(nèi)核中通過(guò)內(nèi)核函數(shù)來(lái)實(shí)現(xiàn)。圖6-1應(yīng)用程序訪問(wèn)內(nèi)核空間6.1.2API

API(ApplicationProgramminginterface,應(yīng)用程序編程接口)是應(yīng)用程序在用戶空間直接使用的函數(shù)接口,是對(duì)系統(tǒng)調(diào)用的直接應(yīng)用。它由預(yù)定義的函數(shù)組成,也稱為API函數(shù),如glibc中的大量函數(shù)。每個(gè)API會(huì)對(duì)應(yīng)一定的功能,例如常用的read()、write()函數(shù)等。6.1.3系統(tǒng)調(diào)用系統(tǒng)調(diào)用是由操縱系統(tǒng)內(nèi)核提供的、為了和用戶空間上運(yùn)行的程序進(jìn)行交互的一組接口,通過(guò)該接口,應(yīng)用程序可以訪問(wèn)硬件設(shè)備和其他操作系統(tǒng)資源。

1.實(shí)現(xiàn)在每種平臺(tái)上,都有特定的指令可以使進(jìn)程的執(zhí)行從用戶態(tài)轉(zhuǎn)到內(nèi)核態(tài),這種指令稱為操作系統(tǒng)陷入指令。Linux系統(tǒng)是通過(guò)軟中斷來(lái)實(shí)現(xiàn)陷入的,即Linux系統(tǒng)下的系統(tǒng)調(diào)用是一個(gè)中斷處理函數(shù)的特例。在ARM體系結(jié)構(gòu)下,陷入指令為SWI,執(zhí)行SWI中斷處理。進(jìn)程執(zhí)行陷入指令后,便可以使程序運(yùn)行空間從用戶空間進(jìn)入內(nèi)核空間,處理完畢后再返回用戶空間。

2.分類

Linux2.6內(nèi)核中的系統(tǒng)調(diào)用達(dá)到280多個(gè),繼承了UNIX系統(tǒng)調(diào)用中最基本和最有用的部分。系統(tǒng)調(diào)用表定義在內(nèi)核arch/arm/kernel/syscall_table.h中。部分常見的系統(tǒng)調(diào)用如表6-1所示。

表6-1系統(tǒng)調(diào)用列表續(xù)表

3.標(biāo)識(shí)系統(tǒng)調(diào)用利用函數(shù)名和調(diào)用號(hào)來(lái)進(jìn)行標(biāo)識(shí)。

函數(shù)名,系統(tǒng)調(diào)用處理函數(shù)約定為sys_系統(tǒng)調(diào)用名稱。例如fork()的處理函數(shù)名是sys_fork()。調(diào)用號(hào),是系統(tǒng)調(diào)用的標(biāo)識(shí)號(hào),定義在內(nèi)核arch/arm/include/unistd.h中?!臼纠?-1】系統(tǒng)調(diào)用號(hào)定義//宏定義:系統(tǒng)調(diào)用函數(shù)名、系統(tǒng)調(diào)用號(hào)#define__NR_restart_syscall (__NR_SYSCALL_BASE+0)#define__NR_exit (__NR_SYSCALL_BASE+1)#define__NR_fork (__NR_SYSCALL_BASE+2)#define__NR_read (__NR_SYSCALL_BASE+3)#define__NR_write (__NR_SYSCALL_BASE+4)#define__NR_open (__NR_SYSCALL_BASE+5)#define__NR_close (__NR_SYSCALL_BASE+6)6.1.4API與系統(tǒng)調(diào)用

API與系統(tǒng)調(diào)用的關(guān)系有以下幾種:

一個(gè)API對(duì)應(yīng)一個(gè)系統(tǒng)調(diào)用。

一個(gè)API對(duì)應(yīng)多個(gè)系統(tǒng)調(diào)用。

API不需要系統(tǒng)調(diào)用。

幾個(gè)API對(duì)應(yīng)一個(gè)系統(tǒng)調(diào)用。

API應(yīng)用及系統(tǒng)調(diào)用的過(guò)程示例源碼如下。

【示例6-2】API與系統(tǒng)調(diào)用

#include<unistd.h>

#include<stdio.h>

intmain()

{

fork();

exit(0);

}上述源碼中用到了兩個(gè)API函數(shù)fork()和exit(),而且這兩個(gè)函數(shù)都是glibc庫(kù)中的函數(shù),在跟蹤函數(shù)執(zhí)行過(guò)程中,glibc對(duì)函數(shù)的實(shí)現(xiàn)是在代碼里利用軟中斷的方式陷入到內(nèi)核中并系統(tǒng)調(diào)用內(nèi)核中的函數(shù)實(shí)現(xiàn)功能的。

基于此例,API與系統(tǒng)調(diào)用的過(guò)程總結(jié)如下:

(1)執(zhí)行用戶程序,如fork()函數(shù)。

(2)根據(jù)glibc函數(shù)庫(kù)中的函數(shù)實(shí)現(xiàn),取得系統(tǒng)調(diào)用號(hào)并產(chǎn)生軟中斷。

(3)進(jìn)入內(nèi)核空間,進(jìn)行中斷處理,根據(jù)系統(tǒng)調(diào)用表調(diào)用內(nèi)核函數(shù)。

(4)執(zhí)行內(nèi)核函數(shù)。

(5)返回用戶空間。6.2文件I/O編程6.2.1概述文件是一個(gè)抽象的概念,可以獨(dú)立地存放與操作。Linux系統(tǒng)對(duì)所有的目錄與設(shè)備的操作都等同于文件操作。下面從文件的分類、文件描述符以及文件的操作函數(shù)這幾個(gè)方面來(lái)對(duì)文件進(jìn)行說(shuō)明。

1.分類

Linux的文件分為四種:

普通文件,用“-”表示。

目錄文件,用“d”表示。

鏈接文件,用“|”表示。

設(shè)備文件,用“b”或者“c”分別表示塊設(shè)備文件和字符設(shè)備文件。

2.文件描述符

Linux的所有文件操作都使用文件描述符(filedescriptor,簡(jiǎn)稱fd)來(lái)進(jìn)行。文件描述符是一個(gè)非負(fù)整數(shù),且是一個(gè)索引值,指向內(nèi)核中每一個(gè)進(jìn)程打開文件的記錄表。文件描述符的使用過(guò)程如下:

1)打開或創(chuàng)建新文件時(shí),內(nèi)核向進(jìn)程返回一個(gè)文件描述符fd。

(2)讀寫文件時(shí),把文件描述符fd作為參數(shù)傳遞給相應(yīng)函數(shù)。

Linux默認(rèn)的文件描述符有1024個(gè),可以使用ulimit命令查看,結(jié)果為1024。

【示例6-3】ulimit

$ulimit–n該示例的顯示結(jié)果如圖6-2所示。圖6-2ulimit命令標(biāo)號(hào)為0、1、2的文件描述符分別對(duì)應(yīng)標(biāo)準(zhǔn)輸入(stdin)、標(biāo)準(zhǔn)輸出(stdout)和標(biāo)準(zhǔn)出錯(cuò)處理(stderr)三個(gè)文件。每當(dāng)打開一個(gè)新進(jìn)程時(shí),首先都會(huì)打開這三個(gè)文件,默認(rèn)情況下,stdin連接到鍵盤,stdout和stderr連接到屏幕(終端)。

3.操作函數(shù)

glibc中提供了各種應(yīng)用層次下使用的文件輸入/輸出(I/O)函數(shù),這些函數(shù)分為兩類:

基本I/O函數(shù),基于文件描述符的操作函數(shù),不使用緩存區(qū),常用的函數(shù)有open()、close()、read()、write()等。

標(biāo)準(zhǔn)I/O函數(shù),基于文件流的操作函數(shù),使用緩存區(qū),常用的函數(shù)有fopen()、fclose()、fread()、fwrite()等。下面分別講述兩類I/O函數(shù)的使用方法。6.2.2基本I/O函數(shù)

基本I/O函數(shù)的主要特點(diǎn)有:

不帶緩存區(qū)。

通過(guò)文件描述符對(duì)文件進(jìn)行讀寫操作。基于這兩個(gè)特點(diǎn),用戶可以使用基本I/O函數(shù)直接操作硬件設(shè)備,為開發(fā)硬件設(shè)備驅(qū)動(dòng)提供了便利。例如,串口設(shè)備的操作都可以用基本I/O函數(shù)來(lái)實(shí)現(xiàn)。

1.基本操作文件的基本操作函數(shù)有open()、close()、read()、write()、lseek(),下面分別來(lái)介紹。

1)?open()

open()函數(shù)用于打開或創(chuàng)建文件,并可指定文件屬性及用戶權(quán)限等參數(shù)。該函數(shù)原型以及編程調(diào)用時(shí)要包含的頭文件如下。

【代碼6-1】open()

#include<sys/types.h> //聲明類型pid_t

#include<sys/stat.h> //聲明函數(shù)中使用的flag常量

#include<fcntl.h> //聲明ssize_t類型intopen(constchar*pathname,intflags,mode_tmode);其中,各參數(shù)含義如下:

*pathname,被打開或創(chuàng)建的文件名,也可以為路徑名。

flags,文件打開的方式,如只讀、只寫等,常用方式選項(xiàng)如表6-2所示。flags中各方式參數(shù)可通過(guò)“|”進(jìn)行組合,但是前三項(xiàng)參數(shù)(O_RDONLY、O_WRONLY、O_RDWR)只能三選一,不能組合。

表6-2常用flags選項(xiàng)

mode,創(chuàng)建新文件的操作權(quán)限。當(dāng)flags選項(xiàng)為O_CREAT時(shí)有效,定義了文件所有者/文件所屬組/其他用戶的讀、寫、執(zhí)行的權(quán)限,如表6-3所示。表6-3mode選項(xiàng)

open(?)函數(shù)若設(shè)置成功,則返回文件描述符;若設(shè)置失敗,則返回?-1,同時(shí)設(shè)置錯(cuò)誤變量。使用man命令查看幫助,例如使用man命令查看open()幫助信息。

【示例6-4】man

$manopen終端顯示幫助信息如圖6-3所示。圖6-3使用man命令查看open(?)函數(shù)幫助信息

2)?close()

close()函數(shù)用于關(guān)閉打開的文件。當(dāng)進(jìn)程終止時(shí),所有被打開的文件都由內(nèi)核自動(dòng)關(guān)閉。該函數(shù)原型及相關(guān)頭文件如下。

【代碼6-2】close()

#include<unistd.h>

intclose(intfd);其中參數(shù)fd表示要關(guān)閉文件的文件描述符。函數(shù)若返回0,表示成功;若返回-1,表示失敗,并設(shè)置出錯(cuò)信息。

3)?read()

read()函數(shù)用于從指定的文件描述符中讀出數(shù)據(jù)。該函數(shù)原型及相關(guān)頭文件如下。

【代碼6-3】read()

#include<unistd.h>

ssize_tread(intfd,void*buf,size_tcount);其中各參數(shù)的含義如下:

fd,文件描述符。

*buf,指定要讀出數(shù)據(jù)的緩存區(qū)。

count,指定讀出的字節(jié)數(shù)。

read()函數(shù)的返回值若為字節(jié)數(shù),表示讀成功并返回讀到的字節(jié)數(shù);若為0,表示已到達(dá)文件尾;若為?-1,表示讀出錯(cuò)。

4)?write()

write()函數(shù)用于向打開的文件描述符中寫入數(shù)據(jù)。該函數(shù)原型及相關(guān)頭文件如下。

【代碼6-4】write()

#include<unistd.h>

ssize_twrite(intfd,constvoid*buf,size_tcount);其中各參數(shù)的含義如下:

fd,文件描述符。

*buf,指定寫入數(shù)據(jù)的緩存區(qū)文件。

count,指定寫入的字節(jié)數(shù)。

write(?)函數(shù)的返回值若為字節(jié)數(shù),表示寫成功并返回寫入的字節(jié)數(shù);若為?-1,表示讀出錯(cuò)。

5)?lseek()

lseek()函數(shù)用于控制文件的讀寫位置。該函數(shù)原型及相關(guān)頭文件如下。【代碼6-5】lseek()

#include<sys/types.h>

#include<unistd.h>

off_tlseek(intfd,off_toffset,intwhence);其中各參數(shù)的含義如下:

fd,文件描述符。

offset,偏移量,每一次讀寫需要移動(dòng)的距離,單位為字節(jié)。

whence,當(dāng)前位置的基點(diǎn)。常用以下幾個(gè)值:

SEEK_SET,當(dāng)前位置為文件開頭。

SEEK_CUR,當(dāng)前位置為文件指針的位置。

SEEK_END,當(dāng)前位置為文件的結(jié)尾。

lseek()函數(shù)的返回值若為字節(jié)數(shù),表示文件的當(dāng)前位移字節(jié)數(shù);若為-1,表示出錯(cuò)。以下代碼用于實(shí)現(xiàn)任務(wù)描述6.D.1——編寫程序,實(shí)現(xiàn)將源文件的最后10KB寫到目標(biāo)文件中。

【描述6.D.1】copy_file.c#include<unistd.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<stdlib.h>#include<stdio.h>#defineBUFFER_SIZE1024/*每次讀寫緩存大小,影響運(yùn)行效率*/#defineSRC_FILE_NAME"src_dh"/*源文件名*/#defineDEST_FILE_NAME"dest_dh"/*目標(biāo)文件名*/#defineOFFSET10240/*復(fù)制的數(shù)據(jù)大小*/intmain(){ intsrc_file,dest_file;

unsignedcharbuff[BUFFER_SIZE];

intreal_read_len;

/*以只讀方式打開源文件*/ src_file=open(SRC_FILE_NAME,O_RDONLY);

/*以只寫方式打開目標(biāo)文件,若此文件不存在,則創(chuàng)建該文件,訪問(wèn)權(quán)限值為644*/ dest_file=open(DEST_FILE_NAME,O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);

if(src_file<0||dest_file<0) { printf("Openfileerror\n");

exit(1);

}

/*將源文件的讀寫指針移到最后10KB的起始位置*/ lseek(src_file,-OFFSET,SEEK_END);

/*讀取源文件的最后10KB數(shù)據(jù)并寫到目標(biāo)文件中,每次讀寫1KB*/

while((real_read_len=read(src_file,buff,sizeof(buff)))>0) { write(dest_file,buff,real_read_len);

}

close(dest_file);

close(src_file);

return0;}程序編譯運(yùn)行后,當(dāng)前目錄多了文件dest_dh,大小為10KB,如圖6-4所示。圖6-4copy.c執(zhí)行結(jié)果

2.文件鎖在多任務(wù)操作系統(tǒng)環(huán)境中,如果一個(gè)用戶對(duì)其他用戶正在讀取的文件進(jìn)行寫操作時(shí),會(huì)導(dǎo)致讀取文件的用戶讀到的是被破壞的文件。Linux采取文件鎖技術(shù)解決了共享資源產(chǎn)生競(jìng)爭(zhēng)的狀態(tài)。

1)文件鎖分類文件鎖分為兩種:

建議性鎖(advisorylock),又稱協(xié)同鎖,要求對(duì)每個(gè)上鎖文件都要檢查是否有鎖存在,并且尊重已有的鎖。內(nèi)核提供加鎖和判斷文件是否加鎖的方法。因此,建議性鎖不能阻止用戶對(duì)文件的操作,只能依賴用戶自覺的檢測(cè)并約束自己的行為。

強(qiáng)制性鎖(mandatorylock),是內(nèi)核強(qiáng)制采用的文件鎖。當(dāng)一個(gè)文件被上強(qiáng)制性鎖時(shí),內(nèi)核將阻止其他用戶對(duì)該文件進(jìn)行讀寫操作。另外,還有針對(duì)文件某一記錄而上的鎖,稱為記錄鎖。記錄鎖也分為兩種:讀取鎖(共享鎖),多個(gè)用戶在同一時(shí)刻可以對(duì)同一文件加讀取鎖。寫入鎖(排斥鎖),某一時(shí)刻的某一部分文件只能由一個(gè)用戶建立寫入鎖。也就是說(shuō),對(duì)同一個(gè)文件來(lái)說(shuō),在某一個(gè)時(shí)刻可以擁有多個(gè)讀者,但在某一時(shí)刻只能有一個(gè)寫者。

2)文件鎖函數(shù)

Linux常使用的文件鎖函數(shù)有:

flock(),在Linux2.6中,此函數(shù)只能實(shí)現(xiàn)對(duì)整個(gè)文件上鎖,不能實(shí)現(xiàn)記錄鎖。

fcntl(),是一款非常強(qiáng)大的文件鎖函數(shù),不但能實(shí)現(xiàn)建議性鎖,而且能實(shí)現(xiàn)強(qiáng)制性鎖,還能對(duì)文件某一記錄上鎖。下面介紹fcntl()函數(shù)的用法。【代碼6-6】fcntl()

#include<unistd.h>

#include<fcntl.h>

intfcntl(intfd,intcmd,.../*arg*/);

fcntl()函數(shù)實(shí)現(xiàn)的功能很多,不僅可以管理文件鎖,還可以改變已打開文件的性質(zhì)等。本節(jié)只介紹有關(guān)文件鎖的內(nèi)容,所以此處函數(shù)原型引申為:

intfcntl(intfd,intcmd,structflock*lock);

其中各參數(shù)的含義如下:

fd,文件描述符。

cmd,指定要進(jìn)行的鎖操作,常用值如下:

F_GETLK,根據(jù)lock參數(shù)值,決定是否上鎖。

F_SETLK,設(shè)置lock參數(shù)值的文件鎖。

F_SETLKW,是F_SETLK的阻塞狀態(tài),W表示wait。

*lock,為flock結(jié)構(gòu)體,設(shè)置記錄鎖的具體狀態(tài)。

fcntl()函數(shù)的返回值若為0,表示成功;若為?-1,表示出錯(cuò)。使用fcntl()函數(shù)對(duì)文件上鎖時(shí),首先應(yīng)判斷文件是否已經(jīng)上鎖,即判斷flock結(jié)構(gòu)體描述的鎖操作。flock結(jié)構(gòu)體定義如下?!窘Y(jié)構(gòu)體6-1】flockstructflock{shortl_type; /*Typeoflock:F_RDLCK,F(xiàn)_WRLCK,F(xiàn)_UNLCK*/shortl_whence; /*Howtointerpretl_start:SEEK_SET,SEEK_CUR,SEEK_END*/off_tl_start; /*Startingoffsetforlock*/off_tl_len; /*Numberofbytestolock*/pid_tl_pid; /*PIDofprocessblockingourlock(F_GETLKonly)*/};其中,flock結(jié)構(gòu)體各成員變量描述如表6-4所示。表6-4flock結(jié)構(gòu)體成員整個(gè)文件上鎖,可以設(shè)置l_start為0、l_whence為SEEK_SET、l_len為0。

4)?fcntl()函數(shù)實(shí)例在使用fcntl()函數(shù)給文件上鎖時(shí),應(yīng)先判斷文件是否可以上鎖,示例中展示了分別調(diào)用fcntl()函數(shù)實(shí)現(xiàn)判斷上鎖條件及給“hello”整個(gè)文件上鎖的方法。

【示例6-5】lock_set.c

/*lock_set.c*/

intlock_set(intfd,inttype)

{ //給整個(gè)文件上鎖

structflockold_lock,lock;

lock.l_whence=SEEK_SET;

lock.l_start=0;

lock.l_len=0;

lock.l_type=type;

lock.l_pid=-1;

/*判斷文件是否可以上鎖*/

fcntl(fd,F(xiàn)_GETLK,&lock);

if(lock.l_type!=F_UNLCK) { /*判斷文件不能上鎖的原因*/ if(lock.l_type==F_RDLCK)/*該文件已有讀取鎖*/ {

printf("Readlockalreadysetby%d\n",lock.l_pid);

} elseif(lock.l_type==F_WRLCK)/*該文件已有寫入鎖*/ { printf("Writelockalreadysetby%d\n",lock.l_pid);

} }

/*l_type可能已被F_GETLK修改過(guò)*/ lock.l_type=type;

/*根據(jù)不同的type值進(jìn)行阻塞式上鎖或解鎖*/ if((fcntl(fd,F(xiàn)_SETLKW,&lock))<0) { printf("Lockfailed:type=%d\n",lock.l_type);

return1;

} switch(lock.l_type) {

caseF_RDLCK: { printf("Readlocksetby%d\n",getpid());

} break;

caseF_WRLCK: {printf("Writelocksetby%d\n",getpid());

} break;

caseF_UNLCK: { printf("Releaselockby%d\n",getpid());

return1;

} break;

default: break;

}/*endofswitch*/ return0;}在lock_set.c子函數(shù)中,主要做了兩部分工作:

(1)第一次調(diào)用fcntl()函數(shù),函數(shù)中的參數(shù)cmd取值F_GETLK,判斷flock結(jié)構(gòu)狀態(tài)是否可以上鎖,若可以上鎖,則l_type被設(shè)置為F_UNLCK;若不可上鎖,則判斷并輸出不可以上鎖的原因。

(2)第二次調(diào)用fcntl()函數(shù),函數(shù)中的參數(shù)cmd取值F_SETLK,根據(jù)type類型進(jìn)行上鎖或解鎖。

3.多路復(fù)用

Linux系統(tǒng)實(shí)現(xiàn)I/O多路復(fù)用有多種方式,通常主要使用以下兩種方式:

使用進(jìn)程或線程來(lái)實(shí)現(xiàn)。

通過(guò)系統(tǒng)調(diào)用select()/poll()函數(shù)來(lái)實(shí)現(xiàn)。二者相比,利用select()/poll()函數(shù)來(lái)實(shí)現(xiàn)多路復(fù)用的效率更高,實(shí)現(xiàn)更容易,在底層使用的資源也更少。

1)?select()

select()函數(shù)用于測(cè)試在文件描述符集合中,是否有一個(gè)文件描述符已處于可讀或可寫的狀態(tài)。例如,在大家所熟知的MCU的仿真器程序中,可以調(diào)用select()處理讀取鍵盤或者串行口等的輸入或輸出。另外,在網(wǎng)絡(luò)服務(wù)器程序中也采用調(diào)用select()的方式處理多個(gè)客戶機(jī)的連接請(qǐng)求。

select()函數(shù)原型及相關(guān)頭文件如下?!敬a6-7】select()#include<sys/select.h>#include<sys/time.h>#include<sys/types.h>#include<unistd.h>intselect(intnfds,fd_set*readfds,fd_set*writefds,fd_set*exceptfds,structtimeval*timeout);其中各參數(shù)的含義如下:

nfds:需要監(jiān)視的文件描述符的最大值。

readfds:需要監(jiān)視的讀文件描述符集合。

writefds:需要監(jiān)視的寫文件描述符集合。

exceptfds:需要監(jiān)視的異常處理文件描述符集合。

timeout:select的超時(shí)時(shí)間,可精確到微秒。其三種取值可使select處于三種狀態(tài):

NULL:永遠(yuǎn)等待,直到捕捉到信號(hào)或文件描述符已經(jīng)準(zhǔn)備好為止。

大于0的值:若等待了timeout值的時(shí)間后沒(méi)有等到任何信號(hào)或文件描述符,則返回。

0:從不等待,測(cè)試所有的描述符并立即返回。

select()函數(shù)的返回值若大于0:表示調(diào)用成功,返回準(zhǔn)備好的文件描述數(shù)目;若為0:表示調(diào)用超時(shí);若為?-1:表示調(diào)用出錯(cuò)。從函數(shù)的定義可以看出,select()函數(shù)主要是對(duì)fd_set數(shù)據(jù)結(jié)構(gòu)進(jìn)行操作。fd_set是打開的文件描述符的集合,這些集合可以由一組宏定義來(lái)控制。宏定義如下。【代碼6-8】fd_set宏操作//將一個(gè)文件描述符從文件描述符集中刪除voidFD_CLR(intfd,fd_set*set);//判斷一個(gè)文件描述符是否是文件描述符集中的一員intFD_ISSET(intfd,fd_set*set);//將一個(gè)文件描述符加入文件描述符集中voidFD_SET(intfd,fd_set*set);//清除一個(gè)文件描述符集voidFD_ZERO(fd_set*set);一般使用select()函數(shù)的步驟如下:

(1)使用前,首先使用FD_ZERO()和FD_SET()來(lái)初始化文件描述符集。

(2)使用中,使用FD_ISSET()來(lái)測(cè)試描述符集。

(3)使用后,使用FD_CLR()來(lái)清除描述符集。

2)?poll()

poll()函數(shù)與select()函數(shù)功能相同,但是使用方法不同,poll()函數(shù)原型及相關(guān)頭文件如下。

【代碼6-9】poll()

#include<poll.h>

intpoll(structpollfd*fds,nfds_tnfds,inttimeout);其中各參數(shù)的含義如下:

fds:指針類型,指向pollfd結(jié)構(gòu)體數(shù)組,每一個(gè)pollfd結(jié)構(gòu)表示一個(gè)文件描述符。

nds:監(jiān)聽的文件描述符數(shù)目。

timeout:指定等待時(shí)間。它有兩種取值:

大于0的值:若等待了timeout值的時(shí)間沒(méi)有等到任何信號(hào)或文件描述符,則返回。

0:無(wú)限等待。

poll()函數(shù)的返回值若大于0,表示調(diào)用成功,返回pollfd結(jié)構(gòu)的個(gè)數(shù);若為0,表示調(diào)用超時(shí);若為?-1,表示調(diào)用出錯(cuò)。

poll()函數(shù)所操作的pollfd結(jié)構(gòu)體定義如下。

【結(jié)構(gòu)體6-2】pollfdstructpollfd{ intfd; /*需要監(jiān)聽的文件描述符*/ shortevents; /*需要監(jiān)聽的事件*/ shortrevents;/*已發(fā)生的事件*/}其中,pollfd結(jié)構(gòu)體的各個(gè)成員的含義如表6-5所示。表6-5pollfd結(jié)構(gòu)體成員例如,要監(jiān)視一個(gè)文件是否可讀或可寫,可設(shè)置events為POLLIN|POLLOUT。6.2.3標(biāo)準(zhǔn)I/O函數(shù)標(biāo)準(zhǔn)I/O函數(shù)的主要特點(diǎn)有:

帶緩存區(qū)。

基于文件流對(duì)文件進(jìn)行讀寫操作。標(biāo)準(zhǔn)I/O函數(shù)與基本I/O函數(shù)相比,在對(duì)文件進(jìn)行讀寫操作時(shí),增加了緩存區(qū)的利用,減少了系統(tǒng)調(diào)用的次數(shù),提高了程序的效率。

1.基本操作同基本I/O函數(shù)一樣,標(biāo)準(zhǔn)I/O函數(shù)的基本操作函數(shù)有fopen(?)、fclose(?)、fread(?)和fwrite()。標(biāo)準(zhǔn)I/O函數(shù)定義在<stdio.h>中。

1)?fopen()文件流的打開函數(shù)有三個(gè)標(biāo)準(zhǔn)函數(shù):fopen()、fdopen()、freopen()。該函數(shù)原型及相關(guān)頭文件如下。

【代碼6-10】fopen()#include<stdio.h>FILE*fopen(constchar*path,constchar*mode);FILE*fdopen(intfd,constchar*mode);FILE*freopen(constchar*path,constchar*mode,F(xiàn)ILE*stream);三個(gè)函數(shù)的參數(shù)和功能如表6-6所示。

表6-6標(biāo)準(zhǔn)打開函數(shù)文件打開成功后,對(duì)文件的讀寫都是通過(guò)FILE指針來(lái)進(jìn)行的。其中,函數(shù)中的mode參數(shù)可以定義打開文件的訪問(wèn)權(quán)限,表6-7描述了mode的不同取值,后面加b字符的取值表示打開的文件為二進(jìn)制文件,而不是純文本文件。表6-7打開函數(shù)mode取值

2)?fclose()文件流的關(guān)閉函數(shù)為fclose(),該函數(shù)將緩存區(qū)的數(shù)據(jù)全部寫入到文件中,并釋放系統(tǒng)所提供的文件資源。該函數(shù)原型及相關(guān)頭文件如下。

【代碼6-11】fclose()

#include<stdio.h>

intfclose(FILE*fp);文件關(guān)閉若成功,函數(shù)返回0;若失敗,函數(shù)返回EOF。

3)?fread()

fread()函數(shù)實(shí)現(xiàn)了對(duì)已打開文件流的讀操作。該函數(shù)原型及相關(guān)頭文件如下。

【代碼6-12】fread()

#include<stdio.h>

size_tfread(void*ptr,size_tsize,size_tnmemb,F(xiàn)ILE*stream);其中各參數(shù)的含義如下。

ptr:存放讀入記錄的緩存區(qū)。

size:讀取的記錄大小。

nmemb:讀取的記錄數(shù)。

stream:要讀取的文件流。

fread()函數(shù)的返回值若為實(shí)際要讀取到的nmemb,則表示成功;若為EOF,則表示失敗。

4)?fwrite()fwite()函數(shù)實(shí)現(xiàn)了對(duì)已打開的文件流的寫入操作。該函數(shù)原型及相關(guān)頭文件如下。【代碼6-13】fwrite()#include<stdio.h>size_tfwrite(constvoid*ptr,size_tsize,size_tnmemb,F(xiàn)ILE*stream);其中各參數(shù)的含義如下:ptr:存放寫入記錄的緩存區(qū)。size:寫入的記錄大小。nmemb:寫入的記錄數(shù)。stream:要寫入的文件流。fwrite()函數(shù)的返回值若為實(shí)際要寫入的nmemb,則表示成功;若為EOF,則表示失敗。標(biāo)準(zhǔn)I/O函數(shù)與基本I/O函數(shù)的使用方法基本相同,可以用標(biāo)準(zhǔn)I/O函數(shù)代替任務(wù)描述6.D.1中的函數(shù),結(jié)果相同。

【代碼6-14】標(biāo)準(zhǔn)I/O函數(shù)使用src_file=fopen(SRC_FILE_NAME,"r");dest_file=fopen(DEST_FILE_NAME,"w");fseek(src_file,-OFFSET,SEEK_END);fread(buff,1,sizeof(buff),src_file))fwrite(buff,1,real_read_len,dest_file);fclose(dest_file);fclose(src_file);

2.輸入/輸出

根據(jù)文件中的字符數(shù)目,可將文件的輸入/輸出分為三種方式:

字符輸入/輸出,每次僅輸入/輸出一個(gè)字符。

行輸入/輸出,每次輸入/輸出一行字符。

格式化輸入/輸出,按照函數(shù)的具體格式進(jìn)行輸入/輸出。輸入/輸出用到的標(biāo)準(zhǔn)操作函數(shù)的定義在標(biāo)準(zhǔn)庫(kù)<stdio.h>頭文件中。1)字符輸入/輸出字符輸入/輸出的操作函數(shù)如表6-8所示。表6-8字符輸入/輸出函數(shù)這幾個(gè)函數(shù)的功能基本相同,是以字符形式進(jìn)行操作的。2)行輸入/輸出行輸入/輸出的操作函數(shù)如表6-9所示。

表6-9行輸入/輸出函數(shù)3)格式化輸入/輸出格式化輸入/輸出的操作函數(shù)如表6-10所示。表6-10格式化輸入/輸出函數(shù)格式化輸入/輸出函數(shù)比較常用,本書不再進(jìn)行詳細(xì)的描述了。

3.其他操作

glibc標(biāo)準(zhǔn)庫(kù)里還提供了針對(duì)文件流的其他操作函數(shù),例如fseek()、setbuf()等。在具體實(shí)際應(yīng)用中可以使用man命令查詢相關(guān)函數(shù)的詳細(xì)用法。6.3進(jìn)程

6.3.1概述進(jìn)程是20世紀(jì)60年代初首先由麻省理工學(xué)院的MULTICS系統(tǒng)和IBM公司的CTSS/360系統(tǒng)引入的,到目前已經(jīng)有40多年的發(fā)展歷史。進(jìn)程是現(xiàn)代操作系統(tǒng)中最基本也是最重要的概念,它清晰地刻畫了系統(tǒng)內(nèi)部程序的運(yùn)行動(dòng)態(tài),有效地管理和調(diào)度了操作系統(tǒng)內(nèi)程序的運(yùn)行。

1.進(jìn)程定義進(jìn)程可以從狹義和廣義兩個(gè)方面來(lái)定義:從狹義方面來(lái)說(shuō),進(jìn)程就是一段程序的執(zhí)行過(guò)程;從廣義方面來(lái)說(shuō),進(jìn)程是一個(gè)具有一定獨(dú)立功能的程序關(guān)于某個(gè)數(shù)據(jù)集合的一次運(yùn)行活動(dòng),是操作系統(tǒng)動(dòng)態(tài)執(zhí)行的基本單元。概括來(lái)講,進(jìn)程作為系統(tǒng)內(nèi)的活動(dòng)實(shí)體,是操作系統(tǒng)進(jìn)行調(diào)度和資源分配的基本單位。進(jìn)程的啟動(dòng)與程序的執(zhí)行密切相關(guān),但與程序有著本質(zhì)的區(qū)別,區(qū)別如下:

程序是靜態(tài)概念,本身作為一種軟件資源而長(zhǎng)期保存。進(jìn)程是程序的執(zhí)行過(guò)程,它是動(dòng)態(tài)概念,有一定的生命期,是動(dòng)態(tài)地產(chǎn)生和消亡的。

程序和進(jìn)程無(wú)一一對(duì)應(yīng)關(guān)系,體現(xiàn)在兩個(gè)方面,一個(gè)程序可以由多個(gè)進(jìn)程共用;一個(gè)進(jìn)程在活動(dòng)中可能順序地執(zhí)行若干個(gè)程序。例如:系統(tǒng)里不管有多少個(gè)進(jìn)程在運(yùn)行printf()函數(shù),在內(nèi)存里卻只有一份函數(shù)源碼。

每個(gè)進(jìn)程都是由父進(jìn)程啟動(dòng)的,而這個(gè)新啟動(dòng)的進(jìn)程則稱為子進(jìn)程。由Linux系統(tǒng)的啟動(dòng)過(guò)程可知,在內(nèi)核自檢結(jié)束后,將啟動(dòng)第1號(hào)進(jìn)程init,該進(jìn)程將進(jìn)行系統(tǒng)的初始化工作,并管理其他進(jìn)程。也就是說(shuō),init進(jìn)程是系統(tǒng)中的祖先進(jìn)程,其他進(jìn)程要么是由init進(jìn)程啟動(dòng)的,要么是由init進(jìn)程啟動(dòng)的進(jìn)程啟動(dòng)的。

2.進(jìn)程號(hào)PID

PID(ProcessIdenityNumber,進(jìn)程號(hào))是一個(gè)進(jìn)程的唯一標(biāo)識(shí),父進(jìn)程號(hào)為PPID(ParentPID)。PID和PPID都是非零正整數(shù)(1~32768)。init進(jìn)程的PID為1。當(dāng)一個(gè)進(jìn)程被啟動(dòng)時(shí),它會(huì)被分配一個(gè)未使用的編號(hào)作為PID。當(dāng)進(jìn)程終止后,PID號(hào)可被再次使用。當(dāng)前進(jìn)程及其父進(jìn)程的進(jìn)程號(hào)分別利用系統(tǒng)的調(diào)用函數(shù)getpid(?)與getppid()獲得。

3.進(jìn)程結(jié)構(gòu)

Linux系統(tǒng)是一個(gè)多進(jìn)程的系統(tǒng),各個(gè)進(jìn)程并行運(yùn)行、互不干擾。也就是說(shuō),每一個(gè)進(jìn)程都運(yùn)行在獨(dú)立的虛擬地址空間內(nèi)。

Linux中的進(jìn)程包含三個(gè)段,分別為數(shù)據(jù)段、代碼段和堆棧段。

數(shù)據(jù)段:存放的是全局變量、常數(shù)以及動(dòng)態(tài)數(shù)據(jù)分配的數(shù)據(jù)空間,根據(jù)存放的數(shù)據(jù)不同,數(shù)據(jù)段又分成以下幾種:

普通數(shù)據(jù)段:包括可讀可寫/只讀數(shù)據(jù)段,存放靜態(tài)初始化的全局變量或常量。

BSS數(shù)據(jù)段:存放未初始化的全局變量。

堆:存放動(dòng)態(tài)分配的數(shù)據(jù)。

代碼段:存放的是程序的代碼。

堆棧段:存放的是子程序的返回地址、子程序的參數(shù)以及程序的局部變量等。

4.進(jìn)程運(yùn)行狀態(tài)進(jìn)程執(zhí)行時(shí)的間斷性,決定了進(jìn)程具有以下三種運(yùn)行狀態(tài):就緒態(tài)、運(yùn)行態(tài)和阻塞態(tài)。三種狀態(tài)可以相互轉(zhuǎn)換,如圖6-5所示。其中:

就緒態(tài)(Ready):進(jìn)程已具備執(zhí)行的一切條件,等待系統(tǒng)分配CPU進(jìn)行處理。

運(yùn)行態(tài)(Running):進(jìn)程正在運(yùn)行,占用CPU。若沒(méi)有其他進(jìn)程可執(zhí)行時(shí),系統(tǒng)通常會(huì)自動(dòng)執(zhí)行空閑進(jìn)程。

阻塞態(tài)(Blocked):進(jìn)程在等待滿足條件的事件發(fā)生,若條件不能滿足,進(jìn)程無(wú)法繼續(xù)執(zhí)行。圖6-5進(jìn)程運(yùn)行狀態(tài)

5.進(jìn)程數(shù)據(jù)結(jié)構(gòu)進(jìn)程是通過(guò)進(jìn)程控制塊來(lái)進(jìn)行描述的,包含了進(jìn)程的描述信息、控制信息以及資源信息。進(jìn)程控制塊的數(shù)據(jù)結(jié)構(gòu)是structtask_struct,定義在include/linux/sched.h中。

task_struct結(jié)構(gòu)體非常龐大,本書不再詳細(xì)介紹,只將結(jié)構(gòu)體成員分類歸納,以使讀者了解進(jìn)程控制塊的內(nèi)容,增進(jìn)對(duì)進(jìn)程的理解。task_struct結(jié)構(gòu)從邏輯上可分為:

進(jìn)程運(yùn)行狀態(tài)。

進(jìn)程調(diào)度信息。

進(jìn)程標(biāo)識(shí)符。

處理器相關(guān)信息。

進(jìn)程間的鏈接。

時(shí)間和定時(shí)器。

文件系統(tǒng)信息。

虛擬內(nèi)存信息。

信號(hào)處理信息。

Linux將所有task_struct結(jié)構(gòu)的指針都存儲(chǔ)在task數(shù)組中,數(shù)組的大小為系統(tǒng)所能容納的進(jìn)程數(shù)目。系統(tǒng)通過(guò)task數(shù)組管理系統(tǒng)中所有的進(jìn)程。

6.進(jìn)程管理

Linux下的進(jìn)程管理包括啟動(dòng)進(jìn)程和調(diào)度進(jìn)程。

1)啟動(dòng)進(jìn)程有兩種方式可以啟動(dòng)進(jìn)程:

手工啟動(dòng)。手工啟動(dòng)又分兩種方式:

前臺(tái)啟動(dòng):直接在終端中輸入程序名(外部命令名)的啟動(dòng)方式,例如,vim。

后臺(tái)啟動(dòng):輸入程序名時(shí)加“&”的啟動(dòng)方式,例如,vim&。

調(diào)度啟動(dòng)。指定系統(tǒng)在特定時(shí)間運(yùn)行程序,例如,at命令在指定時(shí)刻執(zhí)行相關(guān)進(jìn)程、cron命令可以自動(dòng)周期性地執(zhí)行相關(guān)進(jìn)程。

2)調(diào)度進(jìn)程調(diào)度進(jìn)程是通過(guò)進(jìn)程管理工具實(shí)現(xiàn)的,常用的有以下幾個(gè)工具命令:

ps:查詢列舉進(jìn)程。

pgrep:按名字查詢進(jìn)程。

pstree:顯示進(jìn)程樹。

kill:殺死進(jìn)程。例如,在終端中運(yùn)行psaux命令。選項(xiàng)組合aux表示按用戶名和啟動(dòng)順序來(lái)顯示所有的進(jìn)程。

【示例6-6】ps命令

$psaux執(zhí)行結(jié)果如圖6-6所示。

圖6-6ps命令執(zhí)行結(jié)果從圖中可以看出,系統(tǒng)啟動(dòng)的1號(hào)進(jìn)程為init進(jìn)程,進(jìn)程的詳細(xì)信息包括啟動(dòng)進(jìn)程的用戶(USER)、進(jìn)程號(hào)(PID)、占用內(nèi)存(%CPU)、啟動(dòng)時(shí)間(TIME)等。6.3.2基本函數(shù)一個(gè)進(jìn)程的生命周期有三個(gè)階段,分別為:

創(chuàng)建進(jìn)程,通過(guò)fork()函數(shù)實(shí)現(xiàn)。

進(jìn)程相關(guān)操作(例如:查找或讀寫文件),通過(guò)exec()函數(shù)族操作。

終止進(jìn)程,通過(guò)exit()或_exit()函數(shù)實(shí)現(xiàn)。下面分別描述各個(gè)階段的實(shí)現(xiàn)函數(shù)的用法。

1.?fork()函數(shù)

fork()函數(shù)是Linux創(chuàng)建新進(jìn)程的唯一方法,用于從已存在的父進(jìn)程中創(chuàng)建一個(gè)子進(jìn)程,且子進(jìn)程為父進(jìn)程的復(fù)制品,但子進(jìn)程有自己的進(jìn)程號(hào)和數(shù)據(jù)段等。該函數(shù)原型及相關(guān)頭文件如下。

【代碼6-15】fork()

#include<unistd.h>

pid_tfork(void);其中函數(shù)的返回值為pid_t類型,有兩種情況,分別為:

創(chuàng)建成功,則有兩個(gè)返回值,為0,表示此進(jìn)程為子進(jìn)程;為子進(jìn)程的PID,表示此進(jìn)程為父進(jìn)程。

創(chuàng)建失敗,則返回值為?-1。

fork(?)函數(shù)的執(zhí)行過(guò)程如圖6-7所示。

圖6-7fork()函數(shù)的執(zhí)行過(guò)程

fork(?)函數(shù)與其他函數(shù)的不同之處就是執(zhí)行成功后會(huì)有兩個(gè)返回值。在父進(jìn)程執(zhí)行此函數(shù)時(shí),父進(jìn)程會(huì)復(fù)制出一個(gè)子進(jìn)程,而且父、子進(jìn)程的源碼從fork()函數(shù)的返回開始分別在兩個(gè)地址空間中同時(shí)運(yùn)行。父進(jìn)程中的返回值是子進(jìn)程的進(jìn)程號(hào),子進(jìn)程中返回0。因此,在程序中可以通過(guò)返回值來(lái)判定該進(jìn)程是父進(jìn)程還是子進(jìn)程。下面的源碼用于實(shí)現(xiàn)任務(wù)描述6.D.2——?jiǎng)?chuàng)建子進(jìn)程,觀察父進(jìn)程和子進(jìn)程的運(yùn)行現(xiàn)象?!久枋?.D.2】fork.c#include<sys/types.h>#include<unistd.h>#include<stdio.h>#include<stdlib.h>intmain(void){ pid_tresult;

/*調(diào)用fork()函數(shù)*/ result=fork();

/*通過(guò)result的值來(lái)判斷fork()函數(shù)的返回情況,首先進(jìn)行出錯(cuò)處理*/

圖6-8fork.c運(yùn)行結(jié)果

注意:fork()函數(shù)使用一次就創(chuàng)建一個(gè)進(jìn)程。若把fork()函數(shù)放在了ifelse判斷語(yǔ)句中則要小心,不能多次使用fork()函數(shù)。

2.?exec()函數(shù)族

exec()函數(shù)族提供了在進(jìn)程中啟動(dòng)一個(gè)程序執(zhí)行的方法。它可以根據(jù)指定的文件名或目錄名找到可執(zhí)行文件,并用它來(lái)取代原調(diào)用進(jìn)程的數(shù)據(jù)段、代碼段和堆棧段,在執(zhí)行完之后,原調(diào)用進(jìn)程的內(nèi)容除了進(jìn)程號(hào)外,其他全部被新的進(jìn)程替換了。另外,這里的可執(zhí)行文件既可以是二進(jìn)制文件,也可以是Linux下任何可執(zhí)行的腳本文件。

exec()函數(shù)族由6個(gè)以exec開頭的函數(shù)組成,函數(shù)原型及相關(guān)頭文件如下。

【代碼6-16】exec函數(shù)族#include<unistd.h>externchar**environ;intexecl(constchar*path,constchar*arg,...);intexeclp(constchar*file,constchar*arg,...);intexecle(constchar*path,constchar*arg,...,char*constenvp[]);intexecv(constchar*path,char*constargv[]);intexecve(constchar*path,char*constargv[],char*constenvp[]);intexecvp(constchar*file,char*constargv[]);這6個(gè)函數(shù)的不同之處表現(xiàn)在3個(gè)方面,分別是:

按查找文件的方式分,有兩種:

按完整的文件目錄路徑查找,傳入?yún)?shù)為?*path,如execl(?)、execle(?)、execv(?)、execve()。

按文件名查找,傳入?yún)?shù)為?*file,如execlp()、execvp()。

按參數(shù)傳遞的方式分,包括兩種:

列舉方式,即函數(shù)名中有“l(fā)”(list),參數(shù)為?*arg,如execl()、execle()、execlp()。

結(jié)構(gòu)指針數(shù)組方式,即函數(shù)名中有“v”(vertor),參數(shù)為argv[?],如execv()、execve()、execvp()。此處所說(shuō)的參數(shù)實(shí)際上是用戶使用可執(zhí)行文件時(shí)所需的全部命令選項(xiàng)字符串,必須用NULL字符表示結(jié)束。

按環(huán)境變量分,exec函數(shù)族可以默認(rèn)系統(tǒng)的環(huán)境變量PATH,也可以傳入指定的環(huán)境變量。以“e”結(jié)尾的exec函數(shù)可以在參數(shù)envp[?]中指定當(dāng)前進(jìn)程所用的環(huán)境變量,例如execle()和execve()。例如,在子進(jìn)程中調(diào)用exec()函數(shù)族實(shí)現(xiàn)“psaux”命令,可以利用execlp()函數(shù)或execl()函數(shù)實(shí)現(xiàn),只要分別使用按文件名或文件目錄查找可執(zhí)行文件即可。

【示例6-7】execlp()

execlp("ps","ps","aux",NULL)

【示例6-8】execl()

execl("/bin/ps","ps","auf",NULL)注意:在使用exec()函數(shù)族時(shí),要加上判斷語(yǔ)句,因?yàn)楹瘮?shù)在執(zhí)行過(guò)程中若找不到文件或沒(méi)有文件執(zhí)行權(quán)限,執(zhí)行將會(huì)失敗。以下代碼簡(jiǎn)要地描述了函數(shù)族的使用。

【示例6-9】execlp.c#include<unistd.h>#include<stdio.h>#include<stdlib.h>intmain(){ //創(chuàng)建子進(jìn)程,在子進(jìn)程中調(diào)用execlp()函數(shù)

if(fork()==0) {

//調(diào)用execlp()函數(shù),這里相當(dāng)于調(diào)用了"psaux"命令

if((execlp("ps","ps","aux",NULL))<0)

{ printf("Execlperror\n");

}

}}上述代碼首先使用fork()函數(shù)創(chuàng)建子進(jìn)程,然后在子進(jìn)程里調(diào)用execlp()函數(shù)。執(zhí)行execlp()函數(shù)時(shí),系統(tǒng)在默認(rèn)的環(huán)境變量PATH下尋找文件名為ps、aux的可執(zhí)行文件,并執(zhí)行此文件。運(yùn)行交叉編譯后的可執(zhí)行程序,結(jié)果同圖6-6所示。也就是execlp(?)函數(shù)相當(dāng)于實(shí)現(xiàn)了在shell終端下運(yùn)行psauf命令。

3.?_exit()與exit()

_exit(?)函數(shù)與exit(?)函數(shù)都是用來(lái)終止進(jìn)程的,最終都是進(jìn)行了exit系統(tǒng)調(diào)用,但是兩個(gè)函數(shù)的執(zhí)行過(guò)程有所不同,如圖6-9所示。

圖6-9_exit()函數(shù)與exit()函數(shù)從圖中可以看出:

_exit()函數(shù)直接使進(jìn)程停止,并清除內(nèi)存空間。

exit()函數(shù)在進(jìn)程終止之前,先檢查文件的打開情況,并對(duì)文件緩存區(qū)的文件進(jìn)行處理,然后終止進(jìn)程。兩個(gè)函數(shù)的原型及相關(guān)頭文件如下。

【代碼6-17】_exit()與exit()#include<unistd.h>void_exit(intstatus);#include<stdlib.h>voidexit(intstatus);其中參數(shù)status一般為0,表示正常結(jié)束,例如exit(0)。從以下示例中可以看出兩個(gè)函數(shù)不同的應(yīng)用過(guò)程?!臼纠?-10】exit.c#include<stdio.h>#include<stdlib.h>intmain(){ printf("Usingexit...\n");

printf("Thisisthecontentinbuffer");

exit(0);}程序編譯后運(yùn)行,結(jié)果如圖6-10所示。

圖6-10exit()函數(shù)執(zhí)行結(jié)果【示例6-11】_exit.c#include<stdio.h>#include<unistd.h>intmain(){ printf("Using_exit...\n");

printf("Thisisthecontentinbuffer");

_exit(0);}程序編譯后運(yùn)行,結(jié)果如圖6-11所示。

圖6-11_exit()函數(shù)執(zhí)行結(jié)果從運(yùn)行結(jié)果來(lái)看,前者執(zhí)行exit()函數(shù)時(shí),緩存區(qū)內(nèi)的數(shù)據(jù)全部輸出;后者執(zhí)行_exit()函數(shù)時(shí),緩存區(qū)內(nèi)的數(shù)據(jù)沒(méi)有輸出,直接終止了進(jìn)程。6.3.3信號(hào)

Linux系統(tǒng)是多任務(wù)系統(tǒng),每個(gè)任務(wù)可能由一個(gè)進(jìn)程完成,也可能由若干個(gè)進(jìn)程完成。由于每個(gè)進(jìn)程工作在獨(dú)立的工作區(qū)間,不同的進(jìn)程不能訪問(wèn)到對(duì)方的內(nèi)存空間,因而進(jìn)程間需要通過(guò)某種方式進(jìn)行通信。

Linux系統(tǒng)常用的進(jìn)程間的通信方式有信號(hào)、管道、信號(hào)量、共享內(nèi)存和消息隊(duì)列等幾種機(jī)制。本節(jié)內(nèi)容針對(duì)信號(hào)進(jìn)行描述。

1.信號(hào)特點(diǎn)信號(hào)(SIGNAL)是Linux系統(tǒng)響應(yīng)某些條件而產(chǎn)生的一個(gè)事件,是古老的進(jìn)程間的通信方法。信號(hào)的特點(diǎn)如下:

是一種軟件中斷機(jī)制。

傳遞用戶進(jìn)程與內(nèi)核進(jìn)程的交互信息。

異步通信模式。

每個(gè)信號(hào)都有一個(gè)名字,且名稱以SIG開頭。

2.信號(hào)產(chǎn)生當(dāng)引發(fā)信號(hào)的事件發(fā)生時(shí),為進(jìn)程產(chǎn)生了一個(gè)信號(hào),有兩種引發(fā)信號(hào)的事件:硬件產(chǎn)生事件,例如按下鍵盤或其他硬件發(fā)生故障。軟件產(chǎn)生事件,例如除0操作或執(zhí)行kill()函數(shù)、raise()函數(shù)等。一個(gè)完整的信號(hào)周期包括信號(hào)的產(chǎn)生、信號(hào)在進(jìn)程內(nèi)的注冊(cè)與注銷以及信號(hào)處理函數(shù)的執(zhí)行這三個(gè)階段。

3.信號(hào)處理進(jìn)程收到信號(hào)時(shí),有三種處理方式:捕捉信號(hào),當(dāng)信號(hào)發(fā)生時(shí),進(jìn)程可執(zhí)行相應(yīng)的自定義處理函數(shù)。忽略信號(hào),對(duì)該信號(hào)不做任何處理,但SIGKILL與SIGSTOP信號(hào)除外。執(zhí)行默認(rèn)操作,Linux系統(tǒng)對(duì)每種信號(hào)都規(guī)定了默認(rèn)操作。

4.信號(hào)列表信號(hào)的定義在內(nèi)核目錄include/signal.h中。其中,Linux系統(tǒng)支持的常用信號(hào)列表如表6-11所示。

表6-11信號(hào)列表

5.信號(hào)操作函數(shù)信號(hào)的操作函數(shù)包括信號(hào)發(fā)送函數(shù)與信號(hào)處理函數(shù),其中發(fā)送函數(shù)主要有kill()、raise()、alarm()、pause(),處理函數(shù)主要有signal()。下面分別描述各個(gè)函數(shù)的用法。

1)?kill()函數(shù)

kill()函數(shù)用于向自身或其他進(jìn)程發(fā)送信號(hào),與shell中的kill命令作用相同。該函數(shù)原型及相關(guān)頭文件如下。

【代碼6-18】kill()#include<sys/types.h>#include<signal.h>intkill(pid_tpid,intsig);其中各個(gè)參數(shù)的含義分別為:pid,進(jìn)程號(hào)。其設(shè)定值如下:

正整數(shù):要發(fā)送信號(hào)的進(jìn)程號(hào)。

0:信號(hào)被發(fā)送到與當(dāng)前進(jìn)程同一進(jìn)程組的所有進(jìn)程。

-1:信號(hào)發(fā)給所有進(jìn)程表中的進(jìn)程。

<-1:信號(hào)發(fā)給進(jìn)程組號(hào)為?-pid的每個(gè)進(jìn)程。

sig:要發(fā)送的信號(hào)。

kill()函數(shù)執(zhí)行成功后返回0,失敗則返回?-1。函數(shù)執(zhí)行失敗的最常見的原因有發(fā)送進(jìn)程權(quán)限不夠、信號(hào)名稱不對(duì)或目標(biāo)進(jìn)程不存在。如下代碼向當(dāng)前進(jìn)程發(fā)送SIGALRM信號(hào)。

【示例6-12】發(fā)送SIGALRM信號(hào)

kill(getppid(),SIGALRM);

2)?raise()函數(shù)

raise()函數(shù)用于進(jìn)程向自身發(fā)送信號(hào)。該函數(shù)原型及相關(guān)頭文件如下。

【代碼6-19】raise()

#include<signal.h>

intraise(intsig);其中參數(shù)sig為要發(fā)送的信號(hào)。

3)?alarm()函數(shù)

alarm()函數(shù)也稱為鬧鐘函數(shù),是專門為信號(hào)SIGALARM而設(shè)置的。在指定的時(shí)間后,該函數(shù)向進(jìn)程本身發(fā)送SIGALARM信號(hào)。該函數(shù)原型及相關(guān)頭文件如下。

【代碼6-20】alarm()

#include<unistd.h>

unsignedintalarm(unsignedintseconds);函數(shù)中的定時(shí)單位為second(秒)。例如5秒后發(fā)送SIGALARM信號(hào)為alarm(5)。需要注意的是,一個(gè)進(jìn)程中只能有一個(gè)alarm(?)函數(shù),調(diào)用alarm(?)函數(shù)之后,任何的alarm()函數(shù)將無(wú)效。

4)?pause()函數(shù)

pause()函數(shù)用于將調(diào)用進(jìn)程掛起直至捕捉到信號(hào)為止,通常用于判斷信號(hào)是否到達(dá)。該函數(shù)原型及相關(guān)頭文件如下。

【代碼6-21】pause()

#include<unistd.h>

intpause(void);

alarm()函數(shù)與pause()函數(shù)可以組合實(shí)現(xiàn)sleep()函數(shù)功能,示例源碼如下。

【示例6-13】sleep.c#include<unistd.h>#include<stdio.h>#include<stdlib.h>intmain(){ //調(diào)用alarm定時(shí)器函數(shù)

intret=alarm(5);

pause();

printf("Ihavebeenwakenup.\n",ret);}上述代碼中先調(diào)用alarm(5),再將進(jìn)程掛起,等待5秒后SIGALARM信號(hào)到來(lái)時(shí)為止。5)?signal()函數(shù)進(jìn)行信號(hào)處理時(shí),signal()函數(shù)指出要處理的信號(hào)和函數(shù)的信息。該函數(shù)原型及相關(guān)頭文件如下?!敬a6-22】signal()#include<signal.h>typedefvoid(*sighandler_t)(int);sighandler_tsignal(intsignum,sighandler_thandler);其中傳入?yún)?shù)的含義如下:signum:信號(hào)代碼。handler:信號(hào)處理信息,分為三種情況。

SIG_IGN:忽略該信號(hào)。

SIG_DFL:以默認(rèn)方式處理該信號(hào)。

自定義信號(hào)處理函數(shù)指針,返回類型為void。返回值:若成功,返回以前的信號(hào)處理配置或者處理函數(shù);若失敗,返回?-1。在實(shí)際編程中,signal()函數(shù)并不被推薦使用,但在一些老程序中會(huì)有應(yīng)用。為了兼容,本書只對(duì)此函數(shù)做了簡(jiǎn)單介紹。

6)?sigaction()函數(shù)

Linux系統(tǒng)支持一個(gè)更健壯、更新的信號(hào)處理函數(shù)sigaction()。顧名思義,此函數(shù)的作用是定義在接收到信號(hào)后應(yīng)該采取的處理方式。函數(shù)原型及相關(guān)頭文件如下。

【代碼6-23】sigaction()

#include<signal.h>

intsigaction(intsignum,conststructsigaction*act,structsigaction*oldact);

其中傳入?yún)?shù)的含義如下:

signum:信號(hào)代碼。

act:信號(hào)處理信息,指向sigaction結(jié)構(gòu)的指針。

oldact:原來(lái)相應(yīng)信號(hào)的處理,也是指向sigaction結(jié)構(gòu)的指針。返回值:若成功,返回0;若失敗,返回-1。

sigaction()函數(shù)用到的信號(hào)處理結(jié)構(gòu)為sigaction結(jié)構(gòu),定義如下?!窘Y(jié)構(gòu)體6-3】sigactionstructsigaction{ void(*sa_handler)(int);

sigset_tsa_mask;

intsa_flags;

};其中,結(jié)構(gòu)體的關(guān)鍵成員的含義如表6-12所示。表6-12sigaction結(jié)構(gòu)體成員由sigaction()函數(shù)設(shè)置的信號(hào)處理函數(shù)在默認(rèn)情況下是不被重置的,如果希望信號(hào)被重置,可將sa_flags設(shè)置為SA_RESETHAND。以下內(nèi)容用于實(shí)現(xiàn)任務(wù)描述6.D.3——自定義信號(hào)處理函數(shù)?!久枋?.D.3】signal.c/*signal.c*/#include<signal.h>#include<stdio.h>#include<stdlib.h>/*自定義信號(hào)處理函數(shù)*/voidmy_func(intsign_no){ if(sign_no==SIGINT) { printf("IhavegetSIGINT\n");

} elseif(sign_no==SIGQUIT)

{ printf("IhavegetSIGQUIT\n");

}}intmain(){

structsigactionaction;

printf("WaitingforsignalSIGINTorSIGQUIT...\n");

/*sigaction結(jié)構(gòu)初始化*/ action.sa_handler=my_func; sigemptyset(&action.sa_mask);

action.sa_flags=0;

/*發(fā)出相應(yīng)的信號(hào),并跳轉(zhuǎn)到信號(hào)處理函數(shù)處*/

sigaction(SIGINT,&action,0);

sigaction(SIGQUIT,&action,0);

pause();

exit(0);}程序運(yùn)行結(jié)果如圖6-12所示。

圖6-12sigaction()函數(shù)執(zhí)行過(guò)程

7)信號(hào)集函數(shù)組信號(hào)集是描述信號(hào)的集合。在頭文件signal.h中定義了一組用來(lái)處理信號(hào)集的函數(shù),其函數(shù)原型及相關(guān)頭文件如下。

【代碼6-24】信號(hào)集函數(shù)

#include<signal.h>

intsigemptyset(sigset_t*set);

intsigfillset(sigset_t*set);

intsigaddset(sigset_t*set,intsignum);

intsigdelset(sigset_t*set,intsignum);

intsigismember(sigset_t*set,intsignum);其中,參數(shù)set為需要設(shè)置的信號(hào)集,參數(shù)signum為指定的信號(hào)代碼。信號(hào)集函數(shù)的功能分別如下所示:

sigemptyset():將set信號(hào)集初始化為空。

igfillset():將系統(tǒng)所持的所有信號(hào)包含進(jìn)set信號(hào)集。

sigaddset():將signum信號(hào)加入set信號(hào)集。

sigdelset():從set信號(hào)集刪除signum信號(hào)。

sigismember():判斷signum信號(hào)是否在set信號(hào)集中。函數(shù)在調(diào)用成功時(shí)返回0,失敗時(shí)返回?-1。

8)信號(hào)阻塞相關(guān)函數(shù)

Linux中的每個(gè)進(jìn)程都有一個(gè)屏蔽字,用來(lái)描述哪些信號(hào)是將被阻塞的信號(hào),這些信號(hào)形成一個(gè)信號(hào)集。該信號(hào)集中的所有信號(hào)在送到進(jìn)程后將被阻塞。因此,信號(hào)集的信號(hào)并不是可以處理的信號(hào),只有當(dāng)信號(hào)處于非阻塞狀態(tài)時(shí)才會(huì)起作用。與信號(hào)阻塞相關(guān)的函數(shù)有sigprocmask()、sigpending()和sigsuspend(),其函數(shù)原型及相關(guān)頭文件如下?!敬a6-25】信號(hào)阻塞函數(shù)

#include<signal.h>

intsigprocmask(inthow,constsigset_t*set,sigset_t*oldset);

intsigpending(sigset_t*set);

intsigsuspend(constsigset_t*mask);這幾個(gè)函數(shù)的作用分別如下:

sigprocmask():將set信號(hào)集加入進(jìn)程的屏蔽字中。

sigpending():獲得當(dāng)前已被送到進(jìn)程但被屏蔽的信號(hào)。

sigsuspend(?):將進(jìn)程的屏蔽字替換為信號(hào)集,并暫停進(jìn)程執(zhí)行,直到收到信號(hào)為止。6.信號(hào)處理流程信號(hào)的處理流程一般遵循如下步驟:(1)調(diào)用信號(hào)集函數(shù)組進(jìn)行信號(hào)集的初始化等。(2)調(diào)用sigprocmask()函數(shù)設(shè)置信號(hào)屏蔽集合。(3)定義信號(hào)處理函數(shù),如signal()、sigaction()等。(4)調(diào)用sigpending()函數(shù)測(cè)試信號(hào)。

6.3.4管道

shell命令在使用時(shí),執(zhí)行的過(guò)程就是把一個(gè)進(jìn)程的輸出直接傳遞給另一個(gè)進(jìn)程的輸入,例如cmd1|cmd2:

$cmd1|cmd2

shell處理過(guò)程如下:

(1)?cmd1的標(biāo)準(zhǔn)輸入來(lái)自于終端鍵盤。

(2)?cmd1的標(biāo)準(zhǔn)輸出傳遞給cmd2,作為cmd2的標(biāo)準(zhǔn)輸入。

(3)?cmd2的標(biāo)準(zhǔn)輸出傳遞到終端屏幕上。

也就是說(shuō),shell所做的工作實(shí)際上是對(duì)標(biāo)準(zhǔn)輸入流和輸出流進(jìn)行了重新連接,使數(shù)據(jù)流從鍵盤通過(guò)命令最終輸出到屏幕上。從中可以看出,shell命令的連接是按照管道字符來(lái)完成的。本節(jié)內(nèi)容將詳細(xì)描述管道的概念以及管道的使用方式。

1.管道的概述管道也是Linux古老的進(jìn)程間的通信方式之一。這里所說(shuō)的管道主要指無(wú)名管道(pipe),它具有如下特點(diǎn):它只能用于具有親緣關(guān)系的進(jìn)程(父子進(jìn)程或者兄弟進(jìn)程)之間的通信。它是一個(gè)半雙工的通信模式,數(shù)據(jù)只能向一個(gè)方向流動(dòng),具有固定的讀端和寫端。管道也可以看成是一種特殊的文件,對(duì)于它的讀寫也可以使用普通的read()和write()等函數(shù)。但是它不是普通的文件,并不屬于其他任何文件系統(tǒng),并且只存在于內(nèi)核的內(nèi)存空間中。一個(gè)單進(jìn)程管道如圖6-13所示。圖6-13單進(jìn)程管道

2.創(chuàng)建與關(guān)閉管道創(chuàng)建管道由pipe()函數(shù)完成,函數(shù)原型及相關(guān)頭文件如下。

【代碼6-26】pipe()

#include<unistd.h>

intpipe(intpipefd[2]);如果函數(shù)調(diào)用成功,進(jìn)程將打開兩個(gè)文件描述符pipefds[0]和pipefds[1],其中pipefds[0]固定用于讀管道,而pipefd[1]固定用于寫管道,這樣就構(gòu)成了一個(gè)半雙工的通道。管道是基于文件描述符的通信方式,所以一般的I/O函數(shù)都可以用

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論