操作系統API: 1、API是一些函數,這些函數是由linux系統提供支持的,由應用層程式來使用,應用層程式通過調用API來調用操作系統中的各種功能,來幹活 文件操作的一般步驟: 1、在linux系統中要操作一個文件,一般是先open打開一個文件,得到一個文件描述符,然後對文件進行讀寫操作(或其他操 ...
操作系統API:
1、API是一些函數,這些函數是由linux系統提供支持的,由應用層程式來使用,應用層程式通過調用API來調用操作系統中的各種功能,來幹活
文件操作的一般步驟:
1、在linux系統中要操作一個文件,一般是先open打開一個文件,得到一個文件描述符,然後對文件進行讀寫操作(或其他操作),最後close關閉文件即可
2、文件平時是存在塊設備中的文件系統中的,我們把這種文件叫靜態文件。當我們去open打開一個文件時,linux內核做的操作包括:內核在進程中建立了一個打開文件的數據結構,記錄下我們打開的這個文件;內核在記憶體中申請一段記憶體,並且將靜態文件的內容從塊設備中讀取到記憶體中特定地址管理存放(叫動態文件)。打開文件後,以後對這個文件的讀寫操作,都是針對記憶體中這一份動態文件的,而並不是針對靜態文件的。當我們對動態文件進行讀寫後,此時記憶體中的動態文件和塊設備中的靜態文件就不同步了,當我們close關閉動態文件時,close內部內核將記憶體中的動態文件的內容去更新(同步)塊設備中的靜態文件。
3、為什麼要這麼設計?
以為塊設備本身有讀寫限制(回憶NnadFlash、SD等塊設備的讀寫特征),本身對塊設備進行操作非常不靈活。而記憶體可以按位元組為單位來操作,而且可以隨機操作(記憶體就叫RAM,random),很靈活。所以內核設計文件操作時就這麼設計了
文件描述符:
1、文件描述符其實實質是一個數字,這個數字在一個進程中表示一個特定的含義,當我們open打開一個文件時,操作系統在記憶體中構建了一些數據結構來表示這個動態文件,然後返回給應用程式一個數字作為文件描述符,這個數字就和我們記憶體中維護這個動態文件的這些數據結構掛鉤綁定上了,以後我們應用程式如果要操作這一個動態文件,只需要用這個文件描述符進行區分。
2、文件描述符的作用域就是當前進程,出了當前進程這個文件描述符就沒有意義了
實時查man手冊
(1)當我們寫應用程式時,很多API原型都不可能記得,所以要實時查詢,用man手冊
(2)man 1 xx查linux shell命令,man 2 xxx查API, man 3 xxx查庫函數
讀取文件內容
ssize_t read(int fd, void *buf, size_t count);
fd表示要讀取哪個文件,fd一般由前面的open返回得到
buf是應用程式自己提供的一段記憶體緩衝區,用來存儲讀出的內容
count是我們要讀取的位元組數
返回值ssize_t類型是linux內核用typedef重定義的一個類型(其實就是int),返回值表示成功讀取的位元組數。
exit、_exit、_Exit退出進程
(1)當我們程式在前面步驟操作失敗導致後面的操作都沒有可能進行下去時,應該在前面的錯誤監測中結束整個程式,不應該繼續讓程式運行下去了。
(2)我們如何退出程式?
第一種;在main用return,一般原則是程式正常終止return 0,如果程式異常終止則return -1。
第一種:正式終止進程(程式)應該使用exit或者_exit或者_Exit之一
open函數的flag詳解:
讀寫許可權:O_RDONLY O_WRONLY O_RDWR
O_RDONLY就表示以只讀方式打開,
O_WRONLY表示以只寫方式打開,
O_RDWR表示以可讀可寫方式打開
當我們附帶了許可權後,打開的文件就只能按照這種許可權來操作
打開存在並有內容的文件時:O_APPEND、O_TRUNC
O_APPEND屬性去打開文件時,如果這個文件中本來是有內容的,則新寫入的內容會接續到原來內容的後面;
O_TRUNC屬性去打開文件時,如果這個文件中本來是有內容的,則原來的內容會被丟棄
1 fd = open("a.txt", O_RDWR | O_APPEND | O_TRUNC);
2 if (-1 == fd) // 有時候也寫成: (fd < 0)
3 {
4 printf("文件打開錯誤\n");
5 // return -1;
6 _exit(-1);
7 }
8 else
9 {
10 printf("文件打開成功,fd = %d.\n", fd);
11 }
打開不存在的文件時:O_CREAT、O_EXCL
open中加入O_CREAT後,不管原來這個文件存在與否都能打開成功,如果原來這個文件不存在則創建一個空的新文件,如果原來這個文件存在則會重新創建這個文件,原來的內容會被消除掉;
O_EXCL標誌和O_CREAT標誌結合使用時,則沒有文件時創建文件,有這個文件時會報錯提醒我們;
open函數在使用O_CREAT標誌去創建文件時,可以使用第三個參數mode來指定要創建的文件的許可權。mode使用4個數字來指定許可權的,其中後面三個很重要,對應我們要創建的這個文件的許可權標誌。譬如一般創建一個可讀可寫不可執行的文件就用0666
1 fd = open("a.txt", O_RDWR | O_CREAT | O_EXCL, 0666);
2 if (-1 == fd) // 有時候也寫成: (fd < 0)
3 {
4 perror("文件打開錯誤");
5 _exit(-1);
6 }
7 else
8 {
9 printf("文件打開成功,fd = %d.\n", fd);
10 }
O_NONBLOCK
(1)阻塞與非阻塞。如果一個函數是阻塞式的,則我們調用這個函數時當前進程有可能被卡住(阻塞住,實質是這個函數內部要完成的事情條件不具備,當前沒法做,要等待條件成熟),函數被阻塞住了就不能立刻返回;如果一個函數是非阻塞式的那麼我們調用這個函數後一定會立即返回,但是函數有沒有完成任務不一定。
(2)阻塞和非阻塞是兩種不同的設計思路,並沒有好壞。總的來說,阻塞式的結果有保障但是時間沒保障;非阻塞式的時間有保障但是結果沒保障。
(3)操作系統提供的API和由API封裝而成的庫函數,有很多本身就是被設計為阻塞式或者非阻塞式的,所以我們應用程度調用這些函數的時候心裡得非常清楚。
(4)我們打開一個文件預設就是阻塞式的,如果你希望以非阻塞的方式打開文件,則flag中要加O_NONBLOCK標誌。
(5)只用於設備文件,而不用於普通文件。
errno和perror
(1)errno就是error number,意思就是錯誤號碼。linux系統中對各種常見錯誤做了個編號,當函數執行錯誤時,函數會返回一個特定的errno編號來告訴我們這個函數到底哪裡錯了。
(2)linux系統提供了一個函數perror(意思print error),perror函數內部會讀取errno並且將這個不好認的數字直接給轉成對應的錯誤信息字元串,然後print列印出來。
read和write的count
(1)count和返回值的關係:count參數表示我們想要寫或者讀的位元組數,返回值表示實際完成的要寫或者讀的位元組數。實現的有可能等於想要讀寫的,也有可能小於(說明沒完成任務)
(2)count再和阻塞非阻塞結合起來,就會更加複雜。如果一個函數是阻塞式的,則我們要讀取30個,結果暫時只有20個時就會被阻塞住,等待剩餘的10個可以讀。
(3)有時候我們寫正式程式時,我們要讀取或者寫入的是一個很龐大的文件(譬如文件有2MB),我們不可能把count設置為2*1024*1024,而應該去把count設置為一個合適的數字(譬如2048、4096),然後通過多次讀取來實現全部讀完。
1 ret = write(fd, writebuf, strlen(writebuf));
2 if (ret < 0)
3 {
4 //printf("write失敗.\n");
5 perror("write失敗");
6 _exit(-1);
7 }
8 else
9 {
10 printf("write成功,寫入了%d個字元\n", ret);
11 }
文件IO效率和標準IO
(1)文件IO就指的是open、close、write、read等API函數構成的一套用來讀寫文件的體系,這套體系可以很好的完成文件讀寫,但是效率並不是最高的。
(2)應用層C語言庫函數提供了一些用來做文件讀寫的函數列表,叫標準IO。標準IO由一系列的C庫函數構成(fopen、fclose、fwrite、fread),這些標準IO函數其實是由文件IO封裝而來的(fopen內部其實調用的還是open,fwrite內部還是通過write來完成文件寫入的)。標準IO加了封裝之後主要是為了在應用層添加一個緩衝機制,這樣我們通過fwrite寫入的內容不是直接進入內核中的buf,而是先進入應用層標準IO庫自己維護的buf中,然後標準IO庫自己根據操作系統單次write的最佳count來選擇好的時機來完成write到內核中的buf(內核中的buf再根據硬碟的特性來選擇好的實際去最終寫入硬碟中)。