《追踪linux tcp/ip代码运行》--网络文件系统的初始化

 《追踪linux tcp/ip代码运行》这本书有些地方作者写的比较简单,需要对linux相关模块实现原理了解,否则不太好理解。估计是由于篇幅有限,作者只能点到为止。
  最近把linux文件系统相关的学习了一下,感觉收获还是比较大。推荐两本新手学习内核代码比较好的书籍《linux内核情景分析》和《独辟蹊径品内核》,当然任何书都不是完美的,这两本书只能说对我这种菜鸟帮助非常大!学习代码时首先不要细扣,先了解代码大致的结构和流程即可,后续如果需要可以深入研究。linux代码错综复杂,当开始一味追求全部搞明白的时候,估计就会失去兴趣了,难免不抓狂。我反正是这样子!^_^

那么,网络文件系统是如何初始化的?
初始化的入口在下面的函数:

core_initcall(sock_init); /* early initcall */
#define core_initcall(fn) __define_initcall("1",fn,1)
#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn


前面写过一篇模块初始化函数module_init(),core_initcall(fn)和module_init函数类似,都是在编译时将定义的函数fn放到指定初始化的section中(系统初始化时即会执行这些函数)。
上面的宏即定义了一个__initcall_sock_init_1变量,这个变量是initcall_t 类型,typedef int (*initcall_t)(void);它是一个函数指针,即是一个函数,而这个变量被赋值为fn,即sock_init。


那么继续看这个函数:
static int __init sock_init(void)
{
/*
*      Initialize sock SLAB cache.
*/


sk_init();


/*
*      Initialize skbuff SLAB cache
*/
skb_init();


/*
*      Initialize the protocols module.
*/


init_inodecache();
register_filesystem(&sock_fs_type);//注册网络文件系统
sock_mnt = kern_mount(&sock_fs_type);//“挂载”网络文件系统


/* The real protocol initialization is performed in later initcalls.
*/


#ifdef CONFIG_NETFILTER
netfilter_init();
#endif


return 0;
}




主要看网络文件系统的注册和挂载:
任何文件系统在mount时首先都会去注册该文件系统,linux内核将所有挂载的文件系统都注册到全局变量file_systems中(依靠成员next组成单链表):
static struct file_system_type *file_systems;

int register_filesystem(struct file_system_type * fs)
{
int res = 0;
struct file_system_type ** p;


BUG_ON(strchr(fs->name, '.'));
if (fs->next)
return -EBUSY;
INIT_LIST_HEAD(&fs->fs_supers);
write_lock(&file_systems_lock);
p = find_filesystem(fs->name, strlen(fs->name));
if (*p)
res = -EBUSY;//该文件系统已经注册
else
*p = fs;//未注册,则在链表结尾将该文件系统链接上去
write_unlock(&file_systems_lock);
return res;
}


static struct file_system_type **find_filesystem(const char *name, unsigned len)
{
struct file_system_type **p;
for (p=&file_systems; *p; p=&(*p)->next)//在全局链表中按照文件系统的名字找该文件系统是否已经注册
if (strlen((*p)->name) == len &&
   strncmp((*p)->name, name, len) == 0)
break;
return p;
}





网络文件系统利用kern_mount函数进行挂载,具体调用流程为:
#define kern_mount(type) kern_mount_data(type, NULL)
struct vfsmount *kern_mount_data(struct file_system_type *type, void *data)
{
return vfs_kern_mount(type, MS_KERNMOUNT, type->name, data);
}


struct vfsmount *
vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)
{
struct vfsmount *mnt;
char *secdata = NULL;
int error;

if (!type)
return ERR_PTR(-ENODEV);

error = -ENOMEM;
mnt = alloc_vfsmnt(name);//每个文件系统挂载都会有一个对应的vfsmount结构,结构主要包含了挂载点、父文件系统挂载点等,后续代码中会有对成员的填充
if (!mnt)
goto out;
      
        省略若干代码
 error = type->get_sb(type, flags, name, data, mnt);//生成vfs对应的super_block结构,调用的函数为sockfs_get_sb
if (error < 0)
goto out_free_secdata;
BUG_ON(!mnt->mnt_sb);
省略若干代码
mnt->mnt_mountpoint = mnt->mnt_root;
mnt->mnt_parent = mnt;
up_write(&mnt->mnt_sb->s_umount);

}


static struct file_system_type sock_fs_type = {
.name = "sockfs",
.get_sb = sockfs_get_sb,
.kill_sb = kill_anon_super,
};




网络文件系统对应的file_system_type结构中的get_sb函数为 sockfs_get_sb,这个函数会对网络文件系统的super_block、inode、dentry结构进行构建填充。。

static int sockfs_get_sb(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data,
struct vfsmount *mnt)
{
return get_sb_pseudo(fs_type, "socket:", &sockfs_ops, SOCKFS_MAGIC,
    mnt);
}
int get_sb_pseudo(struct file_system_type *fs_type, char *name,
const struct super_operations *ops, unsigned long magic,
struct vfsmount *mnt)
{
struct super_block *s = sget(fs_type, NULL, set_anon_super, NULL);
struct dentry *dentry;
struct inode *root;
struct qstr d_name = {.name = name, .len = strlen(name)};

if (IS_ERR(s))
return PTR_ERR(s);

s->s_flags = MS_NOUSER;
s->s_maxbytes = ~0ULL;
s->s_blocksize = 1024;
s->s_blocksize_bits = 10;
s->s_magic = magic;
s->s_op = ops ? ops : &simple_super_operations;
s->s_time_gran = 1;
root = new_inode(s);//调用sock_alloc_inode创建vfs的根inode节点


if (!root)
goto Enomem;
/*
* since this is the first inode, make it number 1. New inodes created
* after this must take care not to collide with it (by passing
* max_reserved of 1 to iunique).
*/
root->i_ino = 1;
root->i_mode = S_IFDIR | S_IRUSR | S_IWUSR;
root->i_uid = root->i_gid = 0;
root->i_atime = root->i_mtime = root->i_ctime = CURRENT_TIME;
dentry = d_alloc(NULL, &d_name);
if (!dentry) {
iput(root);
goto Enomem;
}
dentry->d_sb = s;
dentry->d_parent = dentry;
d_instantiate(dentry, root);
s->s_root = dentry;
s->s_flags |= MS_ACTIVE;
return simple_set_mnt(mnt, s);
Enomem:
up_write(&s->s_umount);
deactivate_super(s);
return -ENOMEM;
}


static struct super_operations sockfs_ops = {
.alloc_inode =sock_alloc_inode,
.destroy_inode =sock_destroy_inode,
.statfs = simple_statfs,
};


static struct inode *sock_alloc_inode(struct super_block *sb)
{
struct socket_alloc *ei;//在网络文件系统中分配inode节点时,会有一个新的结构体socket_alloc,这个结构体包含了一个inode结构和一个socket结构,到此为止,我们终于将网络文件系统和socket这个结构体关联上了。
ei = kmem_cache_alloc(sock_inode_cachep, GFP_KERNEL);
if (!ei)
return NULL;
init_waitqueue_head(&ei->socket.wait);

ei->socket.fasync_list = NULL;
ei->socket.state = SS_UNCONNECTED;
ei->socket.flags = 0;
ei->socket.ops = NULL;
ei->socket.sk = NULL;
ei->socket.file = NULL;
return &ei->vfs_inode;
}


struct socket_alloc {
struct socket socket;
struct inode vfs_inode;
};

在上述sock_alloc_inode中,我们看到了socket结构体的分配是与inode节点的分配息息相关的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 追踪Linux TCP/IP代码运行的过程,可以通过以下几个步骤实施: 首先,需要获得Linux内核的源代码,并找到TCP/IP协议栈的相关代码文件。这些文件通常位于“net”目录下,涉及到TCP/IP的部分大多在“net/ipv4”或“net/ipv6”目录下。 然后,我们可以选择使用调试器(如GDB)或者打印日志的方式来跟踪代码运行。如果选择使用调试器,可以在代码中设置断点,观察程序在断点处的执行情况,查看变量的值和函数的调用关系。如果使用打印日志的方式,可以在关键的代码段插入打印语句,输出相关变量的值和执行路径。 接着,我们需要了解TCP/IP协议栈的基本工作原理。具体来说,可以关注TCP的连接建立、数据传输和连接关闭等过程,以及IP协议的路由选择和分组转发等操作。 在追踪过程中,可以选择从应用层开始,如HTTP请求的发送和响应的接收。通过查看应用层的调用栈以及涉及的网络函数,可以逐步跟进到TCP/IP协议栈的代码中。同时,还可以通过观察网络数据包的发送和接收过程来了解底层数据在协议栈中的处理过程。 在跟踪代码的过程中,需要仔细观察代码中的注释和文档,以及相关的技术文档和论文,以便理解代码的设计思路和实现细节。 最后,可以通过调试器或分析日志的结果来验证自己对代码运行过程的理解,并进行必要的修复和改进。 总之,追踪Linux TCP/IP代码的运行是一个复杂而具有挑战性的任务,需要对Linux内核和网络协议有一定的了解和熟悉。同时,需要具备一定的编程和调试能力,以便在代码中找到关键的位置,并通过调试工具或日志信息来进行代码分析。 ### 回答2: 要追踪Linux TCP/IP代码运行,可以按照以下步骤进行: 1. 首先,了解TCP/IP协议栈的基本原理和相关概念,包括网络层、传输层和应用层的功能和协议。 2. 掌握Linux内核开发的基础知识,包括Linux内核的编译、调试和运行环境的搭建。 3. 下载并编译Linux内核源代码,可以从官方网站或开源代码库获取最新的Linux内核源码。 4. 在源代码中找到相关的TCP/IP代码,可以通过在源代码中搜索关键字或使用开发工具进行定位。 5. 使用调试工具来分析和追踪代码的运行。Linux内核提供了一些调试工具如Ftrace、Kprobes和SystemTap等,可以用于监视和分析内核函数的调用和运行过程。 6. 添加自定义的调试输出或断点来获取更详细的代码执行信息。通过在代码中插入printk语句或断点来观察特定变量的值或代码执行路径。 7. 使用网络抓包工具来捕获和分析网络数据包,可以验证TCP/IP代码的正确性和性能。 8. 进行测试和验证,使用各种测试工具和场景来验证TCP/IP代码的正确性和性能。 9. 探索并阅读现有的文档、论文和开发者社区的讨论,可以从中获取更多关于Linux TCP/IP代码的运行和调试技巧。 总之,追踪Linux TCP/IP代码运行需要深入理解 TCP/IP协议栈的原理,掌握Linux内核开发的基础知识,使用调试工具和网络抓包工具来分析和验证代码的运行。同时,与开发者社区保持联系,探索和学习他人的经验和技巧。 ### 回答3: 要追踪Linux TCP/IP代码的运行,首先需要一个源代码的PDF文件。我们可以从官方Linux内核网站上下载相关的源码文件,然后将其导出为PDF格式。 追踪代码运行的第一步是了解整体的调用流程。打开PDF文件,可以从文件的目录中找到TCP/IP模块的相关代码。这些文件包括传输层代码(如tcp.c)和网络层代码(如ip.c),它们负责TCP/IP协议的实现。 在PDF文件中,我们可以使用搜索功能找到特定的函数名或关键字。比如,如果希望追踪TCP的建立过程,可以搜索"tcp_accept"函数。找到这个函数后,可以查看它的调用关系,看它在哪些地方被调用,以及它又调用了哪些函数。 在追踪过程中,要注意一些关键的数据结构,例如TCP控制块(TCB)和IP包头。这些结构中包含了关键的信息,如IP地址、端口号和序列号等。可以通过搜索这些结构的定义,并在代码中追踪它们的使用。 另外,还可以使用调试工具来加速追踪的过程。在Linux系统中,可以使用GDB(GNU调试器)来逐步执行代码,并观察变量的值。这可以帮助我们更直观地理解代码的执行流程。 追踪Linux TCP/IP代码的运行需要耐心和坚持,需要仔细阅读和理解大量的代码。通过结合源代码的PDF文件和调试工具的使用,我们可以更好地理解代码的逻辑和执行过程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值