Linux文件I/O分为系统IO和标准IO,常用于系统编程
系统I/O通过文件描述符 fd
来操作文件
标准I/O通过文件流 FILE*
来操作文件
Linux下可以使用man
命令来查看使用手册
man指令
__SYSCALL_COMMON(0, sys_read, sys_read)
__SYSCALL_COMMON(1, sys_write, sys_write)
__SYSCALL_COMMON(2, sys_open, sys_open)
__SYSCALL_COMMON(3, sys_close, sys_close)
__SYSCALL_COMMON(5, sys_newfstat, sys_newfstat)
...
...
...
在调用 open 函数时,在成功打开文件后,会返回一个文件句柄fd,在后续的 write 、read 等操作都可以通过文件句柄fd完成对文件的操作,那么是如何通过一个文件句柄fd就可以完成对文件各式各样的操作
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode)
{
long ret;
if (force_o_largefile())
flags |= O_LARGEFILE;
ret = do_sys_open(AT_FDCWD, filename, flags, mode);
/* avoid REGPARM breakage on x86: */
asmlinkage_protect(3, ret, filename, flags, mode);
return ret;
}
long do_sys_open(int dfd, const char __user *filename, int flags, int mode)
{
char *tmp = getname(filename);
int fd = PTR_ERR(tmp);
if (!IS_ERR(tmp)) {
fd = get_unused_fd_flags(flags); /* 获取未使用的函数句柄 */
if (fd >= 0) {
struct file *f = do_filp_open(dfd, tmp, flags, mode, 0); /* 打开文件,得到file结构体 */
if (IS_ERR(f)) {
put_unused_fd(fd);
fd = PTR_ERR(f);
} else {
fsnotify_open(f);
fd_install(fd, f); /* 在当前进程里面记录下来 */
}
}
putname(tmp);
}
return fd;
}
struct file {
/*
* fu_list becomes invalid after file_free is called and queued via
* fu_rcuhead for RCU freeing
*/
union {
struct list_head fu_list;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
#define f_dentry f_path.dentry
#define f_vfsmnt f_path.mnt
const struct file_operations *f_op;
atomic_t f_count;
unsigned int f_flags;
mode_t f_mode;
loff_t f_pos;
struct fown_struct f_owner;
unsigned int f_uid, f_gid;
struct file_ra_state f_ra;
unsigned long f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
spinlock_t f_ep_lock;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
};
通过上述内容,我们得到了文件句柄fd与操作文件结构体file的绑定流程,下述将看看在一个进程里面,是如何管理文件句柄fd的
#include
#include
#include
int main(int argc, char *argv[])
{
int fd;
if(argc != 2)
{
printf("file name error\r\n");
return -1;
}
fd = open(argv[1], O_RDONLY);
if(fd < 0)
{
perror("open:");
return -1;
}
printf("%s file fd is %d\r\n", argv[1], fd);
while(1)
{
sleep(100);
}
return 0;
}
long do_sys_open(int dfd, const char __user *filename, int flags, int mode)
{
char *tmp = getname(filename);
int fd = PTR_ERR(tmp);
if (!IS_ERR(tmp)) {
fd = get_unused_fd_flags(flags); /* 获取未使用的函数句柄 */
if (fd >= 0) {
struct file *f = do_filp_open(dfd, tmp, flags, mode, 0); /* 打开文件,得到file结构体 */
if (IS_ERR(f)) {
put_unused_fd(fd);
fd = PTR_ERR(f);
} else {
fsnotify_open(f);
fd_install(fd, f); /* 在当前进程里面记录下来 */
}
}
putname(tmp);
}
return fd;
}
void fd_install(unsigned int fd, struct file *file)//将文件对象 file 安装到进程的文件描述符表中
{
__fd_install(current->files, fd, file);
/*current->files 是一个指向当前进程的文件描述符表的指针。
在操作系统中,每个进程都有一个与之关联的文件描述符表,用于跟踪进程打开的文件和文件描述符的状态。
current 表示当前正在执行的进程,而 current->files 则是获取该进程的文件描述符表的方式之一。
它是进程控制块(Process Control Block,PCB)中的一个成员变量。
通过 current->files,可以访问当前进程的文件描述符表,并对其中的文件对象进行操作,如打开、关闭、读取、写入等。
文件描述符表是一个数组或链表的形式,在操作系统内核中维护着每个文件描述符对应的文件对象信息。*/
}
使用man 2 open查看使用手册
函数原型:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
函数参数:
mode:
返回值:
int
fd
-1
,并设置errno
使用man 2 read查看使用手册
函数原型:
ssize_t read(int fd, void buf[], size_t count);
函数参数:
参数 | 意义 |
---|---|
fd |
即将读取文件的文件描述符 |
buf |
存储读入数据的缓冲区 |
count |
将要读入的数据的个数 |
返回值:
ssize_t
,32位机上等同于int
使用man 2 write查看使用手册
函数原型:
ssize_t write(int fd, const void buf[.count], size_t count)
函数参数:
参数 | 意义 |
---|---|
fd | 即将读取文件的文件描述符 |
buf | 要写入的数据缓冲区 |
count | 写入数据的个数,大小不应该大于buf大小 |
返回值:
ssize_t
使用 man 2 close查看使用手册
函数原型:
int close(int fd);
函数参数:
返回值:
使用 man 2 lseek查看使用手册
函数原型:
off_t lseek(int fd, off_t offset, int whence);
函数参数:
参数 | 意义 |
---|---|
fd | 文件描述符 |
offset | 偏移量,可正可负 |
whence | 指定一个基准点,基准点+偏移量等于当前位置 |
whence:
返回值:
off_t
, 32位机等同于int使用 man 2 dup查看使用手册
函数原型:
int dup(int oldfd)
函数参数:
返回值:
函数说明:
用来打开一个新的文件描述符,指向和oldfd同一个文件,共享文件偏移量和文件状态。
当我们调用dup(3)的时候,会打开新的最小描述符,也就是4,这个4指向了3所指向的文件,后续操作这两个中任意一个fd都有一样的效果。
函数原型:
int dup2(int oldfd, int newfd)
函数参数:
返回值:
函数说明:
dup2是直接让传入的参数newfd与参数oldfd指向同一文件表项,如果newfd已经被open过,那么就会先将newfd关闭,然后让newfd指向oldfd所指向的文件表项,如果newfd本身就等于oldfd,那么就直接返回newfd
假设我们使用 dup2(3, 4) 则呈现以下效果: