- 1. 文件IO
- 2. 文件描述符
- 什么是文件描述符?文件描述符何时产生?
- 3. 文件IO常用函数
- open()
- close()
- 对比fopen()与open()打开文件权限
- read()
- write()
- memset / bzero 设置一片内存区域的值
- lseek()
- stat() 获取文件信息
- opendir() / readdir() / closedir() 对目录操作
文件IO不涉及缓冲区,每执行一次,都会调用Linux内核的系统调用,然后操作硬件设备。
与标准IO不同,文件IO对文件操作时,使用的是文件描述符。
当程序运行时,操作系统会自动为当前程序创建三个文件描述符
- 0 标准输入文件描述符
- 1 标准输出文件描述符
- 2 标准错误输出文件描述符
在进程中,当使用open函数打开一个文件时,就会产生一个文件描述符。而这个文件描述符其实是一个数组的下标,所以文件描述符一定是一个非负整数,且会按照顺序分配。它被内核用以标识一个特定进程正在访问的文件。
文件描述符是从小到大依次递增分配,但是如果有文件描述符关闭,则新创建的文件描述符的值会先等于最小的没有使用的值,然后在依次递增创建,所以最后创建的文件描述符不一定是最大的
在进程中找到文件描述符
struct task_struct{ //进程的结构体
volatile long state; //进程运行状态
unsigned int cpu; //cpu的序号
int prio; //进程运行的优先级
pid_t pid; //进程号
struct task_struct *real_parent;
struct list_head children;
struct list_head sibling;
struct files_struct *files;
}
首先,看进程相关的结构体的部分内容,其中有一个结构体指针为struct files_struct *,用来保存该进程打开文件的信息,下面截取结构体struct files_struct部分内容
struct files_struct {
struct fdtable *fdt;
struct file * fd_array[32];
};
可以看到该结构体中,又有一个struct file结构体指针数组,意味着该结构体中可以存放32个指向struct file结构体的指针,而struct file中保存着打开文件时的所有信息
我们使用open函数所获得的文件描述符就是这个数组的下标,使用open()的同时,就会创建struct file结构体,前32个文件描述符会静态分配,保证效率,再多就需要动态分配。而这里设定为32个也是考虑时间空间等因素后的经验值。
open()→fd→fd_array[fd]→struct file
struct file结构体中还有一个重要属性atomic_long_t f_count,同一struct file可以被引用多次,这个用来记录该结构体被struct file *同时指向的个数。当struct file *指向该结构体的指针减少一个,f_count就会减一,当f_count为0时,该结构体才会被释放。
struct file {//该结构体的部分信息
struct path f_path; //路径
unsigned int f_flags; //打开文件的方式,读,写,阻塞,非阻塞
atomic_long_t f_count //这个是一个相对来说比较重要的参数,表示打开文件的引用计数,如果有多个文件指针指向它,就会增加f_count的值
const struct file_operations *f_op; //操作方法结构体
fmode_t f_mode; //权限
loff_t f_pos; //文件的光标的位置
void *private_data; //私有数据
struct inode *f_inode;//指向inode的结构指针
}
在不同的进程中,会有不同或者相同的文件描述符指向同一个文件,在同一进程中,也会有不同的文件描述符指向同一个文件,而同一个文件只有一个struct inode结构体,所以这些文件描述符最终都是指向同一个struct inode结构体。Linux中每个文件都有唯一的indoe号
struct inode{
umode_t i_mode; //文件的权限
kuid_t i_uid; //用户
kgid_t i_gid; //组
unsigned long i_ino;//inode号
dev_t i_rdev; //设备号
union {
struct block_device *i_bdev;
struct cdev *i_cdev;
};
}
inode的功能
- 在文件系统中,inode结构体用来描述这个文件的信息。
- 如果这个文件是设备文件,它会唯一关联一个设备驱动。
- 在应用层调用open函数,会使用inode会作为参数,传递给驱动的open函数。
#includeclose()#include #include int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode); 功能: 打开或者创建一个文件 参数: pathname:文件名,不添加路径默认为当前路径 flags:标志位 三者必须有一个: O_RDonLY 只读 O_WRonLY 只写 O_RDWR 读写 其他标志位:如果有多个属性,需要用 位逻辑或 连接 O_APPEND 文件存在以追加方式打开文件,此时写操作指针永远在末尾,读操作不影响 O_CREAT 文件不存在则创建 O_EXCL 一般与O_CREAT一起使用 O_EXCL|O_CREAT, 如果文件存在则报错,文件不存在则创建 O_TRUNC 如果文件存在则清空 mode:如果第二个参数指定了O_CREAT,就需要这个参数,否则不需要 这个参数主要用于设置当前文件的访问权限,一般使用一个三位 的八进制数来设置,分别设置用户主、用户组以及其他用户对当 前文件的操作权限,分别设置读、写、执行权限, 一般设置为0664 → 0 代表8进制,6110 4100 返回值: 成功:文件描述符 失败:-1
#include对比fopen()与open()打开文件权限int close(int fd); 功能: 关闭文件描述符 参数: fd:文件描述符 返回值: 成功:0 失败:-1
| fopen() | open() | 含义 |
|---|---|---|
| "r" | O_RDONLY | 以只读方式打开文件,如果文件不存在则报错 |
| "r+" | O_RDWR | 以读写方式打开文件,如果文件不存在则报错 |
| "w" | O_WRonLY | O_CREAT | O_TRUNC, 0664 | 以只写方式打开文件,如果文件不存在则创建,如果文件存在则清空 |
| "w+" | O_RDWR | O_CREAT | O_TRUNC, 0664 | 以读写方式打开文件,如果文件不存在则创建,如果文件存在则清空 |
| "a" | O_WRonLY | O_CREAT | O_APPEND, 0664 | 以只写追加方式打开文件,如果文件不存在则创建,如果文件存在则追加 |
| "a+" | O_RDWR | O_CREAT | O_APPEND, 0664 | 以读写追加方式打开文件,如果文件不存在则创建,如果文件存在则追加 |
#includessize_t read(int fd, void *buf, size_t count); 功能: 从文件中读取内容 参数: fd:文件描述符 buf:保存读取的数据的首地址,可以是数组或者结构体等 conut:要读取的字节数 返回值: 成功:实际读取的字节数 失败:-1 如果文件读完,返回0
注意:
read读取文件内容时,如果文件内容足够,第三个参数是n,就会读取n个字节,不会预留 的位置,也不会说像fgets一样遇到行结束符就结束。一定要注意read的返回值表示实际读取的个数,往往文件最后一次读取的数据都小于第三个参数设置的值
#includememset / bzero 设置一片内存区域的值ssize_t write(int fd, const void *buf, size_t count); 功能: 向文件写入数据 参数: fd:文件描述符 buf:要写入的数据, 可以是数组或者结构体等 conut:写入的字节数 返回值: 成功:写入的字节数 失败:-1
memset
#includevoid *memset(void *s, int c, size_t n); 功能: 设置指定内存区域的为指定值 参数: s 指定区域的首地址 c 要设置的指定值 n 指定区域的大小,单位字节Bytes 返回值: 返回指定区域的首地址。
bzero
#includelseek()void bzero(void *s, size_t n); 功能: 将指定内存区域的值清空,设置为0 参数: s 指定区域的首地址 n 指定区域的大小,单位字节Bytes 返回值: 无 None
#includestat() 获取文件信息#include off_t lseek(int fd, off_t offset, int whence); 功能: 设置或者获取文件定位的偏移值 参数: fd:文件描述符 offset:偏移值,可正可负 whence:相对位置 SEEK_SET 起始位置 SEEK_CUR 当前位置 SEEK_END 末尾位置 返回值: 成功:当前修改之后的文件的偏移值 失败:-1
#include#include #include int stat(const char *pathname, struct stat *statbuf); 功能: 获取指定文件的信息 参数: pathname:文件名 statbuf:保存获取的信息 struct stat { dev_t st_dev; 文件所在设备的ID ino_t st_ino; inode节点号 mode_t st_mode; 模式(文件类型、访问权限) nlink_t st_nlink; 链向此文件的连接数(硬连接) uid_t st_uid; 用户id gid_t st_gid; 组id dev_t st_rdev; 设备号,针对设备文件 off_t st_size; 文件大小,字节为单位 blksize_t st_blksize; 系统块的大小 blkcnt_t st_blocks; 文件所占块数 time_t st_atime; 最近存取时间 time_t st_mtime; 最近修改时间 time_t st_ctime; 文件创建的时间 }; 返回值: 成功:0 失败:-1
使用时,自己定义一个struct stat结构体,然后取地址传给stat()函数,执行成功后,文件信息就会写入自己定义的struct stat结构体体。
opendir() / readdir() / closedir() 对目录操作标准IO和文件IO相关函数可以操作普通文件、设备文件、管道文件、软链接文件、套接字文件,但是无法操作目录文件
opendir
#include#include DIR *opendir(const char *name); 功能: 打开指定目录 参数: name:目录名 返回值: 成功:目录流指针 失败:NULL
先用DIR *定义一个目录流指针,来接opendir()函数的返回值
readdir()
#includestruct dirent *readdir(DIR *dirp); 功能: 读取目录信息 参数: dirp:目录流指针,opendir的返回值 返回值: 成功:保存目录中文件信息的结构体 struct dirent { ino_t d_ino; inode节点号 off_t d_off; unsigned short d_reclen; unsigned char d_type; 文件类型 char d_name[256]; 文件名 }; 失败:NULL 如果目录中信息读取完毕,也返回NULL
打开目录流指针成功后,定义struct dirent *目录文件信息结构体指针,来接readdir()返回值,获取目录中文件信息的结构体。
注意,每次只能获取一个文件信息,获取成功后,再次执行会获取下一个文件信息,当目录信息读取完毕会返回NULL
我的环境为Ubuntu18.04,文件类型测试结果是
| d_type | 类型 |
|---|---|
| 1 | 管道文件 p |
| 2 | 字符设备文件 c |
| 4 | 目录 d |
| 6 | 块设备 b |
| 8 | 普通文件 - |
| 10 | 符号链接文件 l |
| 12 | 套接字文件 s |
closedir()
#include#include int closedir(DIR *dirp); 功能: 关闭目录流指针 参数: dirp:目录流指针,opendir的返回值 返回值: 成功:0 失败:-1



