20145227《資訊保安系統設計基礎》第九周學習總結
第十章 系統級I/O
unix i/o
一個Unix檔案就是一個m個位元組的序列,所有的I/O裝置都被模型化為檔案,而所有的輸入和輸出都被當做對應檔案的讀和寫來執行。這被稱為UnixI/O,使得所有的輸入和輸出能以一種統一且一致的方式來執行:
1、開啟檔案
應用程式向核心發出請求→要求核心開啟相應的檔案→核心返回檔案描述符
Unix外殼建立的每個程式開始時都有三個開啟的檔案:
標準輸入——0(STDIN_FILENO)
標準輸出——1(STDOUT_FILENO)
標準錯誤——2(STDERR_FILENO)
2、改變當前的檔案位置
對於每個開啟的檔案,核心保持著一個檔案位置k,初始為0。這個檔案位置是從檔案開頭起始的位元組偏移量。通過執行seek操作設定檔案位置為k。
3、讀寫檔案
(1)讀操作
讀操作就是從檔案拷貝n>0個位元組到儲存器,並且改變檔案當前位置。(如果當前位置是k,則改變為k+n)
EOF的來源:
檔案結尾處沒有明確的EOF訊號,是當檔案當前位置的數值超過了檔案大小時,會處罰一個稱為end-of-file的條件,能夠被應用程式檢測到,這就是所謂的EOF訊號。
(2)寫
寫操作是從儲存器拷貝n>0個位元組到一個檔案,然後更新當前檔案位置。
開啟和關閉檔案
- open函式將filename轉換為一個檔案描述符,並且返回描述符數字。
開啟一個已存在的檔案或者建立一個新檔案:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(char *filename,int flags,mode_t mode) (若成功則返回新檔案描述符,若出錯為-1)
flags引數指明瞭程式如何訪問檔案,常見取值:
O_RDONLY:只讀
O_WRONLY:只寫
O_RDWR:可讀可寫O_CREAT:檔案不存在,就建立新檔案
O_TRUNC:如果檔案存在,就截斷它
O_APPEND:寫操作前設定檔案位置到結尾處mode:指定了新檔案的訪問許可權位,符號名字如下所示:
- 關閉一個開啟的檔案:
#include <unisted.h>
int close(int fd)(若成功則為0,若出錯則為-1)
返回值:成功返回0,出錯返回-1
關閉一個已經關閉的描述符會出錯
fd:即檔案的描述符。
讀和寫檔案
- 讀檔案:read函式從描述符為fd的當前檔案位置拷貝最多n個位元組到儲存器位置buf。返回值表示實際傳送的位元組數量,錯誤返回-1,EOF返回0。
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t n);
- 寫檔案:write函式從儲存器位置buf拷貝至多n個位元組到描述符fd的當前檔案位置。
#include <unistd.h>
ssize_t write(int fd, void *buf, size_t n);
通過呼叫lseek函式,應用程式可以顯示地修改當前檔案的位置。
出現不足值(指在某些情況下,read和write傳送的位元組比應用程式要求的要少)的原因:
- 讀的時候遇到EOF:檔案末尾剩餘的位元組數不足讀取檔案的位元組片大小。
- 從終端讀文字行:若開啟檔案與終端相關聯,則每個read函式將一次傳送一個問本行。返回的不足值等於文字行的大小。
- 讀和寫socket:若開啟的檔案對應於網路套接字,那麼內部緩衝約束和較長的網路延遲會引起read和write返回不足值。
用RIO包健壯地讀寫
1、RIO的無緩衝的輸入輸出函式
rio_readn函式和rio_writen函式,應用程式可以在儲存器和檔案之間直接傳送資料:
#include "csapp.h"
ssize_t rio_readn(int fd, void *usrbuf, size_t n);
ssize_t rio_writen(int fd, void *usrbuf, size_t n);
若成功則返回傳送成功的位元組數,若EOF則為0(只對rio_readn而言),若出錯 則為-1。
2.RIO的帶緩衝的輸入函式
rio_ readlineb和rio_readnb函式從一個內部讀緩衝區拷貝一個文字行,當緩衝區變空時,會自動呼叫read重新填滿緩衝區。
void rio_readinitb(rio_t *rp,int fd);(無返回)
ssize_t rio_readlineb(rio_t *rp,void *usrbuf,size_t maxlen);
ssize_t rio_readnb(rio_t *rp,void *usrbuf,size_n);
若成功則返回傳送成功的位元組數,若EOF則為0,若出錯 則為-1。
開啟每一個描述符都會呼叫一次rio_readinitb函式,他將描述符fd和地址rp處的一個型別為rio_t的讀緩衝區聯絡起來。
讀取檔案後設資料
應用程式能通過呼叫stat和fstat函式,檢索到關於檔案的資訊(後設資料)。
#include <unistd.h>
#include <sys/stat.h>
int stat(const char *filename, struct stat *buf);
int fstat(int fd,struct stat *buf);
返回值:成功為0,錯誤為-1
st_size:包含檔案的位元組數大小
st_mode:包編碼檔案訪問許可位和檔案型別。
普通檔案包括某種型別的二進位制或文字資料。
目錄檔案包含關於其他檔案的資訊。
套接字是一種用來通過網路與其他程式通訊的檔案。
共享檔案
核心用三個相關的資料結構來表示其開啟的檔案:
描述符表:表項由程式開啟的檔案描述符來索引的,每個開啟的描述符表指向檔案表中的一個表項,每個程式有其獨立的描述符表。
檔案表:開啟檔案的集合是由一張檔案表來表示的,所有的程式共享這張表。包括:當前的檔案位置、引用計數、以及一個指向v-node表中對應表項的指標。
v-node表:每個表項包含stat結構中的大多數資訊,;包括st_mode和st_size成員,所有程式共享。
I/O重定向
- I/O重定向操作符,允許使用者將磁碟檔案和標準輸入輸出聯絡起來。
unix> ls > foo.txt
- I/O重定向是依靠dup2函式工作的。dup2函式拷貝描述符表表項oldfd到描述符表項newfd,覆蓋描述符表表項newfd以前的內容。如果newfd已經開啟,dup2會在拷貝oldfd之前關閉newfd。
標準I/O
- 標準I/O庫將一個開啟的檔案模型化為一個流,也就是一個指向FILE型別的結構的指標。
#include <stdio.h>
extern FILE *stdin; /*標準輸入,檔案描述符為0*/
extern FILE *stdout; /*標準輸出,檔案描述符為1*/
extern FILE *stderr; /*標準錯誤,檔案描述符為2*/
I/O函式的使用
- 應用程式可以通過open、close、lseek、read、write和stat這樣的函式來訪問Unix I/O。
- RIO函式:read和write的健壯的包裝函式,自動處理不足值,為讀文字行提供一種高效的帶緩衝的方法。
- 標準I/O函式:提供了Unix I/O函式的一個更加完整的帶緩衝的替代品,包括格式化的I/O例程。是磁碟和終端裝置I/O之選。
- 套接字描述符:Unix對網路的抽象是一種稱為套接字的檔案型別,被稱為套接字描述符。應用程式通過讀寫套接字描述符來與執行在其他計算機上的程式通訊。
對流I/O限制是:
- 跟在輸出函式之後的輸入函式,必須在其中間插入fflush、fseek、fsetpos或者rewind函式,後三個函式使用Unix I/O中的lseek函式來重置當前的檔案位置。
- 跟在輸入函式之後的輸出函式,必須在中間插入fseek、fsetpos或者rewind的呼叫,一個輸出函式不能跟隨在一個輸入函式之後,除非該輸入函式遇到了一個EOF。
解決對流I/O限制的方法是:
- 採用在每個輸入操作前重新整理快取區這樣的規則來滿足。
- 對同一個開啟的套接字描述符開啟兩個流,一個用來讀,一個用來寫。
- 對套接字使用lseek函式是非法的。
- 在網路套接字上,使用RIO函式更常見。
遇到的問題和解決過程
問題:執行教材P598練習10.1時出錯:
解決:
- 缺少csapp.h的標頭檔案,這是書的作者編寫的一個標頭檔案,使用的時候要把此標頭檔案csapp.h和csapp.c檔案包含到你的系統中。先到網上下載這兩個檔案,下載地址(http://download.csdn.net/detail/tzasd89812/4206284);
- 在命令列下輸入
sudo mv csapp.h csapp.c /usr/include
指令將檔案移到/usr/include
中;開啟csapp.h標頭檔案,在#end if
前面加上一句#include <csapp.c>
- 由於csapp.c中包含執行緒的一部分,所以編譯的時候要加上-lpthread選項,否則很多錯誤,此時編譯執行:
- 還是出錯了。按照提示將程式碼中的Open,Close換成了open和close,之後成功執行:
實踐
main函式的定義:int main(int argc, char *argv[]){}
經查閱得知,argc是用來表示在命令列下輸入命令時的引數個數,包括指令本身;argv[]是用來取得你輸入的引數。
涉及到的標頭檔案的用處:
stdio.h 標準輸入輸出
stdlib.h C標準函式庫
unistd.h Unix類系統定義符號常量
fcntl.h 定義了很多巨集和open,fcntl函式原型
sys/types.h 基本系統資料型別
dirent.h unix類目錄操作的標頭檔案,包含了許多UNIX系統服務的函式原型,例如opendir函式、readdir函式。
termios.h 在Posix規範中定義的標準介面
自己在虛擬機器裡實現了一些程式碼的功能,具體程式碼已經上傳到碼雲中。以下是實踐結果:
who
這個程式碼的思想是,從UTMP_FILE檔案中讀取想要的資訊到儲存器中,然後再用標準輸出函式列印到螢幕上,最後關閉檔案。
spwd
這個程式碼的功能是列出當前所在目錄。結果如下圖:
fileinfo
這個程式碼的功能是用來顯示檔案資訊的,建立了一個stat資料結構。
- 先判斷命令是否有運算元,有的話才能繼續進行下去,如果沒有報錯就列印出來相關檔案資訊,報錯就用perror將報錯資訊列印出來。
filesize
這段程式碼的功能是用st_size成員來計算檔案的位元組數大小。
本週程式碼託管連結
https://git.oschina.net/20145227/IS-Design-20145227/tree/master/ch09
本週程式碼總數
其他(感悟、思考等,可選)
這周學習的是Unix I/O,以及各種I/O包,對檔案的開啟和關閉、讀和寫等操作又有了更深的理解。學習中遇到的一個問題就是程式以及練習題程式碼中出現的csapp.h這個標頭檔案,由於“csapp.h”這個是教材編寫的標頭檔案,並不是計算機自帶的,要從網上下載下來才能測試程式碼。這一章的內容從頁碼上看起來很少,但是需要實踐的程式碼還是很多的。很多程式碼我並不太懂是什麼意思,查了很多資料才能大概有個瞭解,導致花費了很多時間。
學習進度條
程式碼行數(新增/累積) | 部落格量(新增/累積) | 學習時間(新增/累積) | 重要成長 | |
---|---|---|---|---|
目標 | 5000行 | 30篇 | 400小時 | |
第一週 | 0 | 2/2 | 20/20 | |
第二週 | 100/100 | 1/3 | 20/40 | |
第三週 | 200/300 | 1/4 | 22/62 | |
第五週 | 200/500 | 1/5 | 22/84 | |
第六週 | 274/774 | 1/6 | 22/106 | |
第七週 | 127/901 | 2/8 | 22/128 | |
第八週 | 50/951 | 2/10 | 22/150 | |
第九周 | 418/1369 | 2/12 | 22/172 |