作者:高玉涵
时间:2022.2.8 10:20 (福虎年初八)
博客:blog.csdn.net/cg_i
即使看到“底层”二字,也会有读者臆测其难以理解。实际上,“底层”这个表达可以理解为“与标准无关的操作系统独立提供的”。稍后讲解的函数是由 Linux 提供的,而非 ANSI 标准定义的函数。如果想使用 Linux 提供的文件 I/O 函数,首先应该理解好文件描述符的概念。
此处的文件描述符是系统分配给文件的整数。实际上,学习 C 语言过程中用过的标准输入输出及标准错误在 LINUX 中也被分配表 1-1 中的文件描述符。
文件描述符 | 对象 |
---|---|
0 | 标准输入:Standard Input |
1 | 标准输出:Standard Output |
2 | 标准错误:Standard Error |
文件一般经过创建过程才会被分配文件描述符。而表 1-1 中的 3 种输入输出对象即使未经过特殊的创建过程,程序开始运行后也会被自动分配文件描述符。
首先介绍打开文件以读写数据的函数。调用此函数时必须传递的两个参数:第一个参数是打开的目标文件名及路径信息,第二个参数是文件打开模式(文件特性信息)。
#include
#include
#include
int open(const char *pathname, int oflag, ... /* mode_t mode */);
成功时返回文件描述符,失败时返回 -1 。
├── pathname 文件名的字符串地址。
├── oflag 文件打开模式信息。
├── mode O_CREAT 时需指定新文件访问权限位1
表 1-2 是此函数第二个参数 oflag 可能的常量值及含义。如需传递多个参数,则应通过位或运算(OR)符组合传递。
打开模式 | 含义 |
---|---|
O_CREAT | 必要时创建文件 |
O_TRUNC | 删除全部现有数据 |
O_APPEND | 维持现有数据,保存到其后面 |
O_RDONLY | 只读打开 |
O_WRONLY | 只写打开 |
O_RDWR | 读写打开 |
各位学习 C 语言时学过,使用文件后必须关闭。下面介绍关闭文件时调用的函数。
#include
int close(int fd);
成功时返回 0,失败时返回 -1 。
├── fd 需要关闭的文件或套接字的文件描述符。
若调用此函数的同时传递文件描述符参数,则关闭(终止)相应文件。
接下来介绍的 write 函数用于向文件输出数据。
#include
ssize_t write(int fd, const void *buf, size_t nbytes);
成功时返回写入的字节数,失败时返回 -1 。
├── fd 显示数据传输对象的文件描述符。
├── buf 保存要传输数据的缓冲地址值。
├── nbytes 要传输数据的字节数。
此函数定义中,size_t 是通过 typedef 声明的 unsigned int 类型。对 ssize_t 来说,size_t 前面多加的 s 代表 signed,即 ssize_t 是通过 typedef 声明的 signed int 类型。
以 _t 为后缀的数据类型
我们已经接触到 ssize_t、size_t 等陌生的数据类型。这些都是元数据类型(primitive),在 sys/types.h 头文件中一般由 typedef 声明定义,算是给大家熟悉的基本数据类型起了别名。既然已经有了基本数据类型,为何还要声明并使用这些新的呢?
人们目前普遍认为 int 是 32 位的,因为主流操作系统和计算机仍采用 32 位。而在过去 16 位操作系统时代,int 类型是 16 位的。根据系统的不同、时代的变化,数据类型的表现形式也随之改变,需要修改程序中使用的数据类型。如果之前已在需要声明 4 字节数据类型之处使用了 size_t 或 ssize_t,则将大大减少代码变动,因为只需要修改并编译 size_t 和 ssize_t 的 typedef 声明即>可。在项目中,为了给基本数据类型赋予别名,一般会添加大量 typedef 声明。而为了与程序员定义的新数据类型加以区分,操作系统定义的数据类型会添加后缀 _t 。
下面通过示例帮助大家更好地理解前面讨论过的函数。此程序将创建新文件并保存数据。
#include
#include
#include
#include
void error_handling(char *message);
int main(void)
{
int fd;
char buf[] = "Let's go!\n";
fd = open("data.txt", O_CREAT|O_WRONLY|O_TRUNC, 0777);
if( fd == -1 )
error_handling("Open() error!");
printf("file descriptor: %d \n", fd);
if( write(fd, buf, sizeof(buf)) == -1 )
error_handling("write() error!");
close(fd);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
运行结果:low_open.c
gcc low_open.c -o lopen
./lopen
file descriptor: 3
cat data.txt
Let's go!
与之前的 write 函数相对应, read 函数用来输入数据。
#include
ssize_t read(int fd, void *buf, size_t nbytes);
成功时返回接收的字节数(但遇到文件结尾则返回 0),失败时返回 -1 。
├── fd 显示数据传输对象的文件描述符。
├── buf 保存要传输数据的缓冲地址值。
├── nbytes 要接收数据的字节数。
下列示例将通过 read 函数读取 data.txt 中保存的数据。
#include
#include
#include
#include
#define BUF_SIZE 100
void error_handling(char *message);
int main(void)
{
int fd;
char buf[BUF_SIZE];
fd = open("data.txt", O_RDONLY);
if( fd == -1 )
error_handling("Open() error!");
printf("file descriptor: %d \n", fd);
if( read(fd, buf, sizeof(buf)) == -1 )
error_handling("read() error!");
printf("file data: %s", buf);
close(fd);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
运行结果:low_read.c
gcc low_read.c -o lread
./read
file descriptor: 3
file data: Let's go!
基于文件描述符的 I/O 操作相关介绍到此结束。
Linux下用汇编语言处理文件(https://blog.csdn.net/cg_i/article/details/120725648?spm=1001.2014.3001.5501) ↩︎