http://www.cppblog.com/qinqing1984/archive/2015/05/03/210521.html

引言
   在Unix的世界里,万物皆文件,通过虚拟文件系统VFS,程序可以用标准的Unix系统调用对不同的文件系统,甚至不同介质上的文件系统进行读写操作。对于网络套接字socket也是如此,除了专属的Berkeley Sockets API,还支持一些标准的文件IO系统调用如read(v)、write(v)和close等。那么为什么socket也支持文件IO系统调用呢?在Linux上,这是通过套接口伪文件系统sockfs来实现的,因为sockfs实现了VFS中的4种主要对象:超级块super block、索引节点inode、目录项对象dentry和文件对象file,当执行文件IO系统调用时,VFS就将请求转发给sockfs,而sockfs就调用特定的协议实现,层次结构如下图:

本文以linux 2.6.34实现为基础,本篇阐述初始化和Socket创建两部分的实现,下篇阐述Socket操作和销毁两部分的实现。

初始化


在内核引导时初始化网络子系统,进而调用sock_init,该函数主要步骤如下:创建inode缓存,注册和装载sockfs,定义在net/socket.c中。

1static int __init sock_init(void)
2{
3    
4    init_inodecache();
5    register_filesystem(&sock_fs_type);
6    sock_mnt = kern_mount(&sock_fs_type);
7    
8}

创建inode缓存
   init_inodecache为socket_alloc对象创建SLAB缓存,名称为sock_inode_cachep,socket_alloc定义在include/net/sock.h中。

1struct socket_alloc {
2    struct socket socket;
3    struct inode vfs_inode;
4};
   socket_alloc由socket和inode结构2部分组成,这样就方便了在套接字与inode对象间双向定位。

注册sockfs
   调用VFS的函数register_filesystem实现注册,sock_fs_type定义在net/socket.c中。

1static struct file_system_type sock_fs_type = {
2    .name =        "sockfs",
3    .get_sb =    sockfs_get_sb,
4    .kill_sb =    kill_anon_super,
5};

sock_fs_type包含了文件系统sockfs的名称、创建和销毁super block的函数,其中sockfs_get_sb实现在net/socket.c中。

1static int sockfs_get_sb(struct file_system_type *fs_type,int flags, const char *dev_name, void *data,struct vfsmount *mnt)
2{
3    return get_sb_pseudo(fs_type, "socket:", &sockfs_ops, SOCKFS_MAGIC, mnt);
4}

它在kern_mount内被执行,通过调用get_sb_pseudo创建了一个super block(包含一个对应dentry及一个关联inode):操作对象为sockfs_ops,根目录名称为socket:,对应的根索引节点编号为1。
   sockfs_ops定义在net/socket.c中。

1static const struct super_operations sockfs_ops = {
2    .alloc_inode =    sock_alloc_inode,
3    .destroy_inode = sock_destroy_inode,
4    .statfs =    simple_statfs,
5};

sock_alloc_inode用于分配inode对象,将在socket创建过程中被调用;sock_destroy_inode用于释放inode对象,将在socket销毁过程中被调用;simple_statfs用于获取sockfs文件系统的状态信息。
   
   装载sockfs
   由kern_mount函数实现装载一个伪文件系统(当然,它没有装载点),返回一个static vfsmount对象sock_mnt。

经过以上步骤后,所创建的VFS对象关系如下图:

对于根目录项,不用进行路径转换,因此dentry的d_op为空(未画出);对于伪文件系统,操作索引对象没有意义,所以inode的i_op为空(未画出)。

Socket创建


系统调用socket、accept和socketpair是用户空间创建socket的几种方法,其核心调用链如下图:

从上图可知共同的核心就3个过程:先构造inode,再构造对应的file,最后安装file到当前进程中(即关联映射到一个未用的文件描述符),下面就这3个过程进行详细说明。

构造inode
   由sock_alloc函数实现,定义在net/socket.c中。

 1static struct socket *sock_alloc(void)
 2{
 3    struct inode *inode;
 4    struct socket *sock;
 5
 6    inode = new_inode(sock_mnt->mnt_sb);
 7        
 8    sock = SOCKET_I(inode);
 9            
10    inode->i_mode = S_IFSOCK | S_IRWXUGO;
11    inode->i_uid = current_fsuid();
12    inode->i_gid = current_fsgid();
13        
14    return sock;
15}

先调用new_inode创建inode对象,再设置它的类型为S_IFSOCK,由此可知inode对应的文件类型为套接字。new_inode是文件系统的一个接口函数,用于创建一个inode对象,定义在fs/inode.c中,它调用了sockfs超级块的操作对象即sockfs_ops的sock_alloc_inode方法,由于sock_alloc_inode实际创建的是socket_alloc复合对象,因此要使用SOCKET_I宏从inode中取出关联的socket对象用于返回。

   构造file
   有了inode对象后,接下来就要构造对应的file对象了,由sock_alloc_file实现,定义在net/socket.c中。

 1static int sock_alloc_file(struct socket *sock, struct file **f, int flags)
 2{
 3    struct qstr name = { .name = "" };
 4    struct path path;
 5    struct file *file;
 6    int fd;
 7
 8    fd = get_unused_fd_flags(flags);
 9        
10    path.dentry = d_alloc(sock_mnt->mnt_sb->s_root, &name);
11        
12    path.mnt = mntget(sock_mnt);
13
14    path.dentry->d_op = &sockfs_dentry_operations;
15    d_instantiate(path.dentry, SOCK_INODE(sock));
16    SOCK_INODE(sock)->i_fop = &socket_file_ops;
17
18    file = alloc_file(&path, FMODE_READ | FMODE_WRITE, &socket_file_ops);
19    
20    sock->file = file;
21    file->f_flags = O_RDWR | (flags & O_NONBLOCK);
22    file->f_pos = 0;
23    file->private_data = sock;
24
25    *f = file;
26    return fd;
27}

sock为上一过程返回的套接字对象,该函数主要做了以下几件事:
   1)得到空闲的文件描述符fd,实际上就是fd数组的索引,准备作为返回值。
   2)先初始化路径path:其目录项的父目录项为超级块对应的根目录,名称为空,操作对象为sockfs_dentry_operations,对应的索引节点对象为sock套接字关联的索引节点对象,即SOCK_INODE(sock);装载点为sock_mnt。  
   sockfs_dentry_operations定义在net/socket.c中。

1static const struct dentry_operations sockfs_dentry_operations = {
2    .d_dname  = sockfs_dname,
3};

sockfs_dname会被d_path调用,用于计算socket对象的目录项名称。
   3)设置索引节点的文件操作对象为socket_file_ops,定义在net/socket.c中。

1static const struct file_operations socket_file_ops = {
2    
3    .aio_read =    sock_aio_read,
4    .aio_write =    sock_aio_write,
5    
6    .open =        sock_no_open,    /* special open code to disallow open via /proc */
7    .release =    sock_close,
8    
9};

4)调用alloc_file,以path和socket_file_ops为输入参数,这样返回得到的file便与sock的inode关联上了,并且操作对象为socket_file_ops,最后设置到输出参数f中。
   5)建立file与socket的一一映射关系。
   
   安装file
   由fd_install实现,定义在fs/open.c中。

 1void fd_install(unsigned int fd, struct file *file)
 2{
 3    struct files_struct *files = current->files;
 4    struct fdtable *fdt;
 5    spin_lock(&files->file_lock);
 6    fdt = files_fdtable(files);
 7    BUG_ON(fdt->fd[fd] != NULL);
 8    rcu_assign_pointer(fdt->fd[fd], file);
 9    spin_unlock(&files->file_lock);
10}

fd和file分别为上一过程返回的空闲文件描述符和文件对象,使RCU技术来设置file到当前进程的fd数组中。
 
   经过以上过程后,所创建的VFS对象关系图如下

fd为file*数组的索引而不是成员字段;vfsmount与初始化之VFS对象关系图中的vfsmount是同一个对象,即sock_mnt;对于伪文件系统,操作索引对象没有意义,所以inode的i_op为空(未画出)。

Linux套接字与虚拟文件系统(1):初始化和创建的更多相关文章

  1. 使用 /proc 文件系统来访问 linux操作系统 内核的内容 && 虚拟文件系统vfs及proc详解

    http://blog.163.com/he_junwei/blog/static/19793764620152743325659/ http://www.01yun.com/other/201304 ...

  2. Linux套接字和I/O模型

    目录 1.       socket套接字的属性.地址和创建 2.       如何使用socket编写简单的同步阻塞的服务器/客户端 3.       理解Linux五种I/O模型 1.socket ...

  3. 【 Linux 】Linux套接字简要说明

    Linux套接字    源IP地址和目的IP地址以及源端口和目标端口号的组合称为套接字.其作用于标识客户端请求的服务器和服务. 套接字,支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间 ...

  4. linux 套接字编程入门--Hello World

    下述代码是linux套接字编程的入门代码.分为服务端和客户端源码. 服务端代码的主要流程是绑定ip地址和端口号建立套接字,等待客户端发起访问.接受客户端请求之后,向客户端发送字符串"hell ...

  5. Linux 套接字编程中的 5 个隐患(转)

    本文转自IBM博文Linux 套接字编程中的 5 个隐患. “在异构环境中开发可靠的网络应用程序”. Socket API 是网络应用程序开发中实际应用的标准 API.尽管该 API 简单,但是开发新 ...

  6. socket - Linux 套接字

    总览 #include <sys/socket.h> mysocket = socket(int socket_family, int socket_type, int protocol) ...

  7. Linux 套接字通信笔记(一)

    协议 TCP(传输控制协议),UDP(用户数据包协议)为传输层重要的两大协议,向上为HTTP提供底层协议,向下为数据链路层封装底层接口,乃是通信重中之重.TCP是面向流传输的协议,在编程中形象化为St ...

  8. (笔记)Linux内核学习(十)之虚拟文件系统概念

    虚拟文件系统 虚拟文件系统:内核子系统VFS,VFS是内核中文件系统的抽象层,为用户空间提供文件系统相关接口: 通过虚拟文件系统,程序可以利用标准Linux文件系统调用在不同的文件系统中进行交互和操作 ...

  9. (转载)Linux 套接字编程中的 5 个隐患

    在 4.2 BSD UNIX® 操作系统中首次引入,Sockets API 现在是任何操作系统的标准特性.事实上,很难找到一种不支持 Sockets API 的现代语言.该 API 相当简单,但新的开 ...

随机推荐

  1. Lab_1:练习4——分析bootloader加载ELF格式的OS的过程

    一.实验内容 通过阅读bootmain.c,了解bootloader如何加载ELF文件.通过分析源代码和通过qemu来运行并调试bootloader&OS, bootloader如何读取硬盘扇 ...

  2. SpringBoot扩展点之三:SpringBootServletInitializer扩展

    SpringBootServletInitializer 熟悉了SpringApplication的原理之后,我们再来了解SpringBootServletInitializer的原理就比较容易了. ...

  3. 使用Clion优雅的完全远程自动同步和远程调试c++

    摘要:在linux上用vim写C++的时候,通常用gdb进行调试,不能随心所欲的看代码和跳转代码以及加watch(也有可能是因为我还没有get正确的使用方法).为此我发现Clion可以做到自动同步本场 ...

  4. 不一样的LCA——luoguP1852跳跳棋

    洛谷端题目链接 loj端题目链接 题目大意: 在一条数轴上进行跳跳棋游戏.棋子只能摆在整点上.每个点不能摆超过一个棋子.用跳跳棋完成:棋盘上有3颗棋子,分别在a,b,c这三个位置.我们要通过最少的跳动 ...

  5. GoCN每日新闻(2019-09-23)

    1. 查看 Go 的代码优化过程http://xargin.com/go-compiler-opt 2. go 学习笔记之仅仅需要一个示例就能讲清楚什么闭包 https://segmentfault. ...

  6. 用LabVIEW实现《家国梦》游戏中自动收货收金币

    前几周,很流行一个手机游戏,叫<家国梦>.我在手机上玩了一两天后,发现了两个问题: 1. 收货收金币太没意思,只需要手指滑在固定的路线,一会儿就烦了 2. 怎么追赶也追不上手快的人(当然也 ...

  7. [转] js async await 终极异步解决方案

    阅读目录 回顾 Promise async await 字面理解 async.await 如何执行 await 操作符 总结 既然有了promise 为什么还要有async await ? 当然是pr ...

  8. lnmp1.4安装包

    https://lnmp.org/install.html nginx中虚拟机中的配置 location ~ .*\.(php|php5)?$ { try_files $uri =404; fastc ...

  9. 网络编程中用到的SOCKET是什么?

    摘取网络上的一些内容: 什么是TCP/IP.UDP? Socket在哪里呢? Socket是什么呢? 什么是TCP/IP.UDP? TCP/IP(Transmission Control Protoc ...

  10. 23、vue实现获取短信验证码

    1.html页面: <el-form-item prop="phoneCode" class="pr"> <el-input placehol ...