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. 文件描述符
对于内核而言,所有打开文件都由文件描述符引用。文件描述符是一个非负整数。当打开
一个现存文件或创建一个新文件时,内核向进程返回一个文件描述符。当读、写一个文件时,用open
或creat
返回的文件描述符标识该文件,将其作为参数传送给read
或write
。
默认情况下,程序在开始运行时,系统会自动打开三个文件描述符,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版本中, open的第二个参数只能是 0、 1或2。没有办法打开一
个尚未存在的文件,因此需要另一个系统调用 creat以创建新文件。现在, open函
数提供了选择项O_CREAT和O_TRUNC,于是也就不再需要creat函数了。
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_APPEND选择项,否则该位移量被设置为0。
可以调用lseek显式地定位一个打开文件。
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int filedes, off_to ffset, int whence) ;
//返回:若成功为新的文件位移,若出错为- 1
对参数offset 的解释与参数whence的值有关。
- 若whence是S E E K _ S E T,则将该文件的位移量设置为距文件开始处 offset 个字节。
- 若whence是S E E K _ C U R,则将该文件的位移量设置为其当前值加offset, offset可为正或负。
- 若whence是S E E K _ E N D,则将该文件的位移量设置为文件长度加offset, offset可为正或负。
普通文件的偏移量必须是非负整数。偏移量可以大于文件的长度,这样之后的写会形成一个空洞,空洞不占存储,其中的字节被读为0。
6. read / write函数
用read函数从打开文件中读数据。
#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
其运行结果为: