信息安全系统设计基础第八周学习总结
第十章 系統級I/O
學習目標:
1.掌握系統編程和系統調用的概念
2.掌握系統編程錯誤處理的方式
3.掌握Unix/Linux系統級I/O:open close read write seek stat
4.掌握RIO
5.掌握I/O重定向的方法
一、知識點梳理
(一)系統級I/O概述
1.輸入輸出I/O
輸入輸出I/O是在主存和外部設備(如磁盤,網絡和終端)之間拷貝數據的過程。 - 輸入就是從I/O設備拷貝數據到貯存 - 輸出就是從主存拷貝數據到I/O設備2.使用原因
- 幫助理解其它系統概念。I/O視操作系統中不可或缺的一部分,我們會經常遇到I/O和其它系統概念之間的循環依賴。
- 別無選擇。例如:標準I/O庫沒有提供讀取文件元數據的方式,如文件大小和文件創建時間。
(二)Unix I/O
一個Unix文件就是一個m個字節的序列:B0,B1,...,Bk,...,B(m-1)- 所有的I/O設備,如網絡、磁盤、和終端,都被模型化為文件,而所有的輸入和輸出都被當做想對應的文件的讀寫來執行。這種將設備優雅的映射為文件的方式,允許Unix內核引出一個簡單、低級的應用接口,稱為UnixI/O,這使得所有的輸入和輸出都能以一種統一且一致的方式來執行。
1.打開文件
一個應用程序通過要求內核來打開文件,內核返回一個小的非負整數(描述符),內核記錄有關這個文件的所有的信息,應用程序只需要記住這個描述符。
Unix外殼創建的每個進程開始時都有三個打開的文件:- 標準輸入(描述符為0)- 標準輸出(描述符為1)- 標準錯誤(描述符為2)頭文件<unistd.h>
定義常量STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO可以用來代替顯式的描述符
2.改變當前的文件位置。
- 對于每個打開的文件,內核保持著一個文件位置k,初始為0。這個位置是從文件開頭起始的字節偏移量。
- 應用程序可以通過seek操作顯式的設置文件的當前位置為k。
3.讀寫文件
讀操作就是從文件拷貝n>0個字節到存儲器,從當前文件位置k開始,然后將k增加到k+n。
給定一個大小為m字節的文件,k >= m 時執行讀操作會觸發一個稱為end-of-file(EOF)的條件,應用程序能檢測到這個條件,但是文件結尾處并沒有明確的“EOF符號”。寫操作就是從存儲器拷貝n>0個字節到一個文件,從當前文件位置k開始,然后更新k。
4.關閉文件
- 應用通知內核關閉這個文件。作為響應,內核釋放文件打開時創建的數據結構,并將這個描述符恢復到可用的描述符池當中。
- 無論進程因為何種原因終止時,內核都會關閉所有打開的文件并釋放他們的存儲器資源。
(三)打開和關閉文件
1.打開文件:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h>int open(char *filename,int fliags,mod_it mode); - 若成功,返回值為新文件描述符 - 若出錯,返回值為-1open函數將filename轉換成一個文件描述符,并且返回描述符數字。返回的描述符總是在進程中當前沒有打開的最小描述符。
fd = Open("文件名",flag參數,mode參數)- fd是返回的文件描述符(數字),總是返回在進程中當前沒有打開的最小描述符。
flag參數
表示訪問方式額外提示- O_RDONLY:只讀。- O_WRONLY:只寫。- O_RDWR:可讀可寫。一位或者多位掩碼的或- O_CREAT,表示如果文件不存在,就創建它的一個截斷的文件。- O_TRUNC:如果文件已經存在,就截斷它。- O_APPEND:在每次寫操作前,設置文件位置到文件的結尾處。mode參數:指定新文件的訪問權限位。作為上下文的一部分,每個進程都有一個umask,通過調用umask函數設置。當進程通過帶某個帶mode參數的open函數用來創建一個新文件的時候,文件的訪問權限位被設置為mode & ~umask。
給定mode和umask的默認值:#define DEF_MODE S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH#define DEF_UMASK S_IWGRP|S_IWOTH出錯的時候返回-1。
2.關閉文件:
#include<unistd.h> int close(int fd);- 若成功則返回0,不成功則為-1。
- 關閉一個已經關閉的描述符程序會出錯。
3.訪問權限位在sys/stat.h中定義
(四)讀和寫文件
1.讀函數
#include<unistd.h> ssize_t read(int fd,void *buf,size_t n);- 若成功,返回讀字節數,即實際傳送的字節數量 - 若EOF,返回0 - 若出錯,返回-1- read函數從描述符為fd的當前文件位置拷貝最多n個字節到存儲器位置buf。
注:何為EOF
即給定了m字節大小的文件,在從k字節位置開始讀或者寫的時候,發現k>=m。2.寫函數
#include<unistd.h> ssize_t write(int fd,const void *buf,size_t n);- 若成功,返回寫的字節數 - 若出錯,返回-1- write函數從存儲器位置buf拷貝至多n個字節到描述符fd的當前文件位置。
注:ssziet,sizet的區別
- ssziet被定義為int,有符號 - sizet被定義成unsigned int,無符號- 通過調用lseek函數,應用程序能夠顯示地修改當前文件的位置。
3.不足值
在某些情況下,read和write傳送的字節比應用程序要求的要少,這些不表示有錯誤。
- 讀時遇到EOF。假設準備讀一個文件,該文件從當前文件位置開始只含有20個字節,若以50個字節的片進行讀取,下一個read返回的不足值為20,此后的read將通過返回不足值0來發出EOF信號。- 從終端讀文本行。若打開文件與終端相關聯(如鍵盤和顯示器),那么每個read函數將以此傳送一個文本行,返回的不足值等于文本行大小。- 讀和寫網絡套接字。若打開的文件對應于網絡套接字,內部緩沖約束和較長的網絡延遲會引起read和write返回不足值。(進程間的通信機制:對Unix管道調用read和write時,也有可能出現不足值)- 實際上除了EOF,在讀寫磁盤文件時,不會遇到不足值。
如果想創建健壯的諸如web服務器這樣的網絡應用,就必須通過反復調用read和write處理不足值,直到所有需要的字節都傳送完畢。
(五)用RIO包健壯地讀寫
RIO包會自動處理不足值。RIO提供了兩類不同的函數: - 無緩沖的輸入輸出函數。這些函數直接在存儲器和文件之間傳送數據,沒有應用級緩沖,對將二進制數據讀寫到網絡和從網絡讀寫二進制數據尤其有用。 - 帶緩沖的輸入函數。這些函數允許高效地從文件中讀取文本行和二進制數據(函數從內部緩沖區中拷貝一個文本行,當緩沖區變空的時候,會自動地調用read重新填滿緩沖區),這些文件的內容緩存在應用級緩沖區內,類似于像printf這樣的標準I/O函數提供的緩沖區。帶緩沖的RIO輸入函數是線程安全的,它在同一個描述符上可以被交錯地調用。1.RIO的無緩沖的輸入輸出函數
通過調用rio_readn和rio_writen函數,應用程序可以在存儲器和文件之間直接傳送數據。
#include "csapp.h"//返回值:若成功為傳送的字節數,若EOF則為0,出錯為-1ssize_t rio_readn(int fd,void *usrbuf,size_t n);ssize_t rio_writen(int fd,void *usrbuf,size_t n);- rio_readn函數從描述符fd的當前文件位置最多傳送n和字節到存儲器位置usrbuf,遇到EOF時只能返回一個不足值。
- rio_writen函數從位置usrbuf傳送n個字節到描述符fd,絕不會返回不足值。
對于同一個描述符,可以任意交錯地調用rio_readn和rio_writen。
2.RIO的帶緩沖的輸入函數
一個文本行就是一個由換行符結尾的ASCII碼字符序列。在Unix系統中,換行符(‘\n')與ASCII碼換行符(LF)相同,數字值為0x0a。
- 實現計算文本文件中文本行的數量
- *方法一:用一個程序來計算文本文件中文本行的數量:用read函數來一次一個字節地從文件傳送到用戶存儲器,檢查每個字節來查找換行符。這個方法的缺點是效率低,每讀取文件中的一個字節都要求陷入內核。
*方法二:是調用一個包裝函數(rio_readlineb),它從一個內部讀緩沖區拷貝一個文本行,當緩沖區變空時,會自動地調用read重新填滿緩沖區。
對于既包含文本行也包含二進制數據的文件,書上提供了一個rio_readn帶緩沖區的版本:rio_readnb,它從和rio_readlineb一樣的讀緩沖區中傳送原始字節。
3.RIO讀程序的核心——rio_read函數
static ssize_t rio_read(rio_t *rp,char *usrbuf,size_t n) {int cnt;while(rp->rio_cnt<=0)//如果緩沖區為空,先調用函數填滿緩沖區再讀數據{rp->rio_cnt=read(rp->rio_fd,rp->rio_buf,sizeof(rp->rio_buf));//調用read函數填滿緩沖區if(rp->rio_cnt<0)//排除文件讀不出數據的情況{if(error != EINTR){return -1;}}else if(rp->rio_cnt=0)return 0;else rp->rio_bufptr = rp->rio_buf;//更新現在讀到的位置}cnt=n;if(rp->rio_cnt<n)cnt=rp->rio_cnt;//以上三步,將n與rp->rio_cnt中較小的值賦給cntmemcpy(usrbuf,rp->rio_bufptr,cnt);把讀緩沖區的內容拷貝到用戶緩沖區rp->rio_bufptr+=cnt;rp->rio_cnt-=cnt;return cnt; }(六)讀取文件元數據
1.元數據
應用程序能夠通過調用stat和fstat函數,檢索到關于文件的信息(元數據)。
#include <unistd.h>#include <sys/stat.h>int stat(cost char *filename,struc sta *buf);int fstat(int fd,struct stat *buf);- stat函數以文件名作為輸入- fstat函數以文件描述符作為輸入
2.stat數據結構
- st_size成員包含了文件的字節數大小 - st_mode成員編碼了文件訪問許可位和文件類型Unix提供的宏指令根據st_mode成員來確定文件的類型
宏指令:S_ISREG() 普通文件?二進制或文本數據宏指令:S_ISDIR() 目錄文件?包含其他文件的信息宏指令:S_ISSOCK() 網絡套接字?通過網絡和其他進程通信的文件
(七)共享文件
1.內核表示打開文件的三個相關的數據結構
- 描述符表:每個打開的描述符表項指向文件表中的一個表項 - 文件表:所有進程共享這張表,每個表項包括文件位置,引用計數,以及一個指向v-node表對應表項的指針 - v-node表:所有進程共享這張表,包含stat結構中的大多數信息(1)典型的打開文件的內核數據結構
- 描述符各自引用不同的文件,沒有共享文件。
(2)文件共享
- 多個描述符通過不同的文件表表項引用同一個文件。
、
- 兩個描述符通過打開兩個打開文件表表項共享同一個磁盤文件。 - 關鍵思想:每個描述符都有自己的文件位置,對不同描述符的讀操作可以從文件的不同位置獲取數據(3)父子進程共享文件
- 子進程繼承父進程打開文件。
(八)I/0重定向
重定向工作方式:使用dup2函數
#include<unistd.h>int dup2(int oldfd,int newfd);- dup2函數拷貝描述符表表項oldfd到描述符表表項newfd,覆蓋描述表表項newfd以前的內容。
- 若newfd已經打開,dup2會在拷貝oldfd之前關閉newfd。
(九)標準I/O和I/O函數
ANSI C定義了一組高級輸入輸出函數,稱為標準I/O庫。提供了打開和關閉文件的函數(fopen和fclose),讀和寫字節的函數(fread和fwrite),讀和寫字符串的函數(fgets和fputs),格式化I/O函數(scanf和printf)
標準I/O庫將一個打開的文件模型化為一個流。一個流就是一個指向FILE類型的結構的指針。每個ANSI C程序開始時都有三個打開的流:
#include<stdio.h>extern FILE *stdin; //標準輸入,描述符0extern FILE *stdout; //標準輸出,描述符1extern FILE *stderr; //標準錯誤,描述符2- 類型為FILE的流是對文件描述符和流緩存區的抽象。流緩沖區的目的和RIO讀緩沖區的一樣,就是使開銷較高的UnixI/O系統調用的數量盡可能的少。
- Unix I/O是在操作系統內核中實現的。
- 較高級別的RIO和標準I/O函數都是基于Unix函數來實現的。
- RIO函數是專為本書開發的read和write的健壯的包裝函數。他們自動處理不足值,并且為讀文本行提供一種高效的帶緩沖的方法
- 標準I/O函數提供了Unix函數的一個更加完整的帶緩沖的替代品,包括格式化的I/O例程。
- 標準I/O流,從某種意義上是全雙工的,但對流的限制和對套接字的限制有時會相互沖突。
- 限制一:跟在輸出函數之后的輸入函數
- 限制二:分在輸入函數的輸出函數
- 由此,建議在網絡套接字上不要使用標準 I/O函數來進行輸入和輸出,而要使用健壯的RIO函數。
二、實踐作業(這一部分還需要多研究...只有who命令我自己嘗試編寫了,感覺自己寫一遍和直接研究老師的代碼區別還是挺大的,思路會更清晰)
1.who
正版who應該是:
- 我們可以看到,who命令是查詢當前登錄的每個用戶,輸出包括用戶名、終端類型、登錄日期及遠程主機。
- man一下who,可以看到,who命令是讀取/var/run/utmp文件來得到以上信息的。
- man一下utmp,知道utmp這個文件,是二進制文件,里面保存的是結構體數組,這些數組是struct utmp結構體的。
自己嘗試編寫
偽代碼
- 打開記錄所在文件:utmp - 將文件中的記錄逐條讀取 - 每一條讀取的記錄都要在屏幕上打印 - 關閉文件問題1:
- 剛開始的時候覺得是警告就沒有在意,因為之前運行代碼的時候也有警告,但是最后可以運行出來。
- 但是:
問題1解決:
- 在record.ut_time輸出時我使用的類型是字符數組%s,但是實際上:
- 它在utmp中定義的是ut_tv.tv_sec,而ut_sec是int32_t型的。所以我換用了%ld輸出。
成功版1
問題2:
- 這個與正版的who運行看起來差別還是挺大的
- 第一,沒有對齊
- 第二,時間顯示看不懂
- 第三,記錄比正版who多
問題2解決
解決對齊問題:
- 對齊的問題很好解決。我使用的是\t制表符來排的,但是由于user的長度不一,導致錯位,可以換成固定長度,不足補0的方法顯示。
解決時間顯示問題:
- 使用man查找與time格式化相關(這一部分改了好久,最后還是去網上搜索的編寫who的相關內容、time的相關內容才得以解決)
- 判斷應該是strftime,進入查看用法,繼續修改try_who代碼
- 這個警告應該是64位機的問題,運行之后和第一次的問題一樣顯示核心已轉儲,但是我到現在都不知道怎么在原句上解決。
- 百度了一下想仔細看一下這個localtime的問題,找到一篇特別詳細的文章:c++ 時間類型詳解(time_t和tm),我決定分開每一步的變量試一試,直接在定義的時候就限制住每一個變量,至少能知道到底是哪一個錯了,結果沒有警告,編譯運行成功:
解決顯示記錄多的問題:
- 問題原因:utmp中保存的用戶,不僅僅是已經登陸的用戶,還有系統的其他服務所需要的,所以在顯出所有登陸用戶的時候,應該過濾掉其他用戶,只保留登陸用戶。在utmp結構中的ut_type可以區別,登陸用戶的ut_type是USER_PROCESS,加一個判斷就可以了:
- 終于和正版who至少看起來一樣了!!!
改進
- 在打開和關閉文件的時候加上失敗情況處理。
我的代碼
#include <stdio.h> #include <stdlib.h> #include <unistd.h> //read #include <sys/types.h> //open #include <sys/stat.h> #include <fcntl.h> #include <utmp.h> //utmp #include <time.h> //strftimeint main() {struct utmp record;int fd;int len = sizeof(record);struct tm *p;time_t t;char fortime[40];if ( (fd = open(UTMP_FILE, O_RDONLY)) == -1 ){perror( UTMP_FILE ); exit(1);}while(read(fd,&record,len)){if(record.ut_type == USER_PROCESS){printf("%-10.10s",record.ut_user);printf("%-10.10s",record.ut_line);t = record.ut_time; p = localtime(&t);strftime(fortime,40,"%F %R",p);printf("%12s",fortime);if(record.ut_host[0]!='\0');printf("\t(%s)",record.ut_host);printf("\n");}}if (close(fd) == -1){perror( UTMP_FILE ); exit(1);}return 0; }2.ls
3.filesize
- 用st_size計算文件的字節數大小。
4.fileinfo
- 這個功能用來實現顯示文件信息
5.setecho與echostate
setecho用來改變輸入指令是否可見。
輸入y(或是以y開頭的一串字符),命令可見否則(即輸入不以y開頭的字符),命令不可見echostate顯示輸入命令是否可見。
echo is on:命令可見echo is off:命令不可見
6.spwd
- 列出當前目錄
7.testioctl
- 獲得終端設備的窗口大小
- 下圖一個是我的終端全屏的,一個是還原之后的。
三、遇到的問題
運行書上代碼的也遇到問題,解決過程如下:
1.老師在開學的時候上傳的code中有“csapp.h”、“csapp.c”,剛開始的時候我把文件放與書上10.1代碼相同的文件夾中,但是生成.o文件后出現問題:
2.我覺得出現這種情況的原因,應該之前書上說的是C編譯為.o文件的時候并不需要函數的具體實現,只要有函數的原型即可,但是在鏈接為可執行文件的時候就必須要具體的實現。
查看.h和.c文件,發現.c文件中才是定義的那些函數的具體實現,結合第一次靜態庫的實踐,這個.c文件就相當于那些add.c、sub.c...10-1的代碼相當于那個main函數,所以可以將其做成一個靜態庫來使用,但是又出現新的問題:
3.結合深入理解計算機中的csapp.h和csapp.c的分析,因為csapp.c中有關于線程的頭文件,所以需要加上-lpthread,之后就可以運行教材上的代碼了。
這個問題貌似很多人都在問,有的直接把教材上的Open、Close改成了open、close,我覺得這樣改就失去了書上給這些Unix I/O函數改進的意義了。
四、其它
五、參考資料
參考資料1:深入理解計算機系統(第二版)
參考資料2:博客園:深入理解計算機中的csapp.h和csapp.c
參考資料3:編寫who命令--從Linux中學習Linux
參考資料4:c++ 時間類型詳解(time_t和tm)
轉載于:https://www.cnblogs.com/hyq20135317/p/4946981.html
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的信息安全系统设计基础第八周学习总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 百度兴趣点下载工具设计和实现
- 下一篇: iOS9 判断微信qq是否安装