Linux 内核源码中likely()和unlikely()

在Linux内核源码中,likely()和unlikely()宏用于优化条件判断的执行路径。它们结合了__builtin_expect()编译器指令,通过预测表达式的布尔结果来指导编译器进行更好的指令调度。例如,在if语句中,如果a通常大于b,使用likely(a>b)可以提示编译器优先考虑执行fun1()。
ikely()与unlikely()在2.6内核中,随处可见,那为什么要用它们?它们之间有什么区别呢?

首先明确:

if (likely(value))等价于if (value)
if (likely(a>b)) {
fun1();

if (unlikely(value))等价于if (value)

也就是说likely()和unlikely()从阅读和理解的角度是一样的。

这两个宏在内核中定义如下:
  1. <linux/compiler>

  2. #define likely(x) __builtin_expect(!!(x), 1)

  3. #define unlikely(x) __builtin_expect(!!(x), 0)
复制代码
这里的__built_expect()函数是gcc(version >= 2.96)的内建函数,提供给程序员使用的,目的是将"分支转移"的信

息提供给编译器,这样编译器对代码进行优化,以减少指令跳转带来的性能下降。

__buildin_expect((x), 1)表示x的值为真的可能性更大。

__buildin_expect((x), 0)表示x的值为假的可能性更大。

也就是说,使用likely(),执行if后面的语句的机会更大,使用unlikely(),执行else后面的语句机会更大一些。通过这种

方式,编译器在编译过程中,会将可能性更大的代码紧跟着后面的代码,从而减少指令跳转带来的性能上的下降。

比如 :
  1. if (likely(a>b)) {

  2. fun1();

  3. }
复制代码
这里就是程序员可以确定 a>b 在程序执行流程中出现的可能相比较大,因此运用了likely()告诉编译器将fun1()函数

的二进制代码紧跟在前面程序的后面,这样就cache在预取数据时就可以将fun1()函数的二进制代码拿到cache中。

这样,也就添加了cache的命中率。

同样的,unlikely()的作用就是告诉编译器,a<b 的可能性很小所以这里在编译时,将fun2()的二进制代码尽量

不要和前边的编译在一块。咱们不用对likely和unlikely感到迷惑,须要知晓的就是 if(likely(a>b)) 和 if(a>b)在功能

上是等价的,同样 if(unlikely(a<b)) 和 if(a<b) 的功能也是一样的。不一样的只是他们声称的二进制代码有所不一

样,这一点咱们也可以从他们的汇编代码中看到。总之,likely和unlikely的功能就是添加 cache的命中率,提高系统

执行速度。
<think>我们讨论的是Linux内核3.10.14版本中close系统调用的内核执行流程。 在Linux中,close系统调用用于关闭一个文件描述符。它的调用流程大致如下: 1. 用户空间调用close(fd) -> 触发系统调用,进入内核空间。 2. 内核中,系统调用入口(如x86架构下使用syscall指令)会根据系统调用号(__NR_close)找到对应的处理函数。 3. 在Linux 3.10.14中,close系统调用的处理函数是sys_close()。 具体流程: 用户态调用close(fd) -> 系统调用陷入内核 -> 调用sys_close() -> 调用filp_close() -> 调用fput() -> 如果文件引用计数降为0,则调用__fput() -> 最终调用文件操作表中的release方法(即file_operations->release)。 详细步骤: - sys_close() 函数位于fs/open.c中。 - sys_close() 会调用filp_close(),然后调用fput()。 - 当文件的引用计数变为0时,会调用__fput(),在这个函数中会调用具体文件系统的release方法(通过file->f_op->release)。 因此,最终调用的函数是file_operations结构体中的release函数指针,这个函数指针根据文件类型(例如普通文件、套接字、管道等)而不同。 例如: - 对于普通文件,release方法通常是fuse_file_release(如果使用FUSE)或者ext4_file_release(如果使用ext4)等。 - 对于套接字,release方法指向sock_close。 所以,close系统调用最终会调用到具体文件系统或设备驱动中定义的release函数。 下面给出一个简化的调用链: 用户空间: close(fd) 内核空间: SYSCALL_DEFINE1(close, unsigned int, fd) -> struct file *file = file_close(fd); // 实际上,在sys_close中直接调用了filp_close -> filp_close(file, current->files) -> fput(file) -> if (file_count(file) == 0) 进入__fput -> __fput(file) -> file->f_op->release(inode, file) 注意:在fput()中,如果引用计数不为0,则只是减少引用计数,不会调用release。 因此,最终调用的内核函数是file->f_op->release(inode, file)。 但是,我们需要知道,这个release函数指针是动态绑定的,取决于文件描述符所对应的文件类型。 示例代码(内核源码片段,来自Linux 3.10.14): fs/open.c: ``` SYSCALL_DEFINE1(close, unsigned int, fd) { int retval = __close_fd(current->files, fd); /* can't restart close syscall because file table entry was cleared */ if (unlikely(retval == -ERESTARTSYS)) retval = -EINTR; return retval; } ``` 但是,实际上__close_fd会调用filp_close,然后调用fput。而filp_close也在fs/open.c中: ``` int filp_close(struct file *filp, fl_owner_t id) { int retval = 0; if (!file_count(filp)) { printk(KERN_ERR "VFS: Close: file count is 0\n"); return 0; } if (filp->f_op && filp->f_op->flush) retval = filp->f_op->flush(filp, id); fput(filp); return retval; } ``` 而fput()定义在fs/file_table.c中: ``` void fput(struct file *file) { if (atomic_long_dec_and_test(&file->f_count)) { struct task_struct *task = current; if (likely(!in_interrupt() && !(task->flags & PF_KTHREAD))) { init_task_work(&file->f_u.fu_rcuhead, ____fput); if (!task_work_add(task, &file->f_u.fu_rcuhead, true)) return; } ____fput(&file->f_u.fu_rcuhead); } } ``` 在____fput中: ``` static void ____fput(struct callback_head *work) { __fput(container_of(work, struct file, f_u.fu_rcuhead)); } ``` 然后,在__fput中: ``` void __fput(struct file *file) { struct dentry *dentry = file->f_path.dentry; struct vfsmount *mnt = file->f_path.mnt; struct inode *inode = file->f_inode; might_sleep(); fsnotify_close(file); /* * The function eventpoll_release() should be the first called * in the file cleanup chain. */ eventpoll_release(file); locks_remove_file(file); if (unlikely(file->f_flags & FASYNC)) { if (file->f_op && file->f_op->fasync) file->f_op->fasync(-1, file, 0); } if (file->f_op && file->f_op->release) file->f_op->release(inode, file); security_file_free(file); if (unlikely(S_ISCHR(inode->i_mode) && inode->i_cdev != NULL)) cdev_put(inode->i_cdev); fops_put(file->f_op); put_pid(file->f_owner.pid); file_sb_list_del(file); if (file->f_mode & FMODE_WRITE) drop_file_write_access(file); file->f_path.dentry = NULL; file->f_path.mnt = NULL; file->f_inode = NULL; file_free(file); dput(dentry); mntput(mnt); } ``` 从上面的代码可以看到,在__fput函数中,会调用file->f_op->release(inode, file)。 因此,总结:close系统调用最终会调用具体文件操作表(file_operations)中定义的release函数。 注意:不同的文件系统、设备类型,其release函数不同。例如,对于ext4文件系统,release函数可能是ext4_release_file(在fs/ext4/file.c中定义);对于socket,则是sock_close(在net/socket.c中定义)。 所以,我们不能说一个具体的函数名,而是一个函数指针,它指向具体文件对应的release函数。 但是,我们可以说,在close的内核调用流程中,最终会调用到file_operations->release函数。 因此,回答的重点在于:close系统调用最终会调用具体文件操作表(file_operations)中的release函数。 根据以上分析,我们给出回答。</think>### Linux 3.10.14内核中close()系统调用流程分析 #### close()系统调用完整调用链 ```mermaid graph TD A[用户空间 close(fd)] --> B[系统调用入口] B --> C[sys_close] C --> D[__close_fd] D --> E[filp_close] E --> F[fput] F --> G[__fput] G --> H[file->f_op->release] ``` #### 关键函数解析 1. **sys_close()** (位于`fs/open.c`) ```c SYSCALL_DEFINE1(close, unsigned int, fd) { int retval = __close_fd(current->files, fd); // 处理错误中断 return retval; } ``` 2. **__close_fd()** (位于`fs/file.c`) ```c int __close_fd(struct files_struct *files, unsigned fd) { struct file *file; file = pick_file(files, fd); // 获取文件对象 return filp_close(file, files); // 继续关闭流程 } ``` 3. **filp_close()** (位于`fs/open.c`) ```c int filp_close(struct file *filp, fl_owner_t id) { // 刷新数据缓冲区 if (filp->f_op->flush) filp->f_op->flush(filp, id); // 核心释放操作 fput(filp); return 0; } ``` 4. **fput()** (位于`fs/file_table.c`) ```c void fput(struct file *file) { if (atomic_long_dec_and_test(&file->f_count)) { // 引用计数减1 struct task_struct *task = current; // 异步处理机制 ____fput(file); // 最终释放 } } ``` #### 最终调用函数 **核心释放函数**:`____fput()` → `__fput()` (位于`fs/file_table.c`) ```c static void __fput(struct file *file) { struct dentry *dentry = file->f_path.dentry; struct vfsmount *mnt = file->f_path.mnt; struct inode *inode = file->f_inode; // 关键步骤:调用文件操作表的release方法 if (file->f_op && file->f_op->release) file->f_op->release(inode, file); // ⭐ 最终调用点 ⭐ // 释放相关资源 security_file_free(file); if (unlikely(file->f_flags & FASYNC)) { if (file->f_op->fasync) file->f_op->fasync(-1, file, 0); } file_sb_list_del(file); file_free(file); dput(dentry); mntput(mnt); } ``` #### 文件类型对应的release函数 | 文件类型 | release函数 | 源码位置 | |---------------|-----------------------------|---------------------| | 普通文件 | `ext4_release_file()` | `fs/ext4/file.c` | | 套接字(Socket) | `sock_close()` | `net/socket.c` | | 管道(Pipe) | `pipe_release()` | `fs/pipe.c` | | 字符设备 | `chrdev_release()` | `fs/char_dev.c` | | TTY终端 | `tty_release_dev()` | `drivers/tty/tty_io.c` | #### 关键数据结构 ```c struct file_operations { // ... int (*release) (struct inode *, struct file *); // ... }; ``` #### 执行流程特点 1. **引用计数机制**:通过`f_count`管理文件对象的生命周期 2. **异步释放**:使用`task_work_add()`实现安全异步释放 3. **类型特定操作**:通过`file_operations`函数表实现多态 4. **资源清理**:依次释放dentry、inode、vfsmount等关联资源 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值