《【正点原子】I.MX6U嵌入式Linux C应用编程指南》学习笔记
内核(kernel)利用文件描述符(file descriptor)来访问文件。文件描述符是非负整数。打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。
——百度百科
当调用 oepn() 函数打开或创建一个文件时,内核会向进程返回一个文件描述符,用于代指被打开的文件,所有执行 I/O 操作的系统调用都是通过文件描述符来索引到对应的文件。
每一个被打开的文件在同一进程中都对应一个唯一的文件描述符,当文件被关闭后,它对应的文件描述符就会被释放。
每个线程的前三个文件描述符(0 ~ 2)是固定的,分别表示标准输入(0)、标准输出(1)和标准错误(2),所以我们创建或打开文件时分配的文件描述符最小为 3。
open() 的函数可以打开或创建一个文件,原型如下:
#include
#include
#include
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
参数介绍
pathname: 要操作的文件,可以填文件路径(绝对路径或相对路径都行),如果 pathname 是一个符号链接,会对其进行解引用。
flags: 操作文件时的一些标志,open() 函数有很多种标志,下面介绍一些常用的:
标志 | 作用 | 说明 |
---|---|---|
O_RDONLY | 以只读方式打开文件 | 指定文件的访问权限,三种权限必须选一种 |
O_WRONLY | 以只写方式打开文件 | 指定文件的访问权限,三种权限必须选一种 |
O_RDWR | 以可读可写方式打开文件 | 指定文件的访问权限,三种权限必须选一种 |
O_CREAT | 如果文件不存在,这创建文件 | 使用此标准时,需要传入第 3 个参数 mode,用于指定新文件的权限 |
O_EXCL | 如果文件已存在,则返回错误,一般与 O_CREAT 一起使用 | - |
O_APPEND | 以追加的方式打开文件 | 每次使用 write() 时,文件指针自动先移动到文件尾 |
除了上面这些,还有很多其他标志,如 O_ASYNC、OASYNC、O_DSYNC 等。
如果要同时使用多个标志,中间使用按位或运算符(‘|’) 连接。
该参数支持的选项包括:
宏定义 | 说明 |
---|---|
S_IRUSR | 允许文件所有者读文件 |
S_IWUSR | 允许文件所属者写文件 |
S_IXUSR | 允许文件所属者执行文件 |
S_IRWXU | 允许文件所属者读、写、执行文件 |
S_IRGRP | 允许同组用户读文件 |
S_IWGRP | 允许同组用户写文件 |
S_IXGRP | 允许同组用户执行文件 |
S_IRWXG | 允许同组用户读、写、执行文件 |
S_IROTH | 允许其他用户读文件 |
S_IWOTH | 允许其他用户写文件 |
S_IXOTH | 允许其他用户执行文件 |
S_IRWXO | 允许其他用户读、写、执行文件 |
S_ISUID | set-user-ID 设置 UID |
S_ISGID | set-group-ID 设置 GID |
S_ISVTX | sticky |
如果要同时使用多个标志,中间使用按位或运算符(‘|’) 连接。
返回值
成功时返回文件描述符,文件描述符是一个非负整数,失败时返回 -1。
write() 函数可以向已经打开的文件写入数据,函数原型如下:
#include
ssize_t write(int fd, const void *buf, size_t count);
参数介绍
返回值
成功时返回写入的字节数,失败时返回 -1。如果返回的字节数小于参数 count,可能是因为一些意外错误,比如磁盘空间已满。数据写入后,文件指针也会跟着偏移(比如写入前文件指针偏移量为 0,写入 100 字节后,文件指针偏移量将会变成 100)。
read() 函数的作用是从已经打开的文件中读取数据,其原型如下:
#include
ssize_t read(int fd, void *buf, size_t count);
参数介绍
返回值
返回成功读取到的字节数,实际读取字节数可能会小于参数 count(比如文件指针指向的位置后的字节数小于 count),也有可能是 0(文件指针已经指向文件末尾)。和 write() 函数类似,每次使用 read() 读取数据,文件指针会移动到上一次 read() 结束时的位置,比如一次性读完文件的字节数,再次使用 read(),将会返回 0,因为文件指针已经指向文件末尾。
close() 函数可以关闭一个已经打开的文件,原型如下:
#include
int close(int fd);
参数介绍
返回值
返回 0 表示关闭成功,返回 -1 表示关闭失败。
在很多代码中,程序一直在一个 while(1) 死循环中执行文件操作,而 close() 函数在循环之外,也就是说程序并不会执行 close(),但这种情况文件的数据并不会意外丢失。这是因为在 Linux 系统中,当一个进程结束时,内核会自动关闭它所有打开的文件。但即便如此,在不需要使用文件时调用 close() 是一个良好的编程习惯,因为它能增强程序的可读性和可靠性。
每个打开的文件,系统都会记录下它们读写位置偏移量,可以把它成为读写偏移量,它记录了文件当前读写位置。每次调用 read() 或者 write() 函数时,就会从读写偏移量所在位置进行数据读写。文件头的偏移量为 0。
lseek() 函数可以调整读写偏移量,该函数原型如下:
#include
#include
off_t lseek(int fd, off_t offset, int whence);
参数介绍
返回值
成功时返回从文件头部开始算起的位置偏移量(字节),错误时返回 -1。
实现的功能:新建一个文件 src_file.txt,向里面写入 1K Byte 数据,然后打开一个现有文件 dest_file.txt,获取该文件的大小,然后将 src_file.txt 的内容追加拷贝到 dest_file.txt 文件中,最后再获取一次 dest_file.txt 文件的大小。
我的测试代码(仅供参考)
#include
#include
#include
#include
#include
#define SRC_FILE "./src_file.txt"
#define DEST_FILE "./dest_file.txt"
char buff[1024];
int main()
{
int i = 0;
int src_fd, dest_fd;
int cnt = 0, offset = 0;
char tmp[256];
/* 填充 buff */
for(i = 0; i < 1024; i++)
{
buff[i] = 'a' + i % 26;
}
/* 创建 src_file.txt */
src_fd = open(SRC_FILE, O_RDWR|O_CREAT, S_IRWXU);
if(src_fd < 0)
{
printf("%s open failed.\n", SRC_FILE);
return -1;
}
/* 向 src_file.txt 写入数据 */
cnt = write(src_fd, buff, sizeof(buff));
printf("Successfully wrote %d bytes data to %s.\n", cnt, SRC_FILE);
/* 打开 dest_file.txt */
dest_fd = open(DEST_FILE, O_RDWR);
if(dest_fd < 0)
{
printf("%s open failed.\n", DEST_FILE);
return -1;
}
/* 获取 dest_file.txt 文件末尾的文件指针偏移量, 同时也将指针指向文件尾部 */
offset = lseek(dest_fd, 0, SEEK_END);
printf("The size of %s is %d bytes.\n", DEST_FILE, offset);
/* 将 src_file.txt 的文件指针指向文件头 */
lseek(src_fd, 0, SEEK_SET);
/* 将 src_file.txt 的数据写入到 dest_file.txt 中 */
while(1)
{
cnt = read(src_fd, tmp, sizeof(tmp));
if(cnt > 0)
write(dest_fd, tmp, sizeof(tmp));
else
break;
}
/* 获取 dest_file.txt 文件末尾的文件指针偏移量 */
offset = lseek(dest_fd, 0, SEEK_END);
printf("The size of %s is %d bytes.\n", DEST_FILE, offset);
/* 关闭文件 */
close(src_fd);
close(dest_fd);
}
程序运行结果: