1、Linux中I/O讲解
今天,我们重新学习C语言的基础,特别是I/O口的讲解。
所谓标准 I/O 库则是标准 C 库中用于文件 I/O 操作(譬如读文件、写文件等)相关的一系列库函数的集合,通常标准 I/O 库函数相关的函数定义都在头文件<stdio.h>中,所以我们需要在程序源码中包含<stdio.h>头文件。
标准 I/O 库函数是构建于文件 I/O(open()、read()、write()、lseek()、close()等)这些系统调用之上的,譬如标准 I/O 库函数 fopen()就利用系统调用 open()来执行打开文件的操作、fread()利用系统调用 read()来执行读文件操作、fwrite()则利用系统调用 write()来执行写文件操作等等。
大家需要有一个意识的认知,标准 I/O 与 文件I/O的区别:
- 虽然标准 I/O 和文件 I/O 都是 C 语言函数,但是标准 I/O 是标准 C 库函数,而文件 I/O 则是 Linux系统调用;
- 标准 I/O 是由文件 I/O 封装而来,标准 I/O 内部实际上是调用文件 I/O 来完成实际操作的;
- 可移植性:标准 I/O 相比于文件 I/O 具有更好的可移植性,通常对于不同的操作系统,其内核向应用层提供的系统调用往往都是不同,譬如系统调用的定义、功能、参数列表、返回值等往往都是不一样的;而对于标准 I/O 来说,由于很多操作系统都实现了标准 I/O 库,标准 I/O 库在不同的操作系统之间其接口定义几乎是一样的,所以标准 I/O 在不同操作系统之间相比于文件 I/O 具有更好的可移植性。
- 性能、效率:标准 I/O 库在用户空间维护了自己的 stdio 缓冲区,所以标准 I/O 是带有缓存的,而文件 I/O 在用户空间是不带有缓存的,所以在性能、效率上,标准 I/O 要优于文件 I/O。
2、open函数
在 Linux 系统中要操作一个文件,需要先打开该文件,得到文件描述符,然后再对文件进行相应的读写操作(或其他操作),最后在关闭该文件;open 函数用于打开文件,当然除了打开已经存在的文件之外,还可以创建一个新的文件;
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
在 Linux 系统下,可以通过 man 命令(也叫 man 手册)来查看某一个 Linux 系统调用的帮助信息,man命令可以将该系统调用的详细信息显示出来,譬如函数功能介绍、函数原型、参数、返回值以及使用该函数所需包含的头文件等信息;man 更像是一份帮助手册,所以也把它称为 man 手册,当我们需要查看某个系统调用的功能介绍、使用方法时,不用在上网到处查找,直接通过 man 命令便可以搞定,man 命令用法如下所示:
man 2 open #查看 open 函数的帮助信息
Tips:man 命令后面跟着两个参数,数字 2 表示系统调用,man 命令除了可以查看系统调用的帮助信息外,还可以查看 Linux 命令(对应数字 1)以及标准 C 库函数(对应数字 3)所对应的帮助信息;最后一个参数 open 表示需要查看的系统调用函数名。
在这里插入图片描述
open函数解析
pathname:字符串类型,用于标识需要打开或创建的文件,可以包含路径(绝对路径或相对路径)信息,譬如:“./src_file”(当前目录下的 src_fil件)、"/home/dengtao/hello.c"等;如果 pathname 是一个符号链接,会对其进行解引用。
flags:调用 open 函数时需要提供的标志,包括文件访问模式标志以及其它文件相关标志,这些标志使用宏定义进行描述,都是常量,open 函数提供了非常多的标志,我们传入 flags 参数时既可以单独使用某一个标志,也可以通过位或运算(|)将多个标志进行组合。这些标志介绍如下:
3.Write写文件
调用 write 函数可向打开的文件写入数据,其函数原型如下所示(可通过"man 2 write"查看):
man 2 write
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
查看函数调用方法:
fd:文件描述符。关于文件描述符,前面已经给大家进行了简单地讲解,这里不再重述!我们需要将进
行写操作的文件所对应的文件描述符传递给 write 函数。
buf:指定写入数据对应的缓冲区。
count:指定写入的字节数。
返回值:如果成功将返回写入的字节数(0 表示未写入任何字节),如果此数字小于 count 参数,这不是错误,譬如磁盘空间已满,可能会发生这种情况;如果写入出错,则返回-1。
4.read读文件
调用 read 函数可从打开的文件中读取数据,其函数原型如下所示(可通过"man 2 read"查看):
man 2 read
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
函数参数和返回值含义如下:
fd:文件描述符。与 write 函数的 fd 参数意义相同。
buf:指定用于存储读取数据的缓冲区。
count:指定需要读取的字节数。
返回值:如果读取成功将返回读取到的字节数,实际读取到的字节数可能会小于 count 参数指定的字节数,也有可能会为 0,譬如进行读操作时,当前文件位置偏移量已经到了文件末尾。实际读取到的字节数少于要求读取的字节数,譬如在到达文件末尾之前有 30 个字节数据,而要求读取 100 个字节,则 read 读取成功只能返回 30;而下一次再调用 read 读,它将返回 0(文件末尾)。
5. lseek函数
对于每个打开的文件,系统都会记录它的读写位置偏移量,我们也把这个读写位置偏移量称为读写偏移量,记录了文件当前的读写位置,当调用 read()或 write()函数对文件进行读写操作时,就会从当前读写位置偏移量开始进行数据读写。
读写偏移量用于指示 read()或 write()函数操作时文件的起始位置,会以相对于文件头部的位置偏移量来表示,文件第一个字节数据的位置偏移量为 0。
当打开文件时,会将读写偏移量设置为指向文件开始位置处,以后每次调用 read()、write()将自动对其进行调整,以指向已读或已写数据后的下一字节,因此,连续的调用 read()和 write()函数将使得读写按顺序递增,对文件进行操作。我们先来看看 lseek 函数的原型,如下所示(可通过"man 2 lseek"查看):
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
函数参数和返回值含义如下:
fd:文件描述符。
offset:偏移量,以字节为单位。
whence:用于定义参数 offset 偏移量对应的参考值,该参数为下列其中一种(宏定义):
1).SEEK_SET:读写偏移量将指向 offset 字节位置处(从文件头部开始算);
2).SEEK_CUR:读写偏移量将指向当前位置偏移量 + offset 字节位置处,offset 可以为正、也可以为负,如果是正数表示往后偏移,如果是负数则表示往前偏移;
3).SEEK_END:读写偏移量将指向文件末尾 + offset 字节位置处,同样 offset 可以为正、也可以为负,如果是正数表示往后偏移、如果是负数则表示往前偏移。
返回值:成功将返回从文件头部开始算起的位置偏移量(字节为单位),也就是当前的读写位置;发生错误将返回-1。
参考例程
将hello world 写入文件并读出;
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(void)
{
char buffer[1024];
int fd1,fd2; //fd1写文件,fd2读文件
int ret;
/************写文件***************/
//通过open参数,对用户赋予txt文件的使用权限(也可以单独使用终端修改权限)
fd1 = open("./test.txt", O_WRONLY | O_CREAT | O_EXCL , 644);
if (-1 == fd1)
{
printf("open1 error\n");
return 1;
}
printf("open1 is ok\n");
ret = write(fd1, "hello world", 11 );
if (-1 == ret )
{
printf("write error\n");
close(fd1);
return 1;
}
printf("write %d bytes Ok! \n",ret);
close(fd1);
/************读文件***************/
fd2 = open("./test.txt", O_RDONLY );
if (-1 == fd2)
{
printf("open2 error\n");
return 1;
}
printf("open2 is ok\n");
ret = read(fd2, buffer, 11 );
if (-1 == ret )
{
printf("read error\n");
close(fd2);
return 1;
}
printf("read %d bytes %s\n",ret,buffer);
close(fd2);
return 0;
}
本文参考正点原子的嵌入式LinuxC应用编程手册