内核版本:linux-2.6.11


文件描述符(file descriptor)在Linux编程里随处可见,设备读写、网络通信、进程通信,fd可谓是关键中的关键。

深入理解可以增加我们使用它的信心。

该篇笔记主要解释了文件描述符底层的多态实现和文件描述符的生命周期。希望对自己和大家有所帮助。

先看三段简化后的内核代码

sys_open

fd = get_unused_fd();
if (fd >= 0) {
struct file *f = filp_open(tmp, flags, mode);
fd_install(fd, f);
}

sys_socket

fd = get_unused_fd();
if (fd >= 0) {
struct file *file = get_empty_filp();
fd_install(fd, file)
}

do_pipe

struct file *f1 = get_empty_filp();
struct file *f2 = get_emtpy_filp();
i = get_unused_fd();
j = get_unused_fd();
fd_install(i, f1);
fd_install(j,f2);
fd[0] = i;
fd[1] = j;

这三段代码分别是三个POSIX标准系统调用open,socket,pipe的内核态例程,

简化后的代码可以清楚看到,文件描述符的获取和安装在不同模块中都有着几乎相同的套路。

  1. get_unused_fd顾名思义,它从当前进程描述符中的打开文件数组(current->files)里取得一个空闲的项,然后返回其数组下标。

  2. filp_open最终将调用get_empty_filp,因此三段代码都使用get_empty_filp分配了一个新的文件对象(struct file)。

  3. fd_install将上一个步骤创建的文件对象指针存到该进程的打开文件数组中。

至此,文件描述符安装完毕,返回的fd即为该文件对象在当前进程的task_struct中的files数组中的下标,也就是所谓的文件描述符,但究其本质,我们对文件描述符的所有使用其实就是在操作file对象。

接下来就是程序员最拿手的read,write,以及各种IO控制了。

file作为一个通用的结构体,在不同模块表示的对象是不同的,在文件系统中,它表示为一个文件,在进程通信中,它表示为一个管道,在网络通信中,它又表示为一个套接字。

那么问题来了,作为一个通用的结构体,在表示为不同的通信对象的时候,操作函数肯定是不同的,举个例子来说,write一个文件和write一个socket肯定是不一样的,那么同一个函数入口怎么做到多态的实现呢?

我们同样用write来解释,write()->sys_write()->vfs_write()->file->f_op->write(),重点在write调用流程的最后一步file->f_op->write(),从这里开始,针对不同的file对象将进入不同的write实现。这是因为不同file里注册的各类操作函数是不同的。

struct file中的f_op指针,指向一系列函数指针的集合,这个结构名叫file_operation,这一系列函数指针都指向着当前这个file对象(设备也好,管道也好,套接字也好)对应的各种操作函数的具体实现。

在2.6.11的内核里看起来如下:

struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
...
}

以ext3文件系统为例:

struct file_operations ext3_file_operations = {
.llseek = generic_file_llseek,
.read = do_sync_read,
.write = do_sync_write,
.ioctl = ext3_ioctl,
.mmap = generic_file_mmap,
.open = generic_file_open,
...
};

这里使用了C99的语法,始化了一个file_operations然后用自己的实现去填充一个个的函数指针,在open一个ext3文件系统中的文件时,会将这个file_operations注册到新建的这个file对象里。

同样,在pipe.c、socket.c里也可以看到类似的注册file_operations的行为。

所以,一个file是文件、是设备、是管道还是套接字,就是根据其中存放的file_operations来区分,

在创建file的时候就会针对这个file的类型注册不同的操作函数,这就解释了同一个通用函数的多态实现。

文件描述符的生命周期:

  1. 创建file对象
  2. 根据IO对象的类型注册各个操作函数(注册file_operations)
  3. 将file对象的指针注册到进程描述符的files数组里的fd下标处
  4. read、write等等IO操作
  5. 调用close(fd)释放file对象

Linux内核笔记--深入理解文件描述符的更多相关文章

  1. Linux内核分析:打开文件描述符实现

    在Linux中每一个进程的数据是存储在一个task_struct结构(定义在sched.h中)中的. struct task_struct { volatile long state; /* -1 u ...

  2. Linux 利用进程打开的文件描述符(/proc)恢复被误删文件

    Linux 利用进程打开的文件描述符(/proc)恢复被误删文件 在 windows 上删除文件时,如果文件还在使用中,会提示一个错误:但是在 linux 上删除文件时,无论文件是否在使用中,甚至是还 ...

  3. 修改Linux系统下的最大文件描述符限制

    通常我们通过终端连接到linux系统后执行ulimit -n 命令可以看到本次登录的session其文件描述符的限制,如下: $ulimit -n1024 当然可以通过ulimit -SHn 1024 ...

  4. 并发时-修改Linux系统下的最大文件描述符限制

    通常我们通过终端连接到linux系统后执行ulimit -n 命令可以看到本次登录的session其文件描述符的限制,如下: $ulimit -n1024 当然可以通过ulimit -SHn 1024 ...

  5. Linux 进程级开启最大文件描述符 调优

    开启最大文件数 系统可以开启的最大文件描述符(可同时开启最多的文件数),最大开启65535,可根据需求进行调优. 查看系统当前可开启最大文件描述符数 ulimit -n [root@localhost ...

  6. Linux 套接字与文件描述符

    端口和套接字,用于确定指定主机上的哪个本地进程使用了哪个协议和哪台远程主机上的哪个进程进行了通信.端口和套接字的使用可以基于以下几点: ①为每个应用过程分配一个过程标识符(Process ID),每次 ...

  7. 【Shell脚本学习指南笔记】重定向文件描述符 2>&1

    如: make > results 2>&1 重定向 > results让文件描述符1(标准输出)作为文件results,接下来的重定向2>&1有两个部分.2& ...

  8. Linux中的文件描述符与打开文件之间的关系

    Linux中的文件描述符与打开文件之间的关系 导读 内核(kernel)利用文件描述符(file descriptor)来访问文件.文件描述符是非负整数.打开现存文件或新建文件时,内核会返回一个文件描 ...

  9. Linux的进程、线程、文件描述符是什么

    说到进程,恐怕面试中最常见的问题就是线程和进程的关系了,那么先说一下答案:在 Linux 系统中,进程和线程几乎没有区别. Linux 中的进程就是一个数据结构,看明白就可以理解文件描述符.重定向.管 ...

随机推荐

  1. MySQL主从复制

    Mysql主从复制介绍 MySQL支持单向.双向.链式级联.实时.异步复制.在复制过程中,一台服务器充当服务器(Master),而一个或多个其它的服务器充当从服务器(Slave). 复制可以是单向:M ...

  2. Idea+TestNg配置test-output输出

    说明:testNG的工程我是使用eclipse创建的,直接导入到idea中,运行test时不会生产test-output,只能在idea的控制台中查看运行结果,然后到处报告,经过不懈的百度终于找到怎么 ...

  3. 耿丹CS16-2班课堂测试作业汇总

    Deadline: 2016-11-01 11:59 作业内容 课堂测试作业总结 00.题目得5分,多半扣在格式上,有些同学代码写得很过分,已经很仁慈对待,同学们珍惜之: 01.界面设计得分不好,换行 ...

  4. Qtp常见问题

    (1)Qtp不能识别对象 插件加载错误 IE内未加载BHOManager加载项

  5. C#接口的作用(经典)

    C#接口是一个让很多初学C#者容易迷糊的东西,用起来好像很简单,定义接口,里面包含方法,但没有方法具体实现的代码,然后在继承该接口的类里面要实现接口的所有方法的代码,但没有真正认识到接口的作用的时候就 ...

  6. lombok 简化java代码注解

    lombok 简化java代码注解 安装lombok插件 以intellij ide为例 File-->Setting-->Plugins-->搜索"lombok plug ...

  7. iOS Swift的一些小知识(不断补充)

    1. 在swift文件里是不能写c语言函数的,不兼容c,直接报错.想调用c语言函数,就要利用系统提供的桥接功能,就如同swfit中调用oc一样! 2.swift 2.0后提供了@convention( ...

  8. 海量日志分析方案--logstash+kibnana+kafka

    下图为唯品会在qcon上面公开的日志处理平台架构图.听后觉得有些意思,好像也可以很容易的copy一个,就动手尝试了一下. 目前只对flume===>kafka===>elacsticSea ...

  9. Session在类库中的使用

    转自:http://www.cnblogs.com/JiangXiaoTian/articles/3490904.html 网站开发中,为了保存用户的信息,有时候需要使用session.如果我们在as ...

  10. Linux posix线程库总结

    由于历史原因,2.5.x以前的linux对pthreads没有提供内核级的支持,所以在linux上的pthreads实现只能采用n:1的方式,也称为库实现. 线程的实现,经历了如下发展阶段: Linu ...