精华内容
下载资源
问答
  • 2018-07-08 22:58:16

    1. Linux文件类型

    普通文件

    -,Normal File

    mp4pdfhtml log

    用户可以根据访问权限对普通文件进行查看、更改和删除

    包括 纯文本文件(ASCII);二进制文件(binary);数据格式的文件(data);各种压缩文件.第一个属性为 [-] 

    目录文件

    ddirectory file

     /usr/ /home/

    目录文件包含了各自目录下的文件名和指向这些文件的指针,打开目录事实上就是打开目录文件,只要有访问权限,就可以随意访问这些目录下的文件

    能用#cd命令进入的。第一个属性为[d],例如 [drwxrwxrwx]

    硬链接

    - ,hard links: 

    若一个inode号对应多个文件名,则称这些文件为硬链接。硬链接就是同一个文件使用了多个别名删除时,只会删除链接不会删除文件;

    硬链接的局限性:1.不能引用自身文件系统以外的文件,即不能引用其他分区的文件;2.无法引用目录;

     

    符号链接

    (软链接)

    lsymbolic link

     

    若文件用户数据块中存放的内容是另一文件的路径名的指向,则该文件就是软连接,克服硬链接的局限性类似于快捷方式,使用与硬链接相同。

    字符设备文件 

    cchar

    文件一般隐藏在/dev目录下,在进行设备读取和外设交互时会被使用到

    即串行端口的接口设备,例如键盘、鼠标等等。第一个属性为 [c]

    #/dev/tty的属性是 crw-rw-rw-,注意前面第一个字 c,这表示字符设备文件

    块设备文件

     

    bblock

    存储数据以供系统存取的接口设备,简单而言就是硬盘。

    # /dev/hda1 的属性是 brw-r----- ,注意前面的第一个字符是b,这表示块设备,比如硬盘,光驱等设备

     

    系统中的所有设备要么是块设备文件,要么是字符设备文件,无一例外

    FIFO管道文件

    ppipe

    管道文件主要用于进程间通讯。FIFO解决多个程序同时存取一个文件所造成的错误。比如使用mkfifo命令可以创建一个FIFO文件,启用一个进程AFIFO文件里读数据,启动进程BFIFO里写数据,先进先出,随写随读。

    # pipe

    套接字

     

    ssocket

    以启动一个程序来监听客户端的要求,客户端就可以通过套接字来进行数据通信。用于进程间的网络通信,也可以用于本机之间的非网络通信,第一个属性为 [s],这些文件一般隐藏在/var/run目录下,证明着相关进程的存在

    # softlink...

    参考网址:

    Linux - 硬链接(Hard Links)和符号链接(Symbolic Links)

    Linux一切皆文件

    Linux文件系统详解

    理解linux虚拟文件系统VFS - 概述

    2. 文件的管理机制(虚拟文件系统VFS)

    1) 虚拟文件系统VFS的作用

    虚拟文件系统就是:对于一个system,可以存在多个“实际的文件系统”,例如:ext2ext3fat32ntfs...例如我现在有多个分区,对于每一个分区我们知道可以是不同的“实际文件系统”,例如现在三个磁盘分区分别是:ext2ext3fat32,那么每个“实际的文件系统”的操作和数据结构什么肯定不一样,那么,用户怎么能透明使用它们呢?那么这个时候就需要VFS作为中间一层!用户直接和VFS打交道。例如readwrite,那么映射到VFS中就是sys_readsys_write,那么VFS可以根据你操作的是哪个“实际文件系统”(哪个分区)来进行不同的实际的操作!那么这个技术也是很熟悉的“钩子结构”(此名称不知道是否合理,自己一直这样叫了)技术来处理的。其实就是VFS中提供一个抽象的struct结构体,然后对于每一个具体的文件系统要把自己的字段和函数填充进去,这样就解决了异构问题。

    VFS存在的意义可归结为以下四点

    l 向上,对应用层提供一个标准的文件操作接口;

    l 对下,对文件系统提供一个标准的接口,以便其他操作系统的文件系统可以方便的移植到Linux上;

    l VFS内部则通过一系列高效的管理机制,比如inode cache, dentry cache 以及文件系统的预读等技术,使得底层文件系统不需沉溺到复杂的内核操作,即可获得高性能;

    l 此外VFS把一些复杂的操作尽量抽象到VFS内部,使得底层文件系统实现更简单。

    VFS框架结构图

    或者:

     虚拟文件系统结构图

    2 文件系统分类

    文件系统一般可以分为以下几类

    l 网络文件系统,如 nfscifs、cdoa、afs等网络文件系统

    l 磁盘文件系统,如ext2/ext3/ext4文件系统;

    l 特殊文件系统,如 procsysfsramfstmpfspipe文件系统等。

     

    实现以上这些文件系统并在 Linux 下共存的基础就是Linux VFSVirtual File System又称 Virtual Filesystem Switch),即虚拟文件系统。VFS作为一个通用的文件系统,抽象了文件系统的四个基本概念:文件、目录项(dentry)、索引节点(inode)及挂载点,其在内核中为用户空间层的文件系统提供了相关的接口。VFS实现了open()read()等系统调并使得cp等用户空间程序可跨文件系统。VFS真正实现了上述内容中:在 Linux中除了进程之外一切皆是文件。

     

    3) Linux VFS四个基本对象

    l 超级块对象(superblock object)

    l 索引节点对象(inode object)

    l 目录项对象(dentry object)

    l 文件对象(file object)

    超级块对象代表一个已安装的文件系统;索引节点对象代表一个文件;目录项对象代表一个目录项,如设备文件 event5 在路径 /dev/input/event5 中,其存在四个目录项对象:dev/ input/ 及 event5。文件对象代表由进程打开的文件。为文件路径的快速解析,Linux VFS 设计了目录项缓存(Directory Entry Cache,即 dcache)。

    详细解释网址:Linux 文件系统(一)---虚拟文件系统VFS----超级块、inode、dentry、file

     

    4) 目录树

    VFS是一种软件机制,也许称它为Linux的文件系统管理者更确切点,与它相关的数据结构只存在于物理内存当中。所以在每次系统初始化期间,Linux都首先要在内存当中构造一棵 VFS的目录树(Linux 的源代码里称之为namespace),实际上便是在内存中建立相应的数据结构。VFS目录树在Linux的文件系统模块中是个很重要的概念,VFS中的各目录其主要用途是用来提供实际文件系统的挂载点,当然在 VFS 中也会涉及到文件级的操作,如图:

    是一种可能的目录树在内存中的影像:

     VFS 目录树结构

    5) 文件系统的注册

    这里的文件系统是指可能会被挂载到目录树中的各个实际文件系统,所谓实际文件系统,即是指VFS中的实际操作最终要通过它们来完成而已,并不意味着它们一定要存在于某种特定的存储设备上。比如在笔者的 Linux 机器下就注册有 "rootfs""proc""ext2""sockfs" 等十几种文件系统。

    在一个区被格式化为一个文件系统之后,它就可以被Linux操作系统使用了,只是这个时候Linux操作系统还找不到它,所以我们还需要把这个文件系统「注册」进Linux操作系统的文件体系里,这个操作就叫「挂载」 (mount)
    挂载是利用一个目录当成进入点(类似选一个现成的目录作为代理),将文件系统放置在该目录下,也就是说,进入该目录就可以读取该文件系统的内容,类似整个文件系统只是目录树的一个文件夹(目录)。
    这个进入点的目录我们称为「挂载点」。

    由于整个 Linux 系统最重要的是根目录,因此根目录一定需要挂载到某个分区。 而其他的目录则可依用户自己的需求来给予挂载到不同的分去。

    硬盘经过分区和格式化,每个区都成为了一个文件系统,挂载这个文件系统后就可以让Linux操作系统通过VFS访问硬盘时跟访问一个普通文件夹一样。这里通过一个在目录树中读取文件的实际例子来细讲一下目录文件和普通文件。

    参考网址

    linux的文件系统和虚拟文件系统(VFS)

    理解linux虚拟文件系统VFS - 概述

    解析 Linux 中的 VFS 文件系统机制

    Linux文件系统详解

    更多相关内容
  • 1,引言Linux 中允许众多不同的文件系统共存,如 ext2, ext3, vfat 等。通过使用同一套文件 I/O 系统 调用即可对 Linux 中的任意文件进行操作而无需考虑其所在的具体文件系统格式;更进一步,对文件的 操作可以跨文件...

    7925bb02398edc22ec883fd869196a83.png

    1,引言

    Linux 中允许众多不同的文件系统共存,如 ext2, ext3, vfat 等。通过使用同一套文件 I/O 系统 调用即可对 Linux 中的任意文件进行操作而无需考虑其所在的具体文件系统格式;更进一步,对文件的 操作可以跨文件系统而执行。如图 1 所示,我们可以使用 cp 命令从 vfat 文件系统格式的硬盘拷贝数据到 ext3 文件系统格式的硬盘;而这样的操作涉及到两个不同的文件系统。

    图 1. 跨文件系统的文件操作

    cbc5b19806bcf79800cc2e8cb43fa36f.png

    “一切皆是文件”是 Unix/Linux 的基本哲学之一。不仅普通的文件,目录、字符设备、块设备、 套接字等在 Unix/Linux 中都是以文件被对待;它们虽然类型不同,但是对其提供的却是同一套操作界面。

    图 2. 一切皆是文件

    eaf798a66526d2c3159d5f68f803e0aa.png

    而虚拟文件系统正是实现上述两点 Linux 特性的关键所在。虚拟文件系统(Virtual File System, 简称 VFS), 是 Linux 内核中的一个软件层,用于给用户空间的程序提供文件系统接口;同时,它也提供了内核中的一个 抽象功能,允许不同的文件系统共存。系统中所有的文件系统不但依赖 VFS 共存,而且也依靠 VFS 协同工作。

    为了能够支持各种实际文件系统,VFS 定义了所有文件系统都支持的基本的、概念上的接口和数据 结构;同时实际文件系统也提供 VFS 所期望的抽象接口和数据结构,将自身的诸如文件、目录等概念在形式 上与VFS的定义保持一致。换句话说,一个实际的文件系统想要被 Linux 支持,就必须提供一个符合VFS标准 的接口,才能与 VFS 协同工作。实际文件系统在统一的接口和数据结构下隐藏了具体的实现细节,所以在VFS 层和内核的其他部分看来,所有文件系统都是相同的。图3显示了VFS在内核中与实际的文件系统的协同关系。

    图3. VFS在内核中与其他的内核模块的协同关系

    fbe89062cf371cb44ce91bace6ed048c.png

    我们已经知道,正是由于在内核中引入了VFS,跨文件系统的文件操作才能实现,“一切皆是文件” 的口号才能承诺。而为什么引入了VFS,就能实现这两个特性呢?在接下来,我们将以这样的一个思路来切入

    文章的正题:我们将先简要介绍下用以描述VFS模型的一些数据结构,总结出这些数据结构相互间的关系;然后 选择两个具有代表性的文件I/O操作sys_open()和sys_read()来详细说明内核是如何借助VFS和具体的文件系统打 交道以实现跨文件系统的文件操作和承诺“一切皆是文件”的口号。

    2 VFS数据结构

    2.1 一些基本概念

    从本质上讲,文件系统是特殊的数据分层存储结构,它包含文件、目录和相关的控制信息。为了描述 这个结构,Linux引入了一些基本概念:

    文件 一组在逻辑上具有完整意义的信息项的系列。在Linux中,除了普通文件,其他诸如目录、设备、套接字等 也以文件被对待。总之,“一切皆文件”。

    目录 目录好比一个文件夹,用来容纳相关文件。因为目录可以包含子目录,所以目录是可以层层嵌套,形成 文件路径。在Linux中,目录也是以一种特殊文件被对待的,所以用于文件的操作同样也可以用在目录上。

    目录项 在一个文件路径中,路径中的每一部分都被称为目录项;如路径/home/source/helloworld.c中,目录 /, home, source和文件 helloworld.c都是一个目录项。

    索引节点 用于存储文件的元数据的一个数据结构。文件的元数据,也就是文件的相关信息,和文件本身是两个不同 的概念。它包含的是诸如文件的大小、拥有者、创建时间、磁盘位置等和文件相关的信息。

    超级块 用于存储文件系统的控制信息的数据结构。描述文件系统的状态、文件系统类型、大小、区块数、索引节点数等,存放于磁盘的特定扇区中。

    如上的几个概念在磁盘中的位置关系如图4所示。

    图4. 磁盘与文件系统

    e405c19f6425b024c9b473a31b2a7ef9.png

    关于文件系统的三个易混淆的概念:

    创建 以某种方式格式化磁盘的过程就是在其之上建立一个文件系统的过程。创建文现系统时,会在磁盘的特定位置写入 关于该文件系统的控制信息。

    注册 向内核报到,声明自己能被内核支持。一般在编译内核的时侯注册;也可以加载模块的方式手动注册。注册过程实 际上是将表示各实际文件系统的数据结构struct file_system_type 实例化。

    安装 也就是我们熟悉的mount操作,将文件系统加入到Linux的根文件系统的目录树结构上;这样文件系统才能被访问。

    2.2 VFS数据结构

    VFS依靠四个主要的数据结构和一些辅助的数据结构来描述其结构信息,这些数据结构表现得就像是对象; 每个主要对象中都包含由操作函数表构成的操作对象,这些操作对象描述了内核针对这几个主要的对象可以进行的操作。

    2.2.1 超级块对象

    存储一个已安装的文件系统的控制信息,代表一个已安装的文件系统;每次一个实际的文件系统被安装时, 内核会从磁盘的特定位置读取一些控制信息来填充内存中的超级块对象。一个安装实例和一个超级块对象一一对应。 超级块通过其结构中的一个域s_type记录它所属的文件系统类型。

    根据第三部分追踪源代码的需要,以下是对该超级块结构的部分相关成员域的描述,(如下同):

    清单1. 超级块

    struct super_block { //超级块数据结构

    struct list_head s_list; /*指向超级块链表的指针*/

    ……

    struct file_system_type *s_type; /*文件系统类型*/

    struct super_operations *s_op; /*超级块方法*/

    ……

    struct list_head s_instances; /*该类型文件系统*/

    ……

    };

    struct super_operations { //超级块方法

    ……

    //该函数在给定的超级块下创建并初始化一个新的索引节点对象

    struct inode *(*alloc_inode)(struct super_block *sb);

    ……

    //该函数从磁盘上读取索引节点,并动态填充内存中对应的索引节点对象的剩余部分

    void (*read_inode) (struct inode *);

    ……

    };

    2.2.2 索引节点对象

    索引节点对象存储了文件的相关信息,代表了存储设备上的一个实际的物理文件。当一个 文件***被访问时,内核会在内存中组装相应的索引节点对象,以便向内核提供对一个文件进行操 作时所必需的全部信息;这些信息一部分存储在磁盘特定位置,另外一部分是在加载时动态填充的。

    清单2. 索引节点

    struct inode {//索引节点结构 …… struct inode_operations *i_op; /*索引节点操作表*/ struct file_operations *i_fop; /*该索引节点对应文件的文件操作集*/ struct super_block *i_sb; /*相关的超级块*/ ……};struct inode_operations { //索引节点方法 …… //该函数为dentry对象所对应的文件创建一个新的索引节点,主要是由open()系统调用来调用 int (*create) (struct inode *,struct dentry *,int, struct nameidata *); //在特定目录中寻找dentry对象所对应的索引节点 struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *); ……};

    2.2.3 目录项对象

    引入目录项的概念主要是出于方便查找文件的目的。一个路径的各个组成部分,不管是目录还是 普通的文件,都是一个目录项对象。如,在路径/home/source/test.c中,目录 /, home, source和文件 test.c都对应一个目录项对象。不同于前面的两个对象,目录项对象没有对应的磁盘数据结构,VFS在遍 历路径名的过程中现场将它们逐个地解析成目录项对象。

    清单3. 目录项

    struct inode {//索引节点结构

    ……

    struct inode_operations *i_op; /*索引节点操作表*/

    struct file_operations *i_fop; /*该索引节点对应文件的文件操作集*/

    struct super_block *i_sb; /*相关的超级块*/

    ……

    };

    struct inode_operations { //索引节点方法

    ……

    //该函数为dentry对象所对应的文件创建一个新的索引节点,主要是由open()系统调用来调用

    int(*create) (struct inode *,struct dentry *,int, struct nameidata *);

    //在特定目录中寻找dentry对象所对应的索引节点

    struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);

    ……

    };

    2.2.4 文件对象

    文件对象是已打开的文件在内存中的表示,主要用于建立进程和磁盘上的文件的对应关系。它由sys_open() 现场创建,由sys_close()销毁。文件对象和物理文件的关系有点像进程和程序的关系一样。当我们站在用户空间来看 待VFS,我们像是只需与文件对象打交道,而无须关心超级块,索引节点或目录项。因为多个进程可以同时打开和操作 同一个文件,所以同一个文件也可能存在多个对应的文件对象。文件对象仅仅在进程观点上代表已经打开的文件,它 反过来指向目录项对象(反过来指向索引节点)。一个文件对应的文件对象可能不是惟一的,但是其对应的索引节点和 目录项对象无疑是惟一的。

    清单4. 文件对象

    struct file {

    ……

    struct list_head f_list; /*文件对象链表*/

    struct dentry *f_dentry; /*相关目录项对象*/

    struct vfsmount *f_vfsmnt; /*相关的安装文件系统*/

    struct file_operations *f_op; /*文件操作表*/

    ……

    };

    struct file_operations {

    ……

    //文件读操作

    ssize_t (*read) (struct file *,char__user *, size_t, loff_t *);

    ……

    //文件写操作

    ssize_t (*write) (struct file *, const char__user *, size_t, loff_t *);

    ……

    int(*readdir) (struct file *, void *, filldir_t);

    ……

    //文件打开操作

    int(*open) (struct inode *, struct file *);

    ……

    };

    2.2.5 其他VFS对象

    2.2.5.1 和文件系统相关

    根据文件系统所在的物理介质和数据在物理介质上的组织方式来区分不同的文件系统类型的。 file_system_type结构用于描述具体的文件系统的类型信息。被Linux支持的文件系统,都有且仅有一 个file_system_type结构而不管它有零个或多个实例被安装到系统中。

    而与此对应的是每当一个文件系统被实际安装,就有一个vfsmount结构体被创建,这个结构体对应一个安装点。

    清单5. 和文件系统相关

    struct file_system_type {

    const char*name; /*文件系统的名字*/

    struct subsystem subsys; /*sysfs子系统对象*/

    intfs_flags; /*文件系统类型标志*/

    /*在文件系统被安装时,从磁盘中读取超级块,在内存中组装超级块对象*/

    struct super_block *(*get_sb) (struct file_system_type*,

    int, constchar*, void *);

    void (*kill_sb) (struct super_block *); /*终止访问超级块*/

    struct module *owner; /*文件系统模块*/

    struct file_system_type * next; /*链表中的下一个文件系统类型*/

    struct list_head fs_supers; /*具有同一种文件系统类型的超级块对象链表*/

    };

    struct vfsmount

    {

    struct list_head mnt_hash; /*散列表*/

    struct vfsmount *mnt_parent; /*父文件系统*/

    struct dentry *mnt_mountpoint; /*安装点的目录项对象*/

    struct dentry *mnt_root; /*该文件系统的根目录项对象*/

    struct super_block *mnt_sb; /*该文件系统的超级块*/

    struct list_head mnt_mounts; /*子文件系统链表*/

    struct list_head mnt_child; /*子文件系统链表*/

    atomic_t mnt_count; /*使用计数*/

    intmnt_flags; /*安装标志*/

    char*mnt_devname; /*设备文件名*/

    struct list_head mnt_list; /*描述符链表*/

    struct list_head mnt_fslink; /*具体文件系统的到期列表*/

    struct namespace *mnt_namespace; /*相关的名字空间*/

    };

    2.2.5.2 和进程相关

    清单6. 打开的文件集

    struct files_struct {//打开的文件集

    atomic_t count; /*结构的使用计数*/

    ……

    intmax_fds; /*文件对象数的上限*/

    intmax_fdset; /*文件描述符的上限*/

    intnext_fd; /*下一个文件描述符*/

    struct file ** fd; /*全部文件对象数组*/

    ……

    };

    struct fs_struct {//建立进程与文件系统的关系

    atomic_t count; /*结构的使用计数*/

    rwlock_t lock; /*保护该结构体的锁*/

    intumask; /*默认的文件访问权限*/

    struct dentry * root; /*根目录的目录项对象*/

    struct dentry * pwd; /*当前工作目录的目录项对象*/

    struct dentry * altroot; /*可供选择的根目录的目录项对象*/

    struct vfsmount * rootmnt; /*根目录的安装点对象*/

    struct vfsmount * pwdmnt; /*pwd的安装点对象*/

    struct vfsmount * altrootmnt;/*可供选择的根目录的安装点对象*/

    };

    2.2.5.3 和路径查找相关

    清单7. 辅助查找

    struct nameidata {

    struct dentry *dentry; /*目录项对象的地址*/

    struct vfsmount *mnt; /*安装点的数据*/

    struct qstr last; /*路径中的***一个component*/

    unsigned intflags; /*查找标识*/

    intlast_type; /*路径中的***一个component的类型*/

    unsigned depth; /*当前symbolic link的嵌套深度,不能大于6*/

    char*saved_names[MAX_NESTED_LINKS + 1];/

    /*和嵌套symbolic link 相关的pathname*/

    union{

    struct open_intent open; /*说明文件该如何访问*/

    } intent; /*专用数据*/

    };

    2.2.6 对象间的联系

    如上的数据结构并不是孤立存在的。正是通过它们的有机联系,VFS才能正常工作。如下的几张图是对它们之间的联系的描述。

    如图5所示,被Linux支持的文件系统,都有且仅有一个file_system_type结构而不管它有零个或多个实例被安装到系统 中。每安装一个文件系统,就对应有一个超级块和安装点。超级块通过它的一个域s_type指向其对应的具体的文件系统类型。具体的 文件系统通过file_system_type中的一个域fs_supers链接具有同一种文件类型的超级块。同一种文件系统类型的超级块通过域s_instances链 接。

    图5. 超级块、安装点和具体的文件系统的关系

    873fe33eeea03ead7332acfbf92d4ff2.png

    从图6可知:进程通过task_struct中的一个域files_struct files来了解它当前所打开的文件对象;而我们通常所说的文件 描述符其实是进程打开的文件对象数组的索引值。文件对象通过域f_dentry找到它对应的dentry对象,再由dentry对象的域d_inode找 到它对应的索引结点,这样就建立了文件对象与实际的物理文件的关联。***,还有一点很重要的是, 文件对象所对应的文件操作函数 列表是通过索引结点的域i_fop得到的。图6对第三部分源码的理解起到很大的作用。

    图6. 进程与超级块、文件、索引结点、目录项的关系

    864ae694def9be76eefec0a8677782b9.png

    3 基于VFS的文件I/O

    到目前为止,文章主要都是从理论上来讲述VFS的运行机制;接下来我们将深入源代码层中,通过阐述两个具有代表性的系统 调用sys_open()和sys_read()来更好地理解VFS向具体文件系统提供的接口机制。由于本文更关注的是文件操作的整个流程体制,所以我 们在追踪源代码时,对一些细节性的处理不予关心。又由于篇幅所限,只列出相关代码。本文中的源代码来自于linux-2.6.17内核版本。

    在深入sys_open()和sys_read()之前,我们先概览下调用sys_read()的上下文。图7描述了从用户空间的read()调用到数据从 磁盘读出的整个流程。当在用户应用程序调用文件I/O read()操作时,系统调用sys_read()被激发,sys_read()找到文件所在的具体文件 系统,把控制权传给该文件系统,***由具体文件系统与物理介质交互,从介质中读出数据。

    图7. 从物理介质读数据的过程

    114ac2be7c82c0fce7e4e76ed353f5d9.png

    3.1 sys_open()

    sys_open()系统调用打开或创建一个文件,成功返回该文件的文件描述符。图8是sys_open()实现代码中主要的函数调用关系图。

    图8. sys_open函数调用关系图

    537120e659ac83c8c1c0e963b2415153.png

    由于sys_open()的代码量大,函数调用关系复杂,以下主要是对该函数做整体的解析;而对其中的一些关键点,则列出其关键代码。

    a. 从sys_open()的函数调用关系图可以看到,sys_open()在做了一些简单的参数检验后,就把接力棒传给do_sys_open():

    1)、首先,get_unused_fd()得到一个可用的文件描述符;通过该函数,可知文件描述符实质是进程打开文件列表中对应某个文件对象的索引值;

    2)、接着,do_filp_open()打开文件,返回一个file对象,代表由该进程打开的一个文件;进程通过这样的一个数据结构对物理文件进行读写操作。

    3)、***,fd_install()建立文件描述符与file对象的联系,以后进程对文件的读写都是通过操纵该文件描述符而进行。

    b. do_filp_open()用于打开文件,返回一个file对象;而打开之前需要先找到该文件:

    1)、open_namei()用于根据文件路径名查找文件,借助一个持有路径信息的数据结构nameidata而进行;

    2)、查找结束后将填充有路径信息的nameidata返回给接下来的函数nameidata_to_filp()从而得到最终的file对象;当达到目的后,nameidata这个数据结构将会马上被释放。

    c.open_namei()用于查找一个文件:

    1)、path_lookup_open()实现文件的查找功能;要打开的文件若不存在,还需要有一个新建的过程,则调用 path_lookup_create(),后者和前者封装的是同一个实际的路径查找函数,只是参数不一样,使它们在处理细节上有所偏差;

    2)、当是以新建文件的方式打开文件时,即设置了O_CREAT标识时需要创建一个新的索引节点,代表创建一个文件。在vfs_create()里的一句 核心语句dir->i_op->create(dir, dentry, mode, nd)可知它调用了具体的文件系统所提供的创建索引节点的方法。注意:这边的索引节点的概念,还只是位于内存之中,它和磁盘上的物理的索引节点的关系就像 位于内存中和位于磁盘中的文件一样。此时新建的索引节点还不能完全标志一个物理文件的成功创建,只有当把索引节点回写到磁盘上才是一个物理文件的真正创 建。想想我们以新建的方式打开一个文件,对其读写但最终没有保存而关闭,则位于内存中的索引节点会经历从新建到消失的过程,而磁盘却始终不知道有人曾经想 过创建一个文件,这是因为索引节点没有回写的缘故。

    3)、path_to_nameidata()填充nameidata数据结构;

    4)、may_open()检查是否可以打开该文件;一些文件如链接文件和只有写权限的目录是不能被打开的,先检查 nd->dentry->inode所指的文件是否是这一类文件,是的话则错误返回。还有一些文件是不能以TRUNC的方式打开的,若 nd->dentry->inode所指的文件属于这一类,则显式地关闭TRUNC标志位。接着如果有以TRUNC方式打开文件的,则更新 nd->dentry->inode的信息

    3.1.1__path_lookup_intent_open()

    不管是path_lookup_open()还是path_lookup_create()最终都是调用 __path_lookup_intent_open()来实现查找文件的功能。 查找时,在遍历路径的过程中,会逐层地将各个路径组成部分解析成目录项对象,如果此目录项对象在目录项缓存中,则直接从缓存中获得;如果该目录项在缓存中 不存在,则进行一次实际的读盘操作,从磁盘中读取该目录项所对应的索引节点。得到索引节点后,则建立索引节点与该目录项的联系。如此循环,直到最终找到目 标文件对应的目录项,也就找到了索引节点,而由索引节点找到对应的超级块对象就可知道该文件所在的文件系统的类型。 从磁盘中读取该目录项所对应的索引节点;这将引发VFS和实际的文件系统的一次交互。从前面的VFS理论介绍可知,读索引节点方法是由超级块来提供的。而 当安装一个实际的文件系统时,在内存中创建的超级块的信息是由一个实际文件系统的相关信息来填充的,这里的相关信息就包括了实际文件系统所定义的超级块的 操作函数列表,当然也就包括了读索引节点的具体执行方式。 当继续追踪一个实际文件系统ext3的ext3_read_inode()时,可发现这个函数很重要的一个工作就是为不同的文件类型设置不同的索引节点操 作函数表和文件操作函数表。

    清单8. ext3_read_inode

    void ext3_read_inode(struct inode * inode)

    {

    ……

    //是普通文件

    if (S_ISREG(inode->i_mode)) {

    inode->i_op = &ext3_file_inode_operations;

    inode->i_fop = &ext3_file_operations;

    ext3_set_aops(inode);

    } elseif (S_ISDIR(inode->i_mode)) {

    //是目录文件

    inode->i_op = &ext3_dir_inode_operations;

    inode->i_fop = &ext3_dir_operations;

    } elseif (S_ISLNK(inode->i_mode)) {

    // 是连接文件

    ……

    } else{

    // 如果以上三种情况都排除了,则是设备驱动

    //这里的设备还包括套结字、FIFO等伪设备

    ……

    }

    3.1.2 nameidata_to_filp子函数:__dentry_open

    这是VFS与实际的文件系统联系的一个关键点。从3.1.1小节分析中可知,调用实际文件系统读取索引节点的方法读取索引节点时,实际文件系统会根据文件 的不同类型赋予索引节点不同的文件操作函数集,如普通文件有普通文件对应的一套操作函数,设备文件有设备文件对应的一套操作函数。这样当把对应的索引节点 的文件操作函数集赋予文件对象,以后对该文件进行操作时,比如读操作,VFS虽然对各种不同文件都是执行同一个read()操作界面,但是真正读时,内核 却知道怎么区分对待不同的文件类型。

    清单9. __dentry_open

    staticstruct file *__dentry_open(struct dentry *dentry, struct vfsmount *mnt,

    intflags, struct file *f,

    int(*open)(struct inode *, struct file *))

    {

    struct inode *inode;

    ……

    //整个函数的工作在于填充一个file对象

    ……

    f->f_mapping = inode->i_mapping;

    f->f_dentry = dentry;

    f->f_vfsmnt = mnt;

    f->f_pos = 0;

    //将对应的索引节点的文件操作函数集赋予文件对象的操作列表

    f->f_op = fops_get(inode->i_fop); 

    ……

    //若文件自己定义了open操作,则执行这个特定的open操作。

    if (!open&& f->f_op)

    open= f->f_op->open;

    if (open) {

    error = open(inode, f);

    if (error)

    gotocleanup_all;

    ……

    returnf;

    }

    3.2 sys_read()

    sys_read()系统调用用于从已打开的文件读取数据。如read成功,则返回读到的字节数。如已到达文件的尾端,则返回0。图9是sys_read()实现代码中的函数调用关系图。

    图9. sys_read函数调用关系图

    1af805ca2df337fd8b987c529c4af8d6.png

    对文件进行读操作时,需要先打开它。从3.1小结可知,打开一个文件时,会在内存组装一个文件对象,希望对该文件执行的操作方法已在文件对象设置好。所以 对文件进行读操作时,VFS在做了一些简单的转换后(由文件描述符得到其对应的文件对象;其核心思想是返回 current->files->fd[fd]所指向的文件对象),就可以通过语句 file->f_op->read(file, buf, count, pos)轻松调用实际文件系统的相应方法对文件进行读操作了。

    4 解决问题

    4.1 跨文件系统的文件操作的基本原理

    到此,我们也就能够解释在Linux中为什么能够跨文件系统地操作文件了。举个例子,将vfat格式的磁盘上的一个文件a.txt拷贝到ext3格式的磁 盘上,命名为b.txt。这包含两个过程,对a.txt进行读操作,对b.txt进行写操作。读写操作前,需要先打开文件。由前面的分析可知,打开文件 时,VFS会知道该文件对应的文件系统格式,以后操作该文件时,VFS会调用其对应的实际文件系统的操作方法。所以,VFS调用vfat的读文件方法将 a.txt的数据读入内存;在将a.txt在内存中的数据映射到b.txt对应的内存空间后,VFS调用ext3的写文件方法将b.txt写入磁盘;从而 实现了最终的跨文件系统的复制操作。

    4.2“一切皆是文件”的实现根本

    不论是普通的文件,还是特殊的目录、设备等,VFS都将它们同等看待成文件,通过同一套文件操作界面来对它们进行操作。操作文件时需先打开;打开文件 时,VFS会知道该文件对应的文件系统格式;当VFS把控制权传给实际的文件系统时,实际的文件系统再做出具体区分,对不同的文件类型执行不同的操作。这 也就是“一切皆是文件”的根本所在。

    5 总结

    VFS即虚拟文件系统是Linux文件系统中的一个抽象软件层;因为它的支持,众多不同的实际文件系统才能在Linux中共存,跨文件系统操作才能实现。 VFS借助它四个主要的数据结构即超级块、索引节点、目录项和文件对象以及一些辅助的数据结构,向Linux中不管是普通的文件还是目录、设备、套接字等 都提供同样的操作界面,如打开、读写、关闭等。只有当把控制权传给实际的文件系统时,实际的文件系统才会做出区分,对不同的文件类型执行不同的操作。由此 可见,正是有了VFS的存在,跨文件系统操作才能执行,Unix/Linux中的“一切皆是文件”的口号才能够得以实现。

    【编辑推荐】

    【责任编辑:武晓燕 TEL:(010)68476606】

    点赞 0

    展开全文
  • 来自《实例演绎Unix/Linux的"一切皆文件"思想》 大家习惯了使用socket来编写网络程序,socket是网络编程事实上的标准。 我们知道,在Unix/Linux系统中“一切皆文件”,socket也被认为是一种文件,socket被表示成文件...

    来自《实例演绎Unix/Linux的"一切皆文件"思想》

    大家习惯了使用socket来编写网络程序,socket是网络编程事实上的标准。

    我们知道,在Unix/Linux系统中“一切皆文件”,socket也被认为是一种文件,socket被表示成文件描述符。

    但socket的行为并不很像文件。比如:

    • 无法用 “open一个路径” 的方式打开一个socket,必须用socket系统调用来创建。
    • 文件系统的close可以关闭socket描述符,但优雅关闭TCP socket却需要shutdown。
    • 标准文件系统没有诸如bind,connect,accept,recvfrom等操作。
    • socket编程繁琐易错,与标准文件操作open/read/write/close等迥异。

    如果能像对待标准文件那样对待socket,用read/write读写它,该有多好。本文就来实现这么一个机制来实现UDP socket的数据收发,非常简单,大概160行代码。

    在给出代码之前,我们得先理解什么是“一切皆文件”。这一切还要从最开始说起。

    “一切皆文件”之始

    在Unix原始论文《The UNIX TimeSharing System》中,里奇和汤普森就提出了“一切皆文件”的朴素思想。

    “Unix将普通文件和设备通过目录统一在了一个递归的树形结构中。形成了一个统一的命名空间。”

    Unix文件系统是一个挂载在ROOT的树形目录结构,每一个目录节点都可以挂载一棵子树。

    “一切皆文件”意味着这棵树上可以挂载一切。比如nfs就可以将网络上另外一台主机的文件系统挂载到本机的目录树上,想想看,这棵文件树上的一个文件竟然在另一台机器上,这是多么不可思议。

    但这终究只是个理想。

    “一切皆文件”之殇

    Unix宣称的 “一切皆文件” 并没有完全做到。我们看两个破坏优雅的反例:

    1. 奇怪的ioctl
    2. 奇怪的BSD socket

    “一切皆文件”的背后是一切操作都可以抽象成open,read,write,close。但是ioctl是什么鬼?

    有一些行为很难用read和write来定义,比如光盘播放时快进。ioctl的出现弥补了read/write的缺失。但是ioctl有自己的问题:

    • 无法根据一个文件描述符确定ioctl命令列表,只能根据错误码来判断。
    • ioctl命令和设备紧耦合,重依赖设备类型,控制命令呈暴增趋势。

    解决这个问题非常简单,为每一个设备增加一个名叫ctrl的文件。将ioctl的调用转换为针对ctrl文件的读写即可。典型的例子参见PCIe设备的配置空间的读写。

    总体而言,ioctl增加了文件操作的复杂性。

    现在说说socket的问题。

    虽然socket也是一个文件描述符,它的操作接口和标准文件接口非常不同:

    • 创建socket必须用socket调用而不是open,socket在打开之前不能存在。
    • bind,connect,accept等都是独立的系统调用,没有标准文件操作与之对应。

    socket一开始是作为一种封装TCP/IP网络流的IPC机制出现的,而TCP/IP一开始就没有被抽象成文件。下图来自关于socket的wiki:
    在这里插入图片描述
    可见,socket几乎就是为TCP量身定制的接口,和TCP状态机相互对应。换句话说,socket并不是严格意义上的文件。

    这意味着很难用统一的方法处理socket的IO流和普通文件的IO流。

    Unix “一切皆文件” 退化成了“一切皆文件描述符”:

    • 一切皆文件: 文件属于Unix/Linux目录树,编址于统一命名空间。
    • 一切皆文件描述符: 文件描述符属于进程打开文件表,进程内可见。

    同样奇怪的是pipe调用,它创建了一对文件描述符,但也仅仅是文件描述符,而没有被纳入到统一命名空间的Unix/Linux目录树中。

    Unix哲学中的“一切皆文件”和其它的原则比如“组合小程序”等是相辅相成的。如果“一切皆文件”被破坏,那么便很难简单串接小程序实现复杂逻辑:

    • socket没有标准文件的open和close操作,不能cat一个socket,也没法向一个socket里echo数据。

    因此就出现了socat,netcat这种大家都说好,但实际上没有必要的微型网络程序。

    如果一个网络连接也是一个系统目录树上的文件,便可以如下打开一个连接:

    sd = open("/sys/udp/1.1.1.1/53", ...);
    

    sacat,netcat没有必要了,直接在shell上就能完成所有的操作。比如发包可以这么做:

    echo aaaaaaa >/sys/udp/1.1.1.1/53
    

    对应收包操作如下:

    cat /sys/udp/1.1.1.1/53
    

    你甚至可以这样dup文件描述符:

    exec 6<>/sys/udp/1.1.1.1/53
    echo aaaaaaa >&6
    read -ru6 # cat <&6 将面临EOF问题。
    

    socat,netcat这些微型网络程序实际上就是标准文件IO接口对socket IO接口的封装。

    其实,bash shell中,就隐藏着这么一个socket文件的机制:
    在这里插入图片描述
    我们可以在bash中如下访问baidu的主页:

    bash-3.2$ exec 6<>/dev/tcp/www.baidu.com/80
    bash-3.2$ echo  'GET /index.html HTTP/1.1' >&6
    bash-3.2$ echo   >&6
    bash-3.2$ cat <&6
    HTTP/1.1 200 OK
    Accept-Ranges: bytes
    Cache-Control: no-cache
    Connection: Keep-Alive
    Content-Length: 14615
    Content-Type: text/html
    ...
    

    是不是连telnet也不需要了呢?嗯,wget,curl都可以消失。

    事实上,我们可以用和处理普通文件完全一样的程序来处理socket,理论上只要有cat/read和echo/write两类4个命令就可以了。

    遗憾的是, /dev/tcp/www.baidu.com/80 文件并不存在,这只是bash为我们提供的一种善意的假象。

    如果你用过socat(可以直接用yum install安装),你会发现更酷的玩法,我们来看一个TCP服务器用socat怎么实现:

    [root@localhost ~]# socat tcp-listen:1234,reuseaddr,fork exec:bash,pty,stderr
    

    不用写一行代码。此时从另一个终端,我们便可以用telnet登录这个服务器:

    [root@localhost ~]# telnet 127.0.0.1 1234
    Trying 127.0.0.1...
    Connected to 127.0.0.1.
    Escape character is '^]'.
    bash: 此 shell 中无任务控制
    [root@localhost ~]# pwd
    pwd
    /root
    

    当然了,你可以同样用socat,这么干:

    [root@localhost ~]# socat -,raw,echo=0 tcp:host:1234
    

    如果一个连接可以表示成目录树中的一个文件,我们就不需要socat,telnet了,我们只需要cat/read和echo/write等两类命令即可完成所有这一切。

    我们可以自己实现这样的机制。

    插曲-“一切皆文件”之Plan 9原教旨

    写这篇文章的想法源自于我在班车上刷知乎,偶然间看到了Plan 9这个出自汤普森,里奇这帮人之手号称要取代Unix的下一代分布式操作系统(好长的描述)…

    Plan 9承诺彻底贯彻执行 一切皆文件。 这将使其有能力对外提供统一的文件系统视图,以实现分布式。

    Plan 9是一个真正的分布式系统,它可以将分布在不同位置的所有资源作为文件统一在同一棵目录树中,这便是Unix最初的愿景:
    在这里插入图片描述
    看上图,一台机器上的一个可执行文件可以运行在另一台机器的CPU上,这一切对于用户都是透明的,Plan 9用9P屏蔽了底层的通信细节。之所以能实现这样的效果,全部拜“一切皆文件”所赐。

    Plan 9的创举在于,它将分散的计算机的内部硬件组件和软件组件(而不是计算机本身)看成了独立的资源,而不管它们之间是如何连接的:

    • 同一台机器内部的资源通过内部总线连接。
    • 不同机器的资源通过网络连接。

    让我们领略一下Plan 9中的“一切皆文件”对于TCP而言意味着什么:
    在这里插入图片描述
    暂时不要恋战,先把本文看完,因为我下面的UDP的实例算是Plan实践的简版,理解了这个简版,再仔细阅读下面的链接以体会原汁原味的Plan 9-The Organization of Networks in Plan 9:
    http://doc.cat-v.org/plan_9/4th_edition/papers/net/

    Plan 9并没有由于其设计的先进性而变得流行起来,不过幸运的是,它的思想被Linux吸收了。我们便有机会在熟悉的Linux系统实现憧憬中的socket文件机制了。

    “一切皆文件”之Linux

    Linux贯彻一切皆文件的程度要远远超过传统Unix。Linux除了普通文件,目录,设备文件,管道等之外,实现非常多的特殊文件,这些都是直接或间接来自Plan 9:

    • procfs【此乃Plan 9的嫡系】
    • sysfs
    • cpuset
    • debugfs
    • cgroup

    真的是一切皆文件了。你无需调用特殊的接口,只需要echo就可以在sysfs中通过写文件的方式将CPU进行热插拔:

    [root@localhost ~]# echo 0 >/sys/devices/system/cpu/cpu0/online
    [root@localhost ~]# echo 0 >/sys/devices/system/cpu/cpu2/online
    

    结果就只剩2个CPU了

    [root@localhost ~]# cat /proc/cpuinfo |grep processor
    processor	: 1
    processor	: 3
    [root@localhost ~]#
    

    Linux sysfs实现UDP socket文件机制

    UDP socket文件就是基于这种sysfs实现的,我称它UDP socket sysfs。

    本文不是讲sysfs原理的,这方面的资源已经很多了,我就不再赘述。这里仅仅提sysfs的最基本特征:

    • 每一个可以表示为文件的对象Obj都是sysfs中的一个目录。
    • 每一个Obj的任何属性都表示为该Obj对应目录下的一个文件。

    以上面的CPU热插拔为例,/sys/devices/system/cpu是一个目录,它表示系统的所有CPU,其属性为:

    cpu0  cpu1  cpu2  cpu3  cpuidle  isolated  kernel_max  microcode  modalias  nohz_full  offline  online  possible  power  present  uevent
    

    我们查看其offline属性,它表示已经下掉的CPU,只需要读该文件即可:

    [root@localhost ~]# cat /sys/devices/system/cpu/offline
    0,2
    

    Linux sysfs使传统的ioctl系统调用再无必要。


    为了获得第一即视感,我先演示UDP socket sysfs文件的效果,然后再给出源码。

    我们希望用sysfs下的文件表示UDP socket,因此我们要创建一个表示UDP socket的目录:

    [root@localhost sysfs_test]# ls /sys/kobject_udp/
    ctrl
    

    该目录表示UDP socket的汇总,它有一个属性文件,即ctrl,对其读写将会触发一系列的事件:

    • 写create到ctrl:创建一个UDP socket。
    • 写shutdown到ctrl:销毁UDP socket。
    [root@localhost sysfs_test]# echo -n create >/sys/kobject_udp/ctrl
    [root@localhost sysfs_test]# ls /sys/kobject_udp/
    ctrl  instance_0
    [root@localhost sysfs_test]# ls /sys/kobject_udp/instance_0/
    ctrl  data
    [root@localhost sysfs_test]#
    

    创建一个UDP socket sysfs实例相当于在kobject_udp创建了一个目录instance_0,该UDP socket sysfs实例有两个属性:

    • data:用于数据的收发。
    • ctrl:用于控制,读写该文件可以实现connect,bind,set/getsockopt等。

    数据和控制相分离,但是它们都是Linux系统目录树中的可读写的文件,写ctrl就能达到对socket进行控制的效果:

    [root@localhost sysfs_test]# echo -n bind 127.0.0.1:123 >/sys/kobject_udp/instance_0/ctrl
    [root@localhost sysfs_test]# netstat -anup
    Active Internet connections (servers and established)
    Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
    udp        0      0 0.0.0.0:123             0.0.0.0:*                           -
    [root@localhost sysfs_test]#
    

    可见,通过写入UDP socket sysfs实例的ctrl文件,新建的socket便bind到本地123端口,可以通过netstat看出来操作已经成功。

    接下来我们将其connect到本地的另一个端口,试试数据的收发:
    在这里插入图片描述
    现在仿照bash的/dev/udp/ h o s t / host/ host/port的实现,试试文件描述符dup:
    在这里插入图片描述
    OK,工作的不错。就是要这样的效果。

    好了,现在该给出源码了:

    // sysudp.c
    // make -C /lib/modules/`uname -r`/build SUBDIRS=`pwd` modules
    // insmod ./sysudp.ko
    #include <linux/module.h>
    #include <linux/init.h>
    #include <linux/sysfs.h>
    #include <linux/ip.h>
    #include <linux/in.h>
    
    static struct kobject *udp_kobject, *srv;
    static char ctrl, cctrl, data;
    
    struct socket *ksock;
    struct sockaddr_in addr, raddr;
    struct msghdr msg;
    struct iovec iov;
    mm_segment_t oldfs;
    
    static int bind_socket(unsigned short port)
    {
    	memset(&addr, 0, sizeof(struct sockaddr));
    	addr.sin_family = AF_INET;
    	addr.sin_addr.s_addr = htonl(INADDR_ANY);
    	addr.sin_port = htons(port);
    
    	if (ksock->ops->bind(ksock, (struct sockaddr*)&addr, sizeof(struct sockaddr)) < 0)
    		return -1;
    
    	return 0;
    }
    
    static ssize_t cctrl_store(struct kobject *kobj, struct kobj_attribute *attr, char *buf, size_t count)
    {
    	// 为了代码简短,未做字符串解析,采用了硬编码,且仅支持一个socket的创建
    	if (!strcmp(buf, "connect 127.0.0.1:321")) { // 写ctrl文件实现connect
    		memset(&raddr, 0, sizeof(struct sockaddr));
    		raddr.sin_family = AF_INET;
    		raddr.sin_addr.s_addr = htonl(0x7f000001);
    		raddr.sin_port = htons(321);
    	} else if (strstr(buf, "bind 127.0.0.1:123")) { // 写ctrl文件实现bind
    		bind_socket(123);
    	} else if (strstr(buf, "setsockopt")) { // sockopt也是写文件完成
    		// TODO
    	}
    	return count;
    }
    
    static ssize_t cctrl_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
    {
    	return sprintf(buf, "bind x.x.x.x:yyy\nconnect x.x.x.x:yyy\nsetsockopt value\n[TODO]....\n");
    }
    
    static ssize_t data_store(struct kobject *kobj, struct kobj_attribute *attr, char *buf, size_t count)
    {
    	int size = 0;
    
    	if (ksock->sk == NULL) return 0;
    
    	iov.iov_base = buf;
    	iov.iov_len = count;
    	msg.msg_flags = 0;
    	msg.msg_name = &raddr;
    	msg.msg_namelen  = sizeof(struct sockaddr_in);
    	msg.msg_control = NULL;
    	msg.msg_controllen = 0;
    	msg.msg_iov = &iov;
    	msg.msg_iovlen = 1;
    	msg.msg_control = NULL;
    
    	oldfs = get_fs();
    	set_fs(KERNEL_DS);
    	size = sock_sendmsg(ksock, &msg, count);
    	set_fs(oldfs);
    
    	return size;
    }
    
    static ssize_t data_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
    {
    	int size = 2048;
    
    	if (ksock->sk == NULL) return 0;
    
    	iov.iov_base = buf;
    	iov.iov_len = size;
    	msg.msg_flags = 0;
    	msg.msg_name = &addr;
    	msg.msg_namelen  = sizeof(struct sockaddr_in);
    	msg.msg_control = NULL;
    	msg.msg_controllen = 0;
    	msg.msg_iov = &iov;
    	msg.msg_iovlen = 1;
    	msg.msg_control = NULL;
    
    	oldfs = get_fs();
    	set_fs(KERNEL_DS);
    	size = sock_recvmsg(ksock, &msg, size, msg.msg_flags);
    	set_fs(oldfs);
    
    	return size;
    }
    
    static struct kobj_attribute ctrl_attribute =__ATTR(ctrl, 0660, cctrl_show, cctrl_store);
    static struct kobj_attribute data_attribute =__ATTR(data, 0660, data_show, data_store);
    
    static ssize_t ctrl_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
    {
    	return sprintf(buf, "create\nshutdown\n[TODO]....\n");
    }
    
    static int create_socket()
    {
    	if (sock_create(AF_INET, SOCK_DGRAM, IPPROTO_UDP, &ksock) < 0) 
    		return -1;
    
    	return 0;
    }
    
    static ssize_t ctrl_store(struct kobject *kobj, struct kobj_attribute *attr, char *buf, size_t count)
    {
    	// 仅支持一个socket
    	if (!strcmp(buf, "create")) { // 写ctrl文件创建socket实例
    		if (!srv) {
    			srv = kobject_create_and_add("instance_0", udp_kobject);
            	sysfs_create_file(srv, &ctrl_attribute.attr);
            	sysfs_create_file(srv, &data_attribute.attr);
    			create_socket();
    		}
    	} else if (!strcmp(buf, "shutdown")) { // 写ctrl文件销毁socket实例
    		if (srv)
    			if (ksock)
    				sock_release(ksock);
    			kobject_put(srv);
    			srv = NULL;
    	}
    
    	return count;
    }
    static struct kobj_attribute foo_attribute =__ATTR(ctrl, 0660, ctrl_show, ctrl_store);
    
    static int __init sysudp_init (void)
    {
    	int error = 0;
    
    	srv = NULL;
    	udp_kobject = kobject_create_and_add("kobject_udp", NULL);
    	if(!udp_kobject)
    		return -ENOMEM;
    
    	error = sysfs_create_file(udp_kobject, &foo_attribute.attr);
    
    	return error;
    }
    
    static void __exit sysudp_exit (void)
    {
    	if (srv) {
    		if (ksock)
    			sock_release(ksock);
    		kobject_put(srv);
    	}
    	kobject_put(udp_kobject);
    }
    MODULE_LICENSE("GPL");
    module_init(sysudp_init);
    module_exit(sysudp_exit);
    

    Review源码,我们发现了socket sysfs和socket接口的两点最大不同,socket sysfs文件有以下性质:

    • socket用sysfs的一个目录表示
      即socket sysfs文件作为一个对象在sysfs是一个目录,该目录下两个属性文件用于实际操作,一个是数据通信用的data属性文件,一个是作为控制使用的ctrl属性文件。
    • 消除了socket的open行为
      这是最精妙的。socket只能被创建而不是被打开,只有存在的东西才能被打开。能被打开的是socket的data属性文件和ctrl文件,打开它们的目的是读写它们,这就是“一切皆文件”在socket上的体现。

    在这个源码之后,其实还有很多的TODO:

    • socket文件的访问控制如何做
      以往的socket文件描述符的作用域是创建它的进程,如果采用sysfs文件的话,将会是全局可见,如何进程访问控制,需要设计一套规则。
    • 性能问题
      本文到此为止没有涉及任何有关性能的问题,但是在实际实现中,这个是必须要考虑的。

    UDP socket sysfs文件实现后,TCP呢?我们需要实现一个TCP的socket sysfs文件机制,从而可以用shell脚本粘合独立的小程序实现复杂的TCP客户端和TCP服务器。

    TCP客户端比较容易实现,和UDP socket文件实现类似,TCP服务端需要做的更多。典型是如何实现连接管理。或者说,到底还需不需要连接管理,需不需要socket的listen,accept那一套也都是疑问。

    bash没有实现类似 /dev/tcp/$host/$port 那样的伪设备文件来实现TCP脚本服务器,但是zsh的ztcp module可以做到。这里给出一个zsh脚本实现的TCP服务器:

    #!/usr/bin/zsh
    
    # 加载ztcp模块
    zmodload zsh/net/tcp
    
    # 处理IO,其实就是一个echo
    handle-io() (
    	# 从TCP client读取一行。这里用cat <&4会有问题,因为没有EOF
        read -ru4 line
        # 打印收到的东西
        echo from client: $line
        # 把收到的内容echo回对端
        echo echo from server: $line >&4
        # 关闭描述符
        exec 4>&-
    )
    
    ztcp -l -d 3 12345
    while ztcp -ad4 3; do
    	# 在子进程中处理TCP client
        (handle-io)
        # 父进程关闭TCP client
        exec 4>&-
    done
    

    多么典型的TCP accept/fork编程模型啊。

    写一个sysfs内核模块,实现以下机制即可:

    • 实现ztcp -l:通过写TCP目录的ctrl文件实现
    • 实现ztcp -a:接收连接后自动创建TCP client目录
    • 实现TCP通信:通过读写TCP client目录的data文件实现。

    这并不困难。

    我们憧憬着,将来netcat,socat,telnet,ztcp这类专门处理简单网络操作的微型程序将不再需要,我们只需要cat/read,echo/write即可。而这个憧憬在Plan 9上已经成了现实。

    思考

    本文结束之前,我们来思考一个问题:

    • 是设计一个新的API,还是用不同的参数调用既有API?

    以文件操作为例,假设文件IO的read和write都工作的很好,现在又个新的需求,要实现的业务逻辑我们称之为business,如何实现这个需求不外乎以下两种:

    1. 将business的逻辑封装成一个新的API,使用者直接调用
    2. 将business的逻辑抽象成数据流暴露给使用者

    这就又回到了《60行C代码实现一个shell》一文中的例子。

    请实现式子 a + b c \dfrac{a+b}{c} ca+b的计算。

    对于第1种方案,显然是要这么做:

    int business(int a, int b, int c)
    {
    	return (a+b)/c;
    }
    

    简单直接,实现又快。

    对于第2种思路,便需要如下步骤:

    1. 分别写一个表示两个数加法和两个数相除的程序。
    2. 用管道将这些程序组织起来,获取结果。

    显然没有第1种方法直接快速。但是,如果这个要求解的式子换成一元二次方程的求根公式怎么办?是重新重构business函数呢,还是重新组合运算符程序用管道连接它们呢?

    之所以会出现ioctl以及socket接口这种奇怪的API,因为它们足够直接,实现足够快速,才因此破坏了Unix“一切皆文件”的原则。

    这些API的名字基本能看出它们的功能,connect是建立连接的,bind是绑定地址的,ioctl是发送控制命令的。这些API只能做它们声明能做的事情。

    read和write则不然,参数只是一个裸buffer,在API层面没有任何自解释特征,所以,少就是多,无则是全,read和write事实上可以完成 “任意” 操作!

    这便是“一切皆文件”的精髓,这便是我写sysfs UDP socket文件模块的缘由,欣赏这种美并宣扬它便是我写这篇文章的动机。


    月雪 灌肠。
    浙江温州皮鞋湿,下雨进水不会胖。

    展开全文
  • Linux中一切皆文件

    千次阅读 2021-01-17 18:29:35
    谈一谈Linux中一切皆文件1、Linux中所有内容都是以文件的形式保存和管理,即:一切皆文件。普通文件是文件。目录(在win下称为文件夹)是文件。硬件设备(键盘、硬盘、打印机)是文件。套接字(socket)、网络通信等资源也...

    谈一谈Linux中一切皆文件

    1、

    Linux中所有内容都是以文件的形式保存和管理,即:一切皆文件。

    普通文件是文件。

    目录(在win下称为文件夹)是文件。

    硬件设备(键盘、硬盘、打印机)是文件。

    套接字(socket)、网络通信等资源也都是文件。

    2、

    文件类型:

    1) 普通文件

    类似 mp4、jgp、html这样,可直接拿来使用的文件都属于普通文件。

    8773429fcedb509ffef539905f668a72.png

    2) 目录文件

    习惯win系统的用户来说,目录是文件可能不太好接受。

    Linux系统中,目录文件包含了此目录中各个文件的文件名以及指向这些文件的指针,打开目录等同于打开目录文件。

    即:只要有权限,可以随意访问目录中的任何文件。

    vim 目录名

    5633990860d1973ff8deb908bc12e876.png

    3) 字符设备文件和块设备文件

    通常隐藏在/dev/目录下,当进行设备读取或外设交互时才会被使用。

    例如:磁盘光驱属于块设备文件,串口设备则属于字符设备文件。

    0c301b7914c2f02cd11ba74ec25073cc.png

    4) 套接字文件(socket)

    套接字文件一般隐藏在 /var/run/目录下,用于进程间的网络通信。

    15e7130bea152ab6017046b65ea33482.png

    5) 符号链接文件(symbolic link)

    类似与win中的快捷方式,是指向另一文件的指针(软链接)。

    436dfdb8b5ed04ee501be2570a75352f.png

    6) 管道文件(pipe)

    主要用于进程间通信。

    例如:使用mkfifo命令创建一个FIFO文件,与此同时启用进程A从FIFO文件读数据,启用进程B从FIFO文件中写数据,随写随读。

    3、

    一切皆文件的利弊:

    与Windows 系统不同,Linux 系统没有C盘、D盘、E盘那么多的盘符,只有一个根目录(/),所有的文件(资源)都存储在以根目录(/)为树根的树形目录结构中。

    (1)这样做最明显的好处是,开发者仅需要使用一套 API 和开发工具即可调取 Linux 系统中绝大部分的资源。举个简单的例子,Linux 中几乎所有读(读文件,读系统状态,读 socket,读PIPE)的操作都可以用read函数来进行;几乎所有更改(更改文件,更改系统参数,写 socket,写 PIPE)的操作都可以用write函数来进行。

    (2)不利之处在于,使用任何硬件设备都必须与根目录下某一目录执行挂载操作,否则无法使用。我们知道,本身Linux具有一个以根目录为树根的文件目录结构,每个设备也同样如此,它们是相互独立的。如果我们想通过Linux上的根目录找到设备文件的目录结构,就必须将这两个文件系统目录合二为一,这就是挂载的真正含义。

    展开全文
  • 我们知道我们平时使用的文件操作,如C语言C++或者JAVA等语言,都会封装自己的对文件操作的函数接口,供我们对文件进行操作;而实际上,当我们使用这些文件操作的函数接口时候,本质都是访问硬件设备(磁盘); 一个...
  • 深入解析Linux系统中的“一切皆文件

    千次阅读 2021-03-23 10:29:11
    一切皆文件1 C文件IO2 文件相关系统调用接口2.1 open 接口介绍3 文件描述符3.1 什么是文件描述符3.2 文件描述符的分配规则3 你是怎么理解重定向的(从OS的角度)4二级目录三级目录 1 C文件IO C默认会打开三个输入...
  • 为什么说在Linux世界里,一切皆文件
  • Linux下一切文件,文件夹,socket,管道,设备等都被看成是文件,可以直接用相同的系统API(如write/read)对其读写操作,所以说Linux下一切皆文件。(在windows下,不同文件或者不同的文件类型需要调用不同的API去...
  • 为何说在LINUX中一切皆文件

    千次阅读 2020-03-20 00:11:16
    关于linux下一切皆文件的思考linux和windows的区别了解一切皆文件的内容文件解析图虚拟文件系统VFS文件系统 linux和windows的区别 今天突然想到 在linux中 我们所熟知的一句话叫做,“linux下一切皆文件” 于是乎就...
  • Linux运维-运维课程运维基本功d2-基本命令-07-系统中一切皆文件.mp4
  • Linux 一切皆文件思想

    千次阅读 2018-07-27 10:42:56
    一切皆文件” 在linux开发过程中,相信大家都听过一句话叫作“limux下,一切皆文件”。这句话是linux/unix的哲学核心思想,下面我们就针对这句话给大家进行展开解释下。 这句话中的“文件”不仅仅是我们通常所指...
  • linux系统中一切皆文件

    万次阅读 2018-07-27 15:21:46
    linux系统中一切皆文件 linux下“一切皆文件”是Unix/Linux的基本哲学之一。 普通文件、目录、字符设备、块设备和网络设备(套接字)等在Unix/Linux都被当做文件来对待。虽然他们的类型不同,但是linux系统为...
  • 一切皆文件”,指的是,对所有文件(目录、字符设备、块设备、套接字、打印机等)操作,读写都可用fopen()/fclose()/fwrite()/fread()等函数进行处理。屏蔽了硬件的区别,所有设备都抽象成文件,提供统一的接口给...
  • 大家习惯了使用socket来编写网络程序,socket是网络编程事实上的标准。我们知道,在Unix/Linux系统中“一切皆文件”,socket也被认为是一种文件,soc...
  • 走进Linux(一切皆文件)

    万次阅读 多人点赞 2021-12-16 17:43:30
    众生皆浮云,一切皆文件 我来管系统,文件任我玩
  • Linux一切皆文件(包含好处和弊端)

    千次阅读 2019-06-29 20:44:56
    Linux 中所有内容都是以文件的形式保存和管理的,即一切皆文件,普通文件是文件,目录(Windows 下称为文件夹)是文件,硬件设备(键盘、监视器、硬盘、打印机)是文件,就连套接字(socket)、网络通信等资源也都是...
  • 一切皆文件(文件系统)

    千次阅读 2021-11-11 09:46:36
    文件,在Linux中一切皆文件,普通的文件和目录、块设备、管道和Socket都是交给文件系统管理。 文件的数据结构是? 索引节点(inode):记录文件的原信息,比如inode编号、文件大小、访问权限、创建时间、修改时间...
  • 为何linux一切皆文件

    千次阅读 2019-11-25 23:35:17
    本文作为linux文件的基础介绍,是linux文件系列的开篇,主要面向想深入学习linux的小伙伴,最后的延伸学习部分给出了学习建议。
  • UNIX一切皆文件!!

    千次阅读 2017-05-09 17:32:45
    忘记从哪里听到这样一句话,UNIX一切皆文件。下面是我的理解,这句话真的UNIX最精髓的一句话! 我在研究管道的时候,用到popen和pclose,这里popen返回值是FILE *fp,大多是理解成管道,可以跟子进程的标准输入输出...
  • 记得Linux里有一句话,叫一切皆文件。 最近在看书的时候,看到说,为什么在任何目录下都能执行ls等命令呢。原因是ls是一个文件,它的位置是/bin/ls,虽然是一个文件,但是它通过PATH添加到了执行文件路径的变量中,...
  • 一切皆文件:UNIX,Linux 操作系統的設計哲學

    万次阅读 多人点赞 2020-08-19 10:37:27
    Linux的进程、线程、文件描述符是什么 说到进程,恐怕面试中最常见的问题就是线程和进程的关系了,那么先说一下答案:在 Linux 系统中,进程和线程几乎没有区别。 Linux 中的进程就是一个数据结构,看明白就可以理解...
  • 一切皆Socket!” 话虽些许夸张,但是事实也是,现在的网络编程几乎都是用的socket。 ——有感于实际编程和开源项目研究。 我们深谙信息交流的价值,那网络中进程之间如何通信,如我们每天打开浏览器浏览网页时...
  • C-一切皆文件

    千次阅读 2020-06-06 16:59:05
    输入输出就是把输入输出设备抽象为文件 #include <stdio.h> int main(int argc, char const *argv[]) { int a=0; printf("%s\n","aaaaaaaaaaaa"); //fclose(stdout);关闭输出文件以后就无法输出到屏幕 ...
  • linux一切皆文件

    千次阅读 2018-03-06 11:52:59
    为什么说linux中一切都为文件?首先,我们看一下linux中文件的类型有哪些。 VFS(Virtual Filesystem,虚拟文件系统)是linux内核中的重要组成部分,用来处理与Unix标准文件系统相关的所有系统调用。其健壮性表现在能...
  • Linux中“一切皆文件

    千次阅读 2017-05-15 19:59:30
    一切皆文件” 在linux开发过程中,相信大家都听过一句话叫作“limux下,一切皆文件”。这句话是linux/unix的哲学核心思想,下面我们就针对这句话给大家进行展开解释下。 这句话中的“文件”不仅仅是我们通常所...
  • 刚开始接触linux的时候, 看到过这样一句话:linux中一切皆文件。 当理解深刻后, 发现确实如此。  又记得当年学习C语言的时候, 看到书上介绍文本文件和二进制文件, 瞬间懵逼, 理解非常模糊。 后来玩h.264, 生成...
  • 一切皆文件的 Linux 是怎样分区的?

    千次阅读 2020-04-21 20:15:00
    文件系统管理-1.1回顾分区和文件系统 写在前面:自己写完检查可能会有所纰漏,读者们如若发现哪里有错误,可在下方留言或私信我,我会第一时间改正 写在后面:希望这些讲解对你有所帮助,希望大家多多点赞和关注...
  • md5sum file

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 49,364
精华内容 19,745
关键字:

一切皆文件

友情链接: GPS库文件proteus.zip