Linux下文件I/O詳解
1. 文件I/O與標准I/O區別
文件I/O:文件I/O稱之為不帶緩存的IO(unbuffered I/O),不帶緩存指的是每個read和write都調用內核中的一個系統調用,也就是一般所說的低級I/O——操作系統提供的基本IO服務,與os綁定,特定於Linux或Unix平台。這些不帶緩存的I/O函數不是ANSI C的組成部分,但是是P O S I X . 1和X P G 3的組成部分。
標准I/O:標准I/O是ANSI C建立的一個標准I/O模型,是一個標准函數包和stdio.h頭文件中的定義,具有一定的可移植性。標准I/O庫處理很多細節。例如緩存分配,以優化長度執行I/O等。標准的I/O提供了三種類型的緩存。
它們函數使用的區別如下:
這里不再過多討論標准I/O,目前只以文件I/O作為講解。
大多數Unix文件I/O只需用到5個函數:open、read、write、lseek 和 close
。
2. 文件描述符
對於內核而言,所有打開文件都由文件描述符引用。文件描述符是一個非負整數。當打開
一個現存文件或創建一個新文件時,內核向進程返回一個文件描述符。當讀、寫一個文件時,用o p e n或c r e a t返回的文件描述符標識該文件,將其作為參數傳送給 r e a d或w r i t e。
默認情況下,程序在開始運行時,系統會自動打開三個文件描述符,0是標准輸入,1是標准輸出,2是標准錯誤。POSIX標准要求每次打開文件時(含socket)必須使用當前進程 中最小可用的文件描述符號碼,因此第一次打開的文件描述符一定是3。
文件描述符 | 用途 | POSIX文件描述符 | 標准I/O文件流 |
---|---|---|---|
0 | 標准輸入 | STDIN_FILENO | stdin |
1 | 標准輸出 | STDOUT_FILENO | stdout |
2 | 標准出錯 | STDERR_FILENO | stderr |
3. open / creat函數
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int oflag, ... /* mode_t m o d e */ ) ;
int creat(const char *pathname, mode_t mode);
//返回:若成功為文件描述符,若出錯為- 1
open()系統調用用來打開一個文件,並返回一個文件描述符(file description), 並且該文件描述符是當前進程最小、未使用的 文件描述符數值。
參數:
pathname
: 要打開的文件、設備的路徑;
oflag
: 由多個選項進行或運算
構造oflag參數。
必選:
- O_RDONLY (只讀)
- O_WRONLY(只寫)
- O_RDWR(讀寫)
可選:
- O_APPEND 每次寫時都追加到文件的尾端。
- O_CREAT 文件不存在則創建它,使用該選項需要第三個參數mode
- O_TRUNC 如果文件存在,而且為只寫或讀寫成功打開,則將其長度截取為0;
- O_NONBLOCK 如果path是一個FIFO、塊設備、字符特殊文件則此選項為文件的本次打開和后續的I/O操作 設置非阻塞模式方式。
- O_EXEC、O_SEARCH、O_CLOEXEC、O_NOCTTY…
mode
: oflag帶O_CREAT選項時可以用來創建文件,這時必須帶該參數用來指定創建文件的權限模式,如0666。 否則不需要。
注意,以下此函數等價:
/* 兩者等價 */
creat(pathname, mode);
open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode)
在早期的U N I X版本中, o p e n的第二個參數只能是 0、 1或2。沒有辦法打開一
個尚未存在的文件,因此需要另一個系統調用 c r e a t以創建新文件。現在, o p e n函
數提供了選擇項O _ C R E AT和O _ T R U N C,於是也就不再需要c r e a t函數了。
c r e a t的一個不足之處是它以只寫方式打開所創建的文件。在提供 o p e n的新版本之前,如果要創建一個臨時文件,並要先寫該文件,然后又讀該文件,則必須先調用 c r e a t, c l o s e,然后再調用o p e n。現在則可用下列方式調用o p e n:
open(pathname, O_RDWR | O_CREAT | O_TRUNC, mode)
4. close函數
可用c l o s e函數關閉一個打開文件:
#include <unistd.h>
/* 關閉打開的文件 * @return 成功返回0,出錯返回-1 */
int close(int fd);
功能:指定相應的文件描述符就可以關閉打開的文件。
參數:fd
文件描述符,是open或者creat返回的非負整數。
5. lseek函數
每個打開文件都有一個與其相關聯的“當前文件位移量”。它是一個非負整數,用以度量
從文件開始處計算的字節數。 通常,讀、寫操作都從當前文件位移量處開始,並使位移量增加所讀或寫的字節數。按系統默認,當打開一個文件時,除非指定O _ A P P E N D選擇項,否則該位移量被設置為0。
可以調用l s e e k顯式地定位一個打開文件。
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int filedes, off_to ffset, int whence) ;
//返回:若成功為新的文件位移,若出錯為- 1
對參數offset 的解釋與參數w h e n c e的值有關。
- 若w h e n c e是S E E K _ S E T,則將該文件的位移量設置為距文件開始處 offset 個字節。
- 若w h e n c e是S E E K _ C U R,則將該文件的位移量設置為其當前值加offset, offset可為正或負。
- 若w h e n c e是S E E K _ E N D,則將該文件的位移量設置為文件長度加offset, offset可為正或負。
普通文件的偏移量必須是非負整數。偏移量可以大於文件的長度,這樣之后的寫會形成一個空洞,空洞不占存儲,其中的字節被讀為0。
6. read / write函數
用r e a d函數從打開文件中讀數據。
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
//返回:讀到的字節數,若已到文件尾為 0,若出錯為- 1
ssize_t write(int fd, const void *buf, size_t count);
//返回:若成功為已寫的字節數,若出錯為- 1
帶符號整數( ssize_t);不帶符號整數(size_t)。
參數:
fd:相應文件的文件描述符;
buf:讀/寫的緩沖區;
count:對read,為要讀的字節數; 對write,為buf的大小。
read
有多種情況可使實際讀到的字節數少於要求讀字節數:
- 讀普通文件時,在讀到要求字節數之前已到達了文件尾端。例如,若在到達文件尾端之前還有3 0個字節,而要求讀1 0 0個字節,則r e a d返回3 0,下一次再調用r e a d時,它將返回0 (文件尾端);
- 當從終端設備讀時,通常一次最多讀一行;
- 當從網絡讀時,網絡中的緩沖機構可能造成返回值小於所要求讀的字節數;
- 某些面向記錄的設備,例如磁帶,一次最多返回一個記錄。
7. DS18B20溫度采集
文件file_io.c如下:
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "file_io.h"
#define BUFSIZE 1024
//#define MSG_STR "Hello World\n"
int main(int argc, char *argv[])
{
int fd = -1;
int rv = -1;
char buf[BUFSIZE];
float temper;
fd=open("test.txt", O_RDWR|O_CREAT|O_TRUNC, 0666);
if(fd < 0)
{
perror("Open/Create file test.txt failure");
return 0;
}
printf("Open file returned file descriptor [%d]\n", fd);
if((get_temper(&temper)) < 0)
{
printf("Get temperature failure: %s\n", strerror(errno));
goto cleanup;
}
memset(buf, 0, sizeof(buf));
snprintf(buf, sizeof(buf), "%.2f%c", temper, 'C');
if( (rv=write(fd, buf, strlen(buf))) < 0 )
{
printf("Write %d bytes into file failure: %s\n", rv, strerror(errno));
goto cleanup;
}
lseek(fd, 0, SEEK_SET);
memset(buf, 0, sizeof(buf));
if( (rv=read(fd, buf, sizeof(buf))) < 0 )
{
printf("Read data from file failure: %s\n", strerror(errno));
goto cleanup;
}
printf("Read %d bytes data from file: %s\n", rv, buf);
cleanup:
close(fd);
return 0;
}
文件file_io.h如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <syslog.h>
#include <errno.h>
/* 溫度放置的路徑 */
//#define filepath /28-041731f7c0ff/w1_slave
int get_temper(float *temper);
int get_temper(float *temper)
{
char filepath[120]="/";
char f_name[50];
char data_array[1024];
char *data_p=NULL;
struct dirent *file=NULL;
DIR *dir=NULL;
int data_fd;
int found = -1;
//float *temper;
if((dir = opendir(filepath)) < 0)
{
printf("opendir file failure: %s\n",strerror(errno));
return -1;
}
while((file = readdir(dir)) != NULL)
{
//if((strcmp(file->d_name, ".", 1) == 0) || (strcmp(file->d_name, ".", 1) == 0))
//continue; //ignore '.' and '..' file
if(strstr(file->d_name, "28-"))
{ //memset(f_name, 0, sizeof(f_name));
strncpy(f_name, file->d_name, sizeof(f_name)); //locate reserve temperature data file path
found = 1;
printf("reserve temperature data file path: %s\n",f_name);
}
//closedir(dir);
}
closedir(dir);
/* found == 0; 未找到目的文件夾 */
if(!found)
{
printf("Can not find the folder\n");
return 0;
}
/* 找到相應文件夾后,切換至該文件夾下以獲取溫度數據 */
strncat(filepath, f_name, sizeof(filepath)-strlen(filepath)); //將文件夾名連接到filepath路徑后
strncat(filepath, "/w1_slave", sizeof(filepath)-strlen(filepath)); //將設備文件夾下存放溫度的文件連接到filepath路徑后
//printf("%s\n", f_name);
printf("%s\n", filepath );
data_fd=open(filepath, O_RDONLY);
if(data_fd < 0)
{
printf("open file failure: %s\n", strerror(errno));
return -2;
}
memset(data_array, 0, sizeof(data_array));
if(read(data_fd, data_array, sizeof(data_array)) < 0)
{
printf("read file failure: %s\n", strerror(errno));
return -3;
}
/* data_p指針后移兩個字符單位,其后即為溫度數據 */
data_p=strstr(data_array, "t=");
data_p=data_p+2; //local temperature data
*temper=atof(data_p)/1000; //"()" priority super "/"
close(data_fd);
return 0;
}
這里是DS18B20溫度的采集內容,這里我只取一次溫度采集數據,然后分析出溫度值。
路徑為:/28-041731f7c0ff
文件w1_slave
內容如下:
fe 00 4b 46 7f ff 0c 10 56 : crc=56 YES
fe 00 4b 46 7f ff 0c 10 56 t=15875
其運行結果為: