




版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
1、http:.cn華清遠(yuǎn)見(jiàn)培訓(xùn)“黑色經(jīng)典”系列之Linux 應(yīng)用程序開(kāi)發(fā)詳解第 6 章文件 I/O 編程本章目標(biāo)在搭建起開(kāi)發(fā)。由于開(kāi)發(fā)環(huán)境之后,從本章開(kāi)始,讀者將真正開(kāi)始學(xué)習(xí)Linux 的應(yīng)用Linux 是經(jīng) Linux 裁減而來(lái)的,它的系統(tǒng)調(diào)用及用戶編程接口 API 與 Linux基本是一致的,因此,在以后的章節(jié)中,筆者將首先介紹 Linux 中相關(guān)內(nèi)容的基本編程開(kāi)發(fā),主要講解與Linux 中一致的部分,然后再將程序移植到的開(kāi)發(fā)板上運(yùn)行。因此,沒(méi)有開(kāi)發(fā)板的讀者也可以先在 Linux 上開(kāi)發(fā)相關(guān)應(yīng)用程序,這對(duì)以后進(jìn)入Linux 的實(shí)際開(kāi)發(fā)是十分有幫助的。本章主要講解文件 I/O 相關(guān)開(kāi)發(fā),經(jīng)過(guò)本
2、章的學(xué)習(xí),讀者將會(huì)掌握以下內(nèi)容。掌握 Linux 中系統(tǒng)調(diào)用的基本概念掌握 Linux 中用戶編程接口(API)及系統(tǒng)命令的相互關(guān)系掌握文件描述符的概念掌握 Linux 下文件相關(guān)的不帶緩存 I/O 函數(shù)的使用掌握 Linux 下設(shè)備文件讀寫(xiě)方法掌握 Linux 中對(duì)串口的操作熟悉 Linux 中標(biāo)準(zhǔn)文件 I/O 函數(shù)的使用華清遠(yuǎn)見(jiàn)培訓(xùn)http:.cn6.1Linux 系統(tǒng)調(diào)用及用戶編程接口(API)由于本章是講解 Linux 編程開(kāi)發(fā)的第 1 章,因此希望讀者更加明確 Linux 系統(tǒng)調(diào)用和用戶編程接口(API)的概念。在了解了這些之后,會(huì)對(duì) Linux 以及 Linux 的應(yīng)用編程有更深入
3、地理解。6.1.1系統(tǒng)調(diào)用所謂系統(tǒng)調(diào)用是指操作系統(tǒng)提供給用戶程序調(diào)用的一組“特殊”接口,用戶程序可以通過(guò)這組“特殊”接口來(lái)獲得操作系統(tǒng)內(nèi)核提供的服務(wù)。例如用戶可以通過(guò)進(jìn)程控制相關(guān)的系統(tǒng)調(diào)用來(lái)創(chuàng)建進(jìn)程、實(shí)現(xiàn)進(jìn)程調(diào)度、進(jìn)程管理等。在這里,為什么用戶程序不能直接系統(tǒng)內(nèi)核提供的服務(wù)呢?這是由于在 Linux 中,為了更好地保護(hù)內(nèi)核空間,將程序的運(yùn)行空間分為內(nèi)核空間和用戶空間(也就是常稱的內(nèi)核態(tài)和用戶態(tài)),它們分別運(yùn)行在不同的級(jí)別上,在邏輯上是相互的。因此,用戶進(jìn)程在通常情況下不允許內(nèi)核數(shù)據(jù),也無(wú)法使用內(nèi)核函數(shù),它們只能在用戶空間操作用戶數(shù)據(jù),調(diào)用用戶空間的函數(shù)。但是,在有些情況下,用戶空間的進(jìn)程需要獲
4、得一定的系統(tǒng)服務(wù)(調(diào)用內(nèi)核空間程序),這時(shí)操作系統(tǒng)就必須利用系統(tǒng)提供給用戶的“特殊接口”系統(tǒng)調(diào)用規(guī)定用戶進(jìn)程進(jìn)入內(nèi)核空間的具置。進(jìn)行系統(tǒng)調(diào)用時(shí),程序運(yùn)行空間需要從用戶空間進(jìn)入內(nèi)核空間,處理完后再返回到用戶空間。Linux 系統(tǒng)調(diào)用部分是非常精簡(jiǎn)的系統(tǒng)調(diào)用(只有 250 個(gè)左右),它繼承了 UNIX 系統(tǒng)調(diào)用中最基本和最有用的部分。這些系統(tǒng)調(diào)用按照功能邏輯大致可分為進(jìn)程控制、進(jìn)程間通信、文件系統(tǒng)控制、系統(tǒng)控制、管理、網(wǎng)絡(luò)管理、socket 控制、用戶管理等幾類。6.1.2 用戶編程接口(API)前面講到的系統(tǒng)調(diào)用并不是直接與程序員進(jìn)行交互的,它僅僅是一個(gè)通過(guò)軟中斷機(jī)制向內(nèi)核提交請(qǐng)求,以獲取內(nèi)核服
5、務(wù)的接口。在實(shí)際使用中程序員調(diào)用的通常是用戶編程接口API, 也就是本書(shū)后面要講到的 API 函數(shù)。但并不是所有的函數(shù)都一一對(duì)應(yīng)一個(gè)系統(tǒng)調(diào)用,有時(shí),一個(gè) API 函數(shù)會(huì)需要幾個(gè)系統(tǒng)調(diào)用來(lái)共同完成函數(shù)的功能,甚至還有一些 API 函數(shù)不需要調(diào)用相應(yīng)的系統(tǒng)調(diào)用(因此它所完成的不是內(nèi)核提供的服務(wù))。在Linux 中,用戶編程接口(API)遵循了在 UNIX 中最流行的應(yīng)用編程界面標(biāo)準(zhǔn)POSIX 標(biāo)準(zhǔn)。POSIX 標(biāo)準(zhǔn)是由 IEEE 和ISO/IEC 共同開(kāi)發(fā)的標(biāo)準(zhǔn)系統(tǒng)。該標(biāo)準(zhǔn)基于當(dāng)時(shí)現(xiàn)有的 UNIX 實(shí)踐和經(jīng)驗(yàn),描述了操作系統(tǒng)的系統(tǒng)調(diào)用編程接口(實(shí)際上就是 API),用于保證應(yīng)用程序可以在源代碼一級(jí)
6、上在多種操作系統(tǒng)上移植運(yùn)行。這些系統(tǒng)調(diào)用編程接口主要是通過(guò) C 庫(kù)(libc)實(shí)現(xiàn)的。6.1.3 系統(tǒng)命令以上講解了系統(tǒng)調(diào)用、用戶編程接口(API)的概念,分析了它們之間的相互關(guān)系,那么,讀者在第 2 章中學(xué)到的那么多的 Shell 系統(tǒng)命令與它們之間又是怎樣的關(guān)系呢?系統(tǒng)命令相對(duì) API 更高了一層,它實(shí)際上一個(gè)可執(zhí)行程序,它的內(nèi)部口(API)來(lái)實(shí)現(xiàn)相應(yīng)的功能。它們之間的關(guān)系如下圖 6.1 所示。了用戶編程接Linux 應(yīng)用程序開(kāi)發(fā)詳解第 6 章、文件 IO 編程內(nèi)核空間用戶空間圖 6.1 系統(tǒng)調(diào)用、API 及系統(tǒng)命令之間的關(guān)系6.2Linux 中文件及文件描述符概述正如第 1 章中所述,在
7、 Linux 中對(duì)目錄和設(shè)備的操作都等同于文件的操作,因此,大大簡(jiǎn)化了系統(tǒng)對(duì)不同設(shè)備的處理,提高了效率。Linux 中的文件主要分為 4 種:普通文件、目錄文件、文件和設(shè)備文件。那么,內(nèi)核如何區(qū)分和特定的文件呢?這里用到的就是一個(gè)重要的概念文件描述符。對(duì)于 Linux 而言,所有對(duì)設(shè)備和文件的操作都使用文件描述符來(lái)進(jìn)行的。文件描述符是一個(gè)非負(fù)的整數(shù),它是一個(gè)索引值,并指向內(nèi)核中每個(gè)進(jìn)程打開(kāi)文件的表。當(dāng)打開(kāi)一個(gè)現(xiàn)存文件或創(chuàng)建一個(gè)新文件時(shí),內(nèi)核就向進(jìn)程返回一個(gè)文件描述符;當(dāng)需要讀寫(xiě)文件時(shí), 也需要把文件描述符作為參數(shù)傳遞給相應(yīng)的函數(shù)。通常,一個(gè)進(jìn)程啟動(dòng)時(shí),都會(huì)打開(kāi) 3 個(gè)文件:標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出和
8、標(biāo)準(zhǔn)出錯(cuò)處理。這3 個(gè)文件分別對(duì)應(yīng)文件描述符為 0、1 和 2(也就是宏替換 STDIN_FILENO、STDOUT_FILENO和 STDERR_FILENO,鼓勵(lì)讀者使用這些宏替換)?;谖募枋龇?I/O 操作雖然不能移植到類 Linux 以外的系統(tǒng)上去(如 Windows),但它往往是實(shí)現(xiàn)某些 I/O 操作的惟一途徑,如 Linux 中低級(jí)文件操作函數(shù)、多路 I/O、TCP/IP 套接字編程接口等。同時(shí),它們也很好地兼容 POSIX 標(biāo)準(zhǔn),因此,可以很方便地移植到任何 POSIX 平臺(tái)上?;谖募枋龇?I/O 操作是 Linux 中最常用的操作之一,希望讀者能夠很好地掌握。6.3
9、不帶緩存的文件 I/O 操作本節(jié)主要介紹不帶緩存的文件 I/O 操作,主要用到 5 個(gè)函數(shù):open、read、write、lseek和 close。這里的不帶緩存是指每一個(gè)函數(shù)都只調(diào)用系統(tǒng)中的一個(gè)函數(shù)。這些函數(shù)雖然不是用戶編程接口 API系統(tǒng)調(diào)用系統(tǒng)命令華清遠(yuǎn)見(jiàn)培訓(xùn)http:.cnANSI C 的組成部分,但是是 POSIX 的組成部分。6.3.1open 和close(1)open 和 close 函數(shù)說(shuō)明open 函數(shù)是用于打開(kāi)或創(chuàng)建文件,在打開(kāi)或創(chuàng)建文件時(shí)可以指定文件的屬性及用戶的權(quán)限等各種參數(shù)。close 函數(shù)是用于關(guān)閉一個(gè)打開(kāi)文件。當(dāng)一個(gè)進(jìn)程終止時(shí),它所有已打開(kāi)的文件都由內(nèi)核自動(dòng)關(guān)
10、閉,很多程序都使用這(2)open 和 close 函數(shù)格式能而不顯示地關(guān)閉一個(gè)文件。open 函數(shù)的語(yǔ)法格式如表 6.1 所示。表 6.1open 函數(shù)語(yǔ)法要點(diǎn)續(xù)表在 open 函數(shù)中,flag 參數(shù)可通過(guò)“|”組合構(gòu)成,但前 3 個(gè)函數(shù)不能相互組合。perms 是文件的存取權(quán)限,采用 8 進(jìn)制表示法,相關(guān)內(nèi)容讀者可參見(jiàn)第 2 章。close 函數(shù)的語(yǔ)法格式如下表 6.2 所示。表 6.2close 函數(shù)語(yǔ)法要點(diǎn)函數(shù)原型int open(const char *pathname,flags,int perms)函數(shù)傳入值pathname被打開(kāi)的文件名(可包括路徑名)flag:文件打 開(kāi)的 方
11、式O_RDONLY:只讀方式打開(kāi)文件O_WRONLY:可寫(xiě)方式打開(kāi)文件O_RDWR:讀寫(xiě)方式打開(kāi)文件O_CREAT:如果該文件不存在,就創(chuàng)建一個(gè)新的文件,并用第三個(gè)參數(shù)為其設(shè)置權(quán)限O_EXCL:如果使用 O_CREAT 時(shí)文件存在,則可返回錯(cuò)誤消息。這一參數(shù)可測(cè)試文件是否存在O_NOCTTY:使用本參數(shù)時(shí),如文件為終端,那么終端不可以作為調(diào)用open()系統(tǒng)調(diào)用的那個(gè)進(jìn)程的控制終端O_TRUNC:如文件已經(jīng)存在,并且以只讀或只寫(xiě)成功打開(kāi),那么會(huì)先全部刪除文件中原有數(shù)據(jù)O+APPEND:以添加方式打開(kāi)文件,在打開(kāi)文件的同時(shí),文件指針指向文件的末尾perms被打開(kāi)文件的存取權(quán)限,為 8 進(jìn)制表示法
12、函數(shù)返回值成功:返回文件描述符失?。?1所需頭文件#include <sys/types.h> / 提供類型 pid_t 的定義#include <sys/stat.h> #include <fcntl.h>Linux 應(yīng)用程序開(kāi)發(fā)詳解第 6 章、文件 IO 編程(3)open 和 close 函數(shù)使用實(shí)例下面實(shí)例中的 open 函數(shù)帶有 3 個(gè) flag 參數(shù):O_CREAT、O_TRUNC 和 O_WRONLY, 這樣就可以對(duì)不同的情況指定相應(yīng)的處理方法。另外,這里對(duì)該文件的權(quán)限設(shè)置為 0600。其源碼如下所示:/*open.c*/ #include &
13、lt;unistd.h>#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <stdio.h>int main(void)int fd;/*調(diào)用 open 函數(shù),以可讀寫(xiě)的方式打開(kāi),注意選項(xiàng)可以用“|”符號(hào)連接*/if(fd = open("/tmp/hello.c", O_CREAT | O_TRUNC | O_WRONLY , 0600 )<0) perror(&qu
14、ot;open:");exit(1);elseprintf("Open file: hello.c %dn",fd);if( close(fd) < 0 )perror("close:"); exit(1);elseprintf("Close "); exit(0);root(none) 1# ./open所需頭文件#include <unistd.h>函數(shù)原型int close(int fd)函數(shù)輸入值fd:文件描述符函數(shù)返回值0:成功-1:出錯(cuò)華清遠(yuǎn)見(jiàn)培訓(xùn)http:.cn到目標(biāo)板,則該可
15、執(zhí)行文件運(yùn)行后就能在目錄/tmp 下新建經(jīng)過(guò)交叉編譯后,將文件一個(gè) hello.c 的文件,其權(quán)限為 0600。open 函數(shù)返回的文件描述符一定是最小的未用文件描述符。由于一個(gè)進(jìn)程在啟動(dòng)時(shí)自動(dòng)打開(kāi)了0、1、2 三個(gè)文件描述符,因此,該文件運(yùn)行結(jié)果中返回的文件描述符為 3。讀者可以嘗試在調(diào)用 open 函數(shù)之前,加依據(jù) close(0),則此后在 open 函數(shù)時(shí)返回的文件描述符為 0(若關(guān)閉文件描述符 1,則在執(zhí)行時(shí)會(huì)由于沒(méi)有標(biāo)準(zhǔn)輸出文件而無(wú)法輸出)。注意6.3.2read、write 和lseek(1)read、write 和 lseek 函數(shù)作用read 函數(shù)是用于將指定的文件描述符中讀
16、出數(shù)據(jù)。當(dāng)從終端設(shè)備文件中讀出數(shù)據(jù)時(shí),通常一次最多讀一行。write 函數(shù)是用于向打開(kāi)的文件寫(xiě)數(shù)據(jù),寫(xiě)操作從文件的當(dāng)前位移量處開(kāi)始。若磁盤(pán)已滿或超出該文件的長(zhǎng)度,則 write 函數(shù)返回失敗。lseek 函數(shù)是用于在指定的文件描述符中將文件指針(2)read 和 write 函數(shù)格式read 函數(shù)的語(yǔ)法格式如下表 6.3 所示。到相應(yīng)的位置。表 6.3read 函數(shù)語(yǔ)法要點(diǎn)在讀普通文件時(shí),若讀到要求的字節(jié)數(shù)之前已到達(dá)文件的尾部,則返回的字節(jié)數(shù)會(huì)小于希望讀出的字節(jié)數(shù)。write 函數(shù)的語(yǔ)法格式如下表 6.4 所示。表 6.4write 函數(shù)語(yǔ)法要點(diǎn)所需頭文件#include <unistd
17、.h>函數(shù)原型ssize_t write(int fd,void *buf,size_t count)所需頭文件#include <unistd.h>函數(shù)原型ssize_t read(int fd,void *buf,size_t count)函數(shù)傳入值fd:文件描述符buf:指定器讀出數(shù)據(jù)的緩沖區(qū)count:指定讀出的字節(jié)數(shù)函數(shù)返回值成功:讀到的字節(jié)數(shù)0:已到達(dá)文件尾-1:出錯(cuò)Open file: hello.c 3 Close hello.croot(none) tmp# ls -l |grep hello.c-rw-1 rootroot0 Dec 4 00:59 hel
18、lo.cLinux 應(yīng)用程序開(kāi)發(fā)詳解第 6 章、文件 IO 編程在寫(xiě)普通文件時(shí),寫(xiě)操作從文件的當(dāng)前位移處開(kāi)始。lseek 函數(shù)的語(yǔ)法格式如下表 6.5 所示。表 6.5lseek 函數(shù)語(yǔ)法要點(diǎn)續(xù)表(3)函數(shù)使用實(shí)例該示例程序首先打開(kāi)上一節(jié)中創(chuàng)建的文件,然后對(duì)此文件進(jìn)行讀寫(xiě)操作(記得要將文件打開(kāi)屬性改為可讀寫(xiě),將文件權(quán)限也做相應(yīng)更改)。接著,寫(xiě)入“Hello! I'm writing to this file!”,此時(shí)文件指針位于文件尾部。接著在使用 lseek 函數(shù)將文件指針移到文件開(kāi)始處,并讀出 10 個(gè)字節(jié)并將其打印出來(lái)。程序源代碼如下所示:/*write.c*/ #include
19、 <unistd.h>#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <stdio.h> #include <string.h>#define MAXSIZEwhence:當(dāng)前位置的基點(diǎn)SEEK_SET:當(dāng)前位置為文件的開(kāi)頭,新位置為偏移量的大小SEEK_CUR:當(dāng)前位置為文件指針的位置,新位置為當(dāng)前位置加上偏移量SEEK_END:當(dāng)前位置為文件的結(jié)尾,新位置為文件的大小加上
20、偏移量的大小函數(shù)返回值成功:文件的當(dāng)前位移-1:出錯(cuò)所需頭文件#include <unistd.h> #include <sys/types.h>函數(shù)原型off_t lseek(int fd,off_t offset,int whence)函數(shù)傳入值fd:文件描述符offset:偏移量,每一讀寫(xiě)操作所需要移動(dòng)的距離,是字節(jié)的數(shù)量,可正可負(fù)(向前移,向后移)函數(shù)傳入值fd:文件描述符buf:指定器寫(xiě)入數(shù)據(jù)的緩沖區(qū)count:指定讀出的字節(jié)數(shù)函數(shù)返回值成功:已寫(xiě)的字節(jié)數(shù)-1:出錯(cuò)華清遠(yuǎn)見(jiàn)培訓(xùn)http:.cnint main(void)int i,fd,size,len;ch
21、ar *buf="Hello! I'm writing to this file!" char buf_r10;len = strlen(buf);/*首先調(diào)用 open 函數(shù),并指定相應(yīng)的權(quán)限*/if(fd = open("/tmp/hello.c", O_CREAT | O_TRUNC | O_RDWR,0666 )<0) perror("open:");exit(1);elseprintf("open file:hello.c %dn",fd);/*調(diào)用 write 函數(shù),將 buf 中的內(nèi)容寫(xiě)
22、入到打開(kāi)的文件中*/if(size = write( fd, buf, len) < 0) perror("write:");exit(1);elseprintf("Write:%sn",buf);/*調(diào)用 lsseek 函數(shù)將文件指針移到文件起始,并讀出文件中的 10 個(gè)字節(jié)*/lseek( fd, 0, SEEK_SET );if(size = read( fd, buf_r, 10)<0) perror("read:");exit(1);elseprintf("read form file:%sn"
23、;,buf_r); if( close(fd) < 0 )perror("close:"); exit(1);elseprintf("Close "); exit(0);root(none) 1# ./writeopen file:hello.c 3Linux 應(yīng)用程序開(kāi)發(fā)詳解第 6 章、文件 IO 編程6.3.3fcntl(1)fcntl 函數(shù)說(shuō)明前面的這 5 個(gè)基本函數(shù)實(shí)現(xiàn)了文件的打開(kāi)、讀寫(xiě)等基本操作,這一節(jié)將討論的是,在文件已經(jīng)共享的情況下如何操作,也就是當(dāng)多個(gè)用戶共同使用、操作一個(gè)文件的情況,這時(shí),Linux 通常采用的方法
24、是給文件上鎖,來(lái)避免共享的產(chǎn)生競(jìng)爭(zhēng)的狀態(tài)。文件鎖包括建議性鎖和強(qiáng)制性鎖。建議性鎖要求每個(gè)上鎖文件的進(jìn)程都要檢查是否有鎖存在,并且尊重已有的鎖。在一般情況下,內(nèi)核和系統(tǒng)都不使用建議性鎖。強(qiáng)制性鎖是由內(nèi)核執(zhí)行的鎖,當(dāng)一個(gè)文件被上鎖進(jìn)行寫(xiě)入操作的時(shí)候,內(nèi)核將其他任何文件對(duì)其進(jìn)行讀寫(xiě)操作。采用強(qiáng)制性鎖對(duì)性能的影響很大,每次讀寫(xiě)操作都必須檢查是否有鎖存在。在 Linux 中,實(shí)現(xiàn)文件上鎖的函數(shù)有 lock 和 fcntl,其中 flock 用于對(duì)文件施加建議性鎖,而 fcntl 不僅可以施加建議性鎖,還可以施加強(qiáng)制鎖。同時(shí),fcntl 還能對(duì)文件的某一進(jìn)行上鎖,也就是鎖。鎖又可分為文件的同一部分建立鎖和
25、寫(xiě)入鎖,其中鎖又稱為共享鎖,它能夠使多個(gè)進(jìn)程都能在鎖。而寫(xiě)入鎖又稱為排斥鎖,在任何時(shí)刻只能有一個(gè)進(jìn)程在文件的某個(gè)部分上建立寫(xiě)入鎖。當(dāng)然,在文件的同一部分不能同時(shí)建立鎖和寫(xiě)入鎖。fcntl 是一個(gè)非常通用的函數(shù),它還可以改變文件進(jìn)程各方面的屬性,在本節(jié)中,主要介紹它建注意鎖的方法,關(guān)于它其他用戶感的讀者可以參看 fcntl 手冊(cè)。立(2)fcntl 函數(shù)格式用于建立鎖的 fcntl 函數(shù)格式如表 6.6 所示。fcntl 函數(shù)語(yǔ)法要點(diǎn)表 6.6所需頭文件#include <sys/types.h> #include <unistd.h> #include <fcnt
26、l.h>函數(shù)原型int fcnt1(int fd,int cmd,struct flock *lock)函數(shù)傳入值fd:文件描述符cmdF_DUPFD:文件描述符F_GETFD:獲得 fd 的close-on-exec 標(biāo)志,若標(biāo)志未設(shè)置,則文件經(jīng)過(guò) exec函數(shù)之后仍保持打開(kāi)狀態(tài)F_SETFD:設(shè)置 close-on-exec 標(biāo)志,該標(biāo)志以參數(shù) arg 的FD_CLOEXEC 位決定F_GETFL:得到 open 設(shè)置的標(biāo)志F_SETFL:改變 open 設(shè)置的標(biāo)志W(wǎng)rite:Hello! I'm writing to this file! read form file:He
27、llo! I'mClose hello.croot(none) 1# cat /tmp/hello.c Hello! I'm writing to this file!華清遠(yuǎn)見(jiàn)培訓(xùn)http:.cn這里,lock 的結(jié)構(gòu)如下所示:lock 結(jié)構(gòu)中每個(gè)變量的取值含義如表 6.7 所示。表 6.7lock 結(jié)構(gòu)變量取值為加鎖整個(gè)文件,通常的方法是將 l_start 說(shuō)明為 0,l_whence 說(shuō)明為 SEEK_SET,l_len 說(shuō)明為 0。小技巧(3)fcntl 使用實(shí)例下面首先給出了使用 fcntl 函數(shù)的文件鎖函數(shù)。在該函數(shù)中,首先給 flock 結(jié)構(gòu)體的對(duì)應(yīng)位賦予相應(yīng)的值。
28、接著使用兩次 fcntl 函數(shù)分別用于給相關(guān)文件上鎖和判斷文件是否可以上鎖,這里用到的 cmd 值分別為 F_SETLK 和 F_GETLK。這個(gè)函數(shù)的源代碼如下所示:l_typeF_RDLCK:鎖(共享鎖)F_WRLCK:寫(xiě)入鎖(排斥鎖)F_UNLCK:l_stat相對(duì)位移量(字節(jié))l_whence:相對(duì)位移量的起點(diǎn)(同 lseek 的whence)。SEEK_SET:當(dāng)前位置為文件的開(kāi)頭,新位置為偏移量的大小SEEK_CUR:當(dāng)前位置為文件指針的位置,新位置為當(dāng)前位置加上偏移量SEEK_END:當(dāng)前位置為文件的結(jié)尾,新位置為文件的大小加上偏移量的大小l_len加鎖區(qū)域的長(zhǎng)度Struct f
29、lock short l_type; off_t l_start; short l_whence; off_t l_len; pid_t l_pid;F_GETFK:根據(jù) lock 描述,決定是否上文件鎖F_SETFK:設(shè)置 lock 描述的文件鎖F_SETLKW:這是 F_SETLK 的阻塞版本(命令名中的 W 表示等待(wait)。如果存在其他鎖,則調(diào)用進(jìn)程睡眠;如果捕捉到信號(hào)則睡眠中斷F_GETOWN:檢索將收到 SIGIO 和SIGURG 信號(hào)的進(jìn)程號(hào)或進(jìn)程組號(hào)F_SETOWN:設(shè)置進(jìn)程號(hào)或進(jìn)程組號(hào)Lock:結(jié)構(gòu)為 flock,設(shè)置鎖的具體狀態(tài),后面會(huì)詳細(xì)說(shuō)明函數(shù)返回值成功:0-1:出
30、錯(cuò)Linux 應(yīng)用程序開(kāi)發(fā)詳解第 6 章、文件 IO 編程下面的實(shí)例是測(cè)試文件的寫(xiě)入鎖,這里首先創(chuàng)建了一個(gè) hello 文件,之后對(duì)其上寫(xiě)入鎖,寫(xiě)入鎖。代碼如下所示:最后/*fcntl_write.c 測(cè)試文件寫(xiě)入鎖主函數(shù)部分*/ #include <unistd.h>#include <sys/file.h>/*lock_set 函數(shù)*/void lock_set(int fd, int type)struct flock lock;lock.l_whence = SEEK_SET;/賦值 lock 結(jié)構(gòu)體lock.l_start = 0;lock.l_len =0;
31、 while(1)lock.l_type = type;/*根據(jù)不同的 type 值給文件上鎖或*/if(fcntl(fd, F_SETLK, &lock) = 0) if( lock.l_type = F_RDLCK )printf("read lock set by %dn",getpid(); else if( lock.l_type = F_WRLCK )printf("write lock set by %dn",getpid(); else if( lock.l_type = F_UNLCK )printf("release
32、 lock by %dn",getpid();return;/*判斷文件是否可以上鎖*/fcntl(fd, F_GETLK,&lock);/*判斷文件不能上鎖的原因*/if(lock.l_type != F_UNLCK)/*/該文件已有寫(xiě)入鎖*/if( lock.l_type = F_RDLCK )printf("read lock already set by %dn",lock.l_pid);/*該文件已有鎖*/else if( lock.l_type = F_WRLCK )printf("write lock already set by
33、%dn",lock.l_pid); getchar();華清遠(yuǎn)見(jiàn)培訓(xùn)http:.cn為了能夠使用多個(gè)終端,更好地顯示寫(xiě)入鎖的作用,本實(shí)例主要在 PC 機(jī)上測(cè)試,讀者到目標(biāo)板上運(yùn)行。下面是在 PC 機(jī)上的運(yùn)行結(jié)果。為了使程序有較大可將其交叉編譯,的靈活性,筆者采用文件上鎖后由用戶鍵入一任意鍵使程序繼續(xù)運(yùn)行。建議讀者開(kāi)啟兩個(gè)終端,并且在兩個(gè)終端上同時(shí)運(yùn)行該程序,以達(dá)到多個(gè)進(jìn)程操作一個(gè)文件的效果。在這里,筆者首先運(yùn)行終端一,請(qǐng)讀者注意終端二中的第一句。終端一:終端二:rootlocalhost file#tl_writewrite lock already set by 4994rootl
34、ocalhost file#tl_writewrite lock set by 4994release lock by 4994#include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <stdlib.h>int main(void)int fd;/*首先打開(kāi)文件*/fd=open("hello",O_RDWR | O_CREAT, 0666); if(fd < 0)perror("open"); exit(1);
35、/*給文件上寫(xiě)入鎖*/ lock_set(fd, F_WRLCK); getchar();/*給文件接鎖*/lock_set(fd, F_UNLCK);getchar(); close(fd); exit(0);Linux 應(yīng)用程序開(kāi)發(fā)詳解第 6 章、文件 IO 編程由此可見(jiàn),寫(xiě)入鎖為互斥鎖,一個(gè)時(shí)刻只能有一個(gè)寫(xiě)入鎖存在。接下來(lái)的程序是測(cè)試文件的鎖,原理同上面的程序一樣。同樣開(kāi)啟兩個(gè)終端,并首先啟動(dòng)終端一上的程序,其運(yùn)行結(jié)果如下所示:終端一:終端二:rootlocalhost file#tl2read lock set by 5009release lock by 5009/*fcntl_re
36、ad.c 測(cè)試文件鎖主函數(shù)部分*/ #include <unistd.h>#include <sys/file.h> #include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <stdlib.h>int main(void)int fd;fd=open("hello",O_RDWR | O_CREAT, 0666); if(fd < 0)perror("open"); exit(1);/*給
37、文件上鎖*/ lock_set(fd, F_RDLCK); getchar();/*給文件接鎖*/lock_set(fd, F_UNLCK);getchar(); close(fd); exit(0);write lock set by 4997release lock by 4997華清遠(yuǎn)見(jiàn)培訓(xùn)http:.cn鎖為共享鎖,當(dāng)進(jìn)程 5009讀者可以將此結(jié)果與寫(xiě)入鎖的運(yùn)行結(jié)果相比較,可以看出,已設(shè)定鎖后,進(jìn)程 5010 還可以設(shè)置鎖。如果在一個(gè)終端上運(yùn)行設(shè)置鎖,則在另一個(gè)終端上運(yùn)行設(shè)置寫(xiě)入鎖,會(huì)有什么結(jié)果呢?思考6.3.4select(1) select 函數(shù)說(shuō)明前面的 fcntl 函數(shù)解決了文
38、件的共享問(wèn)題,接下來(lái)該處理 I/O 復(fù)用的情況了??偟膩?lái)說(shuō),I/O 處理的模型有 5 種。· 阻塞 I/O 模型:在這種模型下,若所調(diào)用的 I/O 函數(shù)沒(méi)有完成相關(guān)的功能就會(huì)使進(jìn)程掛起,直到相關(guān)數(shù)據(jù)到才會(huì)出錯(cuò)返回。如常見(jiàn)對(duì)管道設(shè)備、終端設(shè)備和網(wǎng)絡(luò)設(shè)備進(jìn)行讀寫(xiě)時(shí)經(jīng)常會(huì)出現(xiàn)這種情況。· 非阻塞模型:在這種模型下,當(dāng)請(qǐng)求的 I/O 操作不能完成時(shí),則不讓進(jìn)程睡眠,而且返回一個(gè)錯(cuò)誤。非阻塞 I/O 使用戶可以調(diào)用永遠(yuǎn)阻塞的 I/O 操作,如 open、write和 read。如果該操作不能完成,則會(huì)立即出錯(cuò)返回,且表示該 I/O 如果該操作繼續(xù)執(zhí)行就會(huì)阻塞。· I/O 多
39、路轉(zhuǎn)接模型:在這種模型下,如果請(qǐng)求的 I/O 操作阻塞,且它不是真正阻塞 I/O, 而是讓其中的一個(gè)函數(shù)等待,在這期間,I/O 還能進(jìn)行其他操作。如本節(jié)要介紹的 select 函數(shù)和 poll 函數(shù),就是屬于這種模型。· 信號(hào)驅(qū)動(dòng) I/O 模型:在這種模型下,通過(guò)安裝一個(gè)信號(hào)處理程序,系統(tǒng)可以自動(dòng)捕獲特定信號(hào)的到來(lái),從而啟動(dòng) I/O。這是由內(nèi)核通知用戶何時(shí)可以啟動(dòng)一個(gè) I/O 操作決定的。· 異步 I/O 模型:在這種模型下,當(dāng)一個(gè)描述符已準(zhǔn)備好,可以啟動(dòng) I/O 時(shí),進(jìn)程會(huì)通知內(nèi)核?,F(xiàn)在,并不是所有的系統(tǒng)都支持這種模型。可以看到,select 的 I/O 多路轉(zhuǎn)接模型是處
40、理 I/O 復(fù)用的一個(gè)高效的方法。它可以具體設(shè)置每一個(gè)所關(guān)心的文件描述符的條件、希望等待的時(shí)間等,從 select 函數(shù)返回時(shí),內(nèi)核會(huì)通知用戶已準(zhǔn)備好的文件描述符的數(shù)量、已準(zhǔn)備好的條件等。通過(guò)使用 select 返回值,就可以調(diào)用相應(yīng)的 I/O 處理函數(shù)了。(2)select 函數(shù)格式Select 函數(shù)的語(yǔ)法格式如表 6.8 所示。表 6.8fcntl 函數(shù)語(yǔ)法要點(diǎn)所需頭文件#include <sys/types.h> #include <sys/time.h>rootlocalhost file#tl2read lock set by 5010release lock
41、 by 5010Linux 應(yīng)用程序開(kāi)發(fā)詳解第 6 章、文件 IO 編程請(qǐng)讀者考慮一下如何確定最高的文件描述符?思考可以看到,select 函數(shù)根據(jù)希望進(jìn)行的文件操作對(duì)文件描述符進(jìn)行了分類處理,這里,對(duì)文件描述符的處理主要涉及到 4 個(gè)宏函數(shù),如表 6.9 所示。表 6.9select 文件描述符處理函數(shù)一般來(lái)說(shuō),在使用 select 函數(shù)之前,首先使用 FD_ZERO 和 FD_SET 來(lái)初始化文件描述符集,在使用了 select 函數(shù)時(shí),可循環(huán)使用 FD_ISSET 測(cè)試描述符集,在執(zhí)行完對(duì)相關(guān)后文件描述符后,使用 FD_CLR 來(lái)清楚描述符集。另外,select 函數(shù)中的 timeout
42、 是一個(gè) struct timeval 類型的指針,該結(jié)構(gòu)體如下所示:可以看到,這個(gè)時(shí)間結(jié)構(gòu)體的精確度可以設(shè)置到微秒級(jí),這對(duì)于大多數(shù)的應(yīng)用而言已經(jīng)足夠了。(3)使用實(shí)例由于 Select 函數(shù)多用于 I/O 操作可能會(huì)阻塞的情況下,而對(duì)于可能會(huì)有阻塞 I/O 的管道、struct timeval long tv_sec; /* second */long tv_unsec; /* and microseconds*/FD_ZERO(fd_set *set)清除一個(gè)文件描述符集FD_SET(int fd,fd_set *set)將一個(gè)文件描述符加入文件描述符集中FD_CLR(int fd,fd_
43、set *set)將一個(gè)文件描述符從文件描述符集中清除FD_ISSET(int fd,fd_set *set)測(cè)試該集中的一個(gè)給是否有變化#include <unistd.h>函數(shù)原型int select(int numfds,fd_set *readfds,fd_set *writefds,fd_set *exeptfds,struct timeval*timeout)函數(shù)傳入值numfds:需要檢查的號(hào)碼最高的文件描述符加 1readfds:由 select()監(jiān)視的讀文件描述符集合writefds:由select()監(jiān)視的寫(xiě)文件描述符集合exeptfds:由select()監(jiān)
44、視的異常處理文件描述符集合timeoutNULL:永遠(yuǎn)等待,直到捕捉到信號(hào)或文件描述符已準(zhǔn)備好為止具體值:struct timeval 類型的指針,若等待為timeout 時(shí)間還沒(méi)有文件描符準(zhǔn)備好,就立即返回0:從不等待,測(cè)試所有指定的描述符并立即返回函數(shù)返回值成功:準(zhǔn)備好的文件描述符-1:出錯(cuò)華清遠(yuǎn)見(jiàn)培訓(xùn)http:.cn網(wǎng)絡(luò)編程,本書(shū)到現(xiàn)在為止還沒(méi)有涉及。因此,本例主要表現(xiàn)了如何使用 select 函數(shù),而其中的 I/O 操作是阻塞的。本實(shí)例中主要實(shí)現(xiàn)將文件 hello1 里的內(nèi)容讀出,并將此內(nèi)容每隔 10s 寫(xiě)入 hello2 中去。在這里建立了兩個(gè)描述符集,其中一個(gè)描述符集 inset1
45、 是用于文件內(nèi)容,另一個(gè)描述符集 inset2 是用于寫(xiě)入文件的。兩個(gè)文件描述符 fds0和 fds1分別指向這一文件描述符。在首先初始化完各文件描述符集之后,就開(kāi)始了循環(huán)測(cè)試這兩個(gè)文件描述符是否可讀寫(xiě),由于在這里沒(méi)有阻塞,所以文件描述符處于準(zhǔn)備就緒的狀態(tài)。這時(shí),就分別對(duì)文件描述符 fds0和fsd1進(jìn)行讀寫(xiě)操作。該程序的流程圖如圖 6.2 所示。/*select.c*/ #include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include &
46、lt;time.h>int main(void)int fds2; char buf7; int i,rc,maxfd;fd_set inset1,inset2; struct timeval tv;/*首先按一定的權(quán)限打開(kāi) hello1 文件*/if(fds0 = open ("hello1", O_RDWR|O_CREAT,0666)<0) perror("open hello1");/*再按一定的權(quán)限打開(kāi) hello2 文件*/if(fds1 = open ("hello2", O_RDWR|O_CREAT,0666
47、)<0) perror("open hello2");if(rc = write(fds0,"Hello!n",7) printf("rc=%dn",rc);lseek(fds0,0,SEEK_SET);/*取出兩個(gè)文件描述符中的較大者*/maxfd = fds0>fds1 ? fds0 : fds1;/*初始化讀集合 inset1,并在讀集合中加入相應(yīng)的描述集*/ FD_ZERO(&inset1); FD_SET(fds0,&inset1);/*初始化寫(xiě)集合 inset2,并在寫(xiě)集合中加入相應(yīng)的描述集*/
48、FD_ZERO(&inset2);Linux 應(yīng)用程序開(kāi)發(fā)詳解第 6 章、文件 IO 編程FD_SET(fds1,&inset2); tv.tv_sec=2; tv.tv_usec=0;/*循環(huán)測(cè)試該文件描述符是否準(zhǔn)備就緒,并調(diào)用 select 函數(shù)對(duì)相關(guān)文件描述符做對(duì)應(yīng)操作*/while(FD_ISSET(fds0,&inset1)|FD_ISSET(fds1,&inset2) if(select(maxfd+1,&inset1,&inset2,NULL,&tv)<0)perror("select");else
49、if(FD_ISSET(fds0,&inset1) rc = read(fds0,buf,7); if(rc>0)bufrc='0' printf("read: %sn",buf);elseperror("read");if(FD_ISSET(fds1,&inset2) rc = write(fds1,buf,7); if(rc>0)bufrc='0'printf("rc=%d,write: %sn",rc,buf);elseperror("write")
50、;sleep(10);exit(0);華清遠(yuǎn)見(jiàn)培訓(xùn)http:.cn開(kāi)始打開(kāi)文件hello1和hello2 并在hello1中寫(xiě)入“Hello!”并lseek暫停10秒結(jié)束圖 6.2 select 實(shí)例流程圖讀者可以將以上程序交叉編譯,并到開(kāi)發(fā)板上運(yùn)行。以下是運(yùn)行結(jié)果:root(none) 1# ./select rc=7read: Hello!rc=7,write: Hello!rc=7,write:Hello!rc=7,write:Hello!讀he寫(xiě)hello2調(diào)用selectllo1調(diào)用FD_ISSET測(cè)試inset1和inset2是否有變化分別對(duì)fds0、fds1調(diào)用FD_ZERO、F
51、D_SET初始化Linux 應(yīng)用程序開(kāi)發(fā)詳解第 6 章、文件 IO 編程可以看到,使用 select 可以很好地實(shí)現(xiàn) I/O 多路復(fù)用,在有阻塞的情況下更能夠顯示出它的作用。Linux 串口應(yīng)用開(kāi)發(fā)串口概述用戶常見(jiàn)的數(shù)據(jù)通信的基本方式可分為并行通信與串行通信兩種。· 并行通信是指利用多條數(shù)據(jù)傳輸線將一個(gè)資料的各位同時(shí)傳送。它的特點(diǎn)是傳輸速度快,適用于短距離通信,但要求傳輸速度較高的應(yīng)用場(chǎng)合。· 串行通信是指利用一條傳輸線將資料一位位地順序傳送。特點(diǎn)是通信線路簡(jiǎn)單,利用簡(jiǎn)單的線纜就可實(shí)現(xiàn)通信,降低成本,適用于遠(yuǎn)距離通信,但傳輸速度慢的應(yīng)用場(chǎng)合。串口是計(jì)算機(jī)一種
52、常用的接口,常用的串口有 RS-232-C 接口。它是于 1970 年由美國(guó)電子工業(yè)(EIA)貝爾系統(tǒng)、調(diào)制解調(diào)器廠家及計(jì)算機(jī)終端生產(chǎn)廠家共同制定的用于串行通訊的標(biāo)準(zhǔn),它的全稱是“數(shù)據(jù)終端設(shè)備(DTE)和數(shù)據(jù)通訊設(shè)備(DCE)之間串行二進(jìn)制接術(shù)標(biāo)準(zhǔn)”。該標(biāo)準(zhǔn)規(guī)定采用一個(gè) DB25 芯引腳的連接器或 9 芯引腳的連接器,其中 25 芯引腳的連接器如圖 6.3 所示。S3C2410X 內(nèi)部具有 2 個(gè)斷)模式或者 DMA(直接內(nèi)存的 UART 控制器,每個(gè)控制器都可以工作在 Interrupt(中)模式。同時(shí),每個(gè) UART 均具有 16 字節(jié)的 FIFO(先入先出寄存器),支持的最高波特率可達(dá)到
53、 230.4Kbps。UART 的操作主要可分為以下幾個(gè)部分:資料模式。、資料接收、產(chǎn)生中斷、產(chǎn)生波特率、Loopback 模式、紅外模式以及自動(dòng)流控串口參數(shù)的配置讀者在配置超級(jí)終端和時(shí)也已經(jīng)接觸到過(guò),一般包括波特率、起始位數(shù)量、數(shù)據(jù)位數(shù)量、停止位數(shù)量和流控協(xié)議。在此,可以將其配置為波特率 115200、起始位 1b、數(shù)據(jù)位 8b、停止位 1b 和無(wú)流控協(xié)議。在 Linux 中,所有的設(shè)備文件一般都位于“/dev”下,其中串口一、串口二對(duì)應(yīng)的設(shè)備名依次為“/dev/ttyS0”、“/dev/ttyS1”,可以查看在“/dev”下的文件以確認(rèn)。在本章中已經(jīng)提到過(guò),在 Linux 下對(duì)設(shè)備的操作方
54、法與對(duì)文件的操作方法是一樣的,因此,對(duì)串口的讀寫(xiě)就可以使用簡(jiǎn)單的“read”,“write”函數(shù)來(lái)完成,所不同的是只是需要對(duì)串口的其他參數(shù)另做配置,下面就來(lái)詳細(xì)講解串口應(yīng)用開(kāi)發(fā)的步驟。root(none) 1# cat hello1 Hello!root(none) 1# cat hello2 Hello!Hello!華清遠(yuǎn)見(jiàn)培訓(xùn)http:.cn圖 6.3 25 引腳串行接口圖6.4.2串口設(shè)置詳解本節(jié)主要講解設(shè)置串口的主要方法。如前所述,設(shè)置串口中最基本的包括波特率設(shè)置,校驗(yàn)位和停止位設(shè)置。串口的設(shè)置主要是設(shè)置 struct termios 結(jié)構(gòu)體的各成員值,如下所示:在這個(gè)結(jié)構(gòu)中最為重要的
55、是 c_cflag,通過(guò)對(duì)它的賦值,用戶可以設(shè)置波特率、字符大小、數(shù)據(jù)位、停止位、奇偶校驗(yàn)位和硬件流控等。另外 c_iflag 和 c_cc 也是比較常用的標(biāo)志。在此主要對(duì)這 3 個(gè)成員進(jìn)行詳細(xì)說(shuō)明。c_cflag 支持的常量名稱如表 6.10 所示。其中設(shè)置波特率為相應(yīng)的波特率前加上B,由于數(shù)值較多,本表沒(méi)有全部列出。表 6.10c_cflag 支持的常量名稱CBAUD波特率的位掩碼B00 波特率(放棄DTR)include<termios.h> struct termiounsigned short c_iflag; /* 輸入模式標(biāo)志 */ unsigned short c_oflag;/* 輸出模式標(biāo)志 */ unsigned short c_cflag;/* 控制模式標(biāo)志*/ unsigned short c_lflag;/*本地模式標(biāo)志 */unsigned char c_line;/* line discipline */ unsigned char c_ccNCC;/* control c
溫馨提示
- 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ì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 人民村出租田地合同范本
- 農(nóng)村院落租房合同范本
- 個(gè)人購(gòu)買(mǎi)地皮合同范本
- 鄉(xiāng)鎮(zhèn)門(mén)面房購(gòu)房合同范本
- 公司租地協(xié)議合同范本
- 企業(yè)招商加盟合同范本
- 出租水泥模具合同范本
- 北京市公寓出租合同范例
- 個(gè)人房屋托管合同范本
- 農(nóng)村農(nóng)民工勞動(dòng)合同范本
- 四川省自貢市、遂寧市、廣安市等2024-2025學(xué)年高一上學(xué)期期末考試語(yǔ)文試題 含解析
- 22G614-1 砌體填充墻結(jié)構(gòu)構(gòu)造
- 2024年全國(guó)教育大會(huì)精神全文課件
- 人教版八年級(jí)下冊(cè)歷史教案全冊(cè)
- GB/T 44464-2024汽車數(shù)據(jù)通用要求
- 2024年新改版青島版(六三制)四年級(jí)下冊(cè)科學(xué)全冊(cè)知識(shí)點(diǎn)
- 人教版八年級(jí)信息技術(shù)下冊(cè)全冊(cè)教案
- GB/T 25085.3-2020道路車輛汽車電纜第3部分:交流30 V或直流60 V單芯銅導(dǎo)體電纜的尺寸和要求
- 幼兒園教育活動(dòng)設(shè)計(jì)與實(shí)踐 張琳主編 PPT
- 小學(xué)英語(yǔ)微課ppt
- 小學(xué)交通安全主題班會(huì):《一盔一帶 安全出行》
評(píng)論
0/150
提交評(píng)論