linux内核中的文件描述符 四 --fd的分配--get unused fd

本文详细解析了Linux内核中get_unused_fd函数的工作原理,该函数用于在进程的文件描述符表中查找并分配一个未使用的文件描述符。文章深入介绍了文件描述符位图、RLIMIT_NOFILE限制及fd的分配流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

               

linux内核中的文件描述符(四)--fd的分配--get_unused_fd

Kernel version:2.6.14

CPU architecture:ARM920T

Author:ce123(http://blog.youkuaiyun.com/ce123)

 在linux内核中主要有两个函数涉及到文件描述符的分配:get_unused_fd和locate_fd。本文主要讲解get_unused_fd,将会在下一篇文章中介绍locate_fd。首先给出get_unused_fd的定义(fs/open.c):

int get_unused_fd(void){ struct files_struct * files = current->files;//获得当前进程的打开文件列表files int fd, error; struct fdtable *fdt;   error = -EMFILE; spin_lock(&files->file_lock);repeat: fdt = files_fdtable(files);//获得文件描述符位图结构  fd = find_next_zero_bit(fdt->open_fds->fds_bits,    fdt->max_fdset,    fdt->next_fd);//find_next_zero_bit函数在文件描述符位图fds_bits中从next_fd位开始搜索下一个(包括next_fd)为0的位,也就是分配一个文教描述符 /*  * N.B. For clone tasks sharing a files structure, this test  * will limit the total number of files that can be opened.  */ if (fd >= current->signal->rlim[RLIMIT_NOFILE].rlim_cur)//检查是否超过当前进程限定的最大可打开文件数  goto out; /* Do we need to expand the fd array or fd set?  */ error = expand_files(files, fd);//根据需要扩展fd,稍后我们会详细介绍该函数。返回值<0,错误;返回值>0,扩展后再次进行fd的分配 if (error < 0)  goto out; if (error) {  /*    * If we needed to expand the fs array we   * might have blocked - try again.   */  error = -EMFILE;  goto repeat;//之前进行了扩展操作,重新进行一次空闲fd的分配 } FD_SET(fd, fdt->open_fds);//在open_fds的位图上置位 FD_CLR(fd, fdt->close_on_exec); fdt->next_fd = fd + 1;//next_fd加1#if 1 /* Sanity check */ if (fdt->fd[fd] != NULL) {  printk(KERN_WARNING "get_unused_fd: slot %d not NULL!\n", fd);  fdt->fd[fd] = NULL; }#endif error = fd;out: spin_unlock(&files->file_lock); return error;}

current->signal->rlim[RLIMIT_NOFILE].rlim_cur是一个进程可以打开的最大文件数量。我们首先来看RLIMIT_NOFILE,该值定义如下:

# define RLIMIT_NOFILE  7 /* max number of open files */

在signal结构中,rlim是struct rlimit类型的数组,

struct signal_struct { ... struct rlimit rlim[RLIM_NLIMITS]; ... };
struct rlimit定义如下
struct rlimit { unsigned long rlim_cur;//当前值 unsigned long rlim_max;//最大值};

这些值时是在哪设定的呢?我们应该知道,linux内核通过fork创建进程,第一个进程是静态定义的。因此,如果进程创建后没有修改这些值,那么这些和第一个进程中的值应该是一样的。下面是第一个进程的task_struct结构,仅列出部分数据。

linux/arch/arm/kernel/init_task.cstruct task_struct init_task = INIT_TASK(init_task);#define INIT_TASK(tsk) \{         \ ... .signal  = &init_signals,    \ ...   }
init_signals的定义如下:

#define INIT_SIGNALS(sig) { \ .count  = ATOMIC_INIT(1),   \ .wait_chldexit = __WAIT_QUEUE_HEAD_INITIALIZER(sig.wait_chldexit),\ .shared_pending = {     \  .list = LIST_HEAD_INIT(sig.shared_pending.list), \  .signal =  {{0}}}, \ .posix_timers  = LIST_HEAD_INIT(sig.posix_timers),  \ .cpu_timers = INIT_CPU_TIMERS(sig.cpu_timers),  \ .rlim  = INIT_RLIMITS,     \}include\asm-generic\resource.h#define INIT_RLIMITS       \{         \ [RLIMIT_CPU]  = {  RLIM_INFINITY,  RLIM_INFINITY }, \ [RLIMIT_FSIZE]  = {  RLIM_INFINITY,  RLIM_INFINITY }, \ [RLIMIT_DATA]  = {  RLIM_INFINITY,  RLIM_INFINITY }, \ [RLIMIT_STACK]  = {       _STK_LIM,   _STK_LIM_MAX }, \ [RLIMIT_CORE]  = {              0,  RLIM_INFINITY }, \ [RLIMIT_RSS]  = {  RLIM_INFINITY,  RLIM_INFINITY }, \ [RLIMIT_NPROC]  = {              0,              0 }, \ [RLIMIT_NOFILE]  = {       INR_OPEN,       INR_OPEN }, \ [RLIMIT_MEMLOCK] = {    MLOCK_LIMIT,    MLOCK_LIMIT }, \ [RLIMIT_AS]  = {  RLIM_INFINITY,  RLIM_INFINITY }, \ [RLIMIT_LOCKS]  = {  RLIM_INFINITY,  RLIM_INFINITY }, \ [RLIMIT_SIGPENDING] = {   0,        0 }, \ [RLIMIT_MSGQUEUE] = {   MQ_BYTES_MAX,   MQ_BYTES_MAX }, \ [RLIMIT_NICE]  = { 0, 0 },    \ [RLIMIT_RTPRIO]  = { 0, 0 },    \}#define NR_OPEN (1024*1024) /* Absolute upper limit on fd num */#define INR_OPEN 1024  /* Initial setting for nfile rlimits */
从上面的代码我们可以看到rlim_cur = 1024,也就是说进程最多可以打开1024个文件。

           

再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.youkuaiyun.com/jiangjunshow

<think>嗯,用户现在问的是怎么将file结构体关联到进程的文件描述符表的。这个问题看起来是关于Linux内核文件描述符管理的机制。我需要先回顾一下之前讨论的内容,确保这次回答的一致性和准确性。 首先,用户之前已经询问过Linux应用程序打开设备时用户态和内核态的过程,我详细解释了用户态的系统调用触发和文件描述符分配,以及内核态的处理流程,包括VFS、驱动匹配、数据结构创建等。现在用户进一步深入,想知道具体的关联机制,也就是内核中的file结构如何与进程的文件描述符表绑定。 接下来,我需要确保自己对这个过程的理解正确。根据已有的知识,当用户调用open()系统调用时,内核会创建一个file结构体,记录文件的状态信息,比如读写位置、操作函数指针等。然后,这个file结构体需要与进程的文件描述符表关联,使得用户态可以通过返回的文件描述符(如fd=3)来操作这个文件。 关键点在于,每个进程都有一个文件描述符表,这个表是一个数组,索引就是文件描述符(整数),每个条目指向一个file结构体。当open()成功时,内核需要找到一个空闲的文件描述符,将新创建的file结构体指针存入对应的位置,然后返回这个描述符给用户。 但具体步骤是怎样的呢?可能需要涉及内核中的函数,比如get_unused_fd_flags()来分配一个空闲的fd,然后fd_install()将file结构体与fd关联。此外,还需要考虑文件描述符表的扩展,比如当当前表已满时,如何动态扩展数组的大小。 另外,权限检查和安全机制也需要提及,比如在分配fd之前,内核会检查进程是否有足够的权限打开文件,以及SELinux或AppArmor等安全模块的影响。 可能还需要解释文件描述符分配策略,比如总是选择最小的可用fd,这也是为什么关闭一个文件后,新打开的会使用最小的空闲fd的原因。 在组织回答时,应该分步骤说明,从系统调用进入内核,到分配fd,关联file结构体,最后返回给用户态。同时,需要指出相关的内核数据结构和函数,但不需要过于深入代码细节,保持解释的易懂性。 需要确保回答的结构清晰,可能分为几个步骤,比如分配文件描述符、创建file结构体、关联到描述符表等。同时,要强调用户态得到的fd实际上是一个索引,指向进程描述符表中的条目,而该条目指向内核中的file结构体。 还要注意可能出现的错误情况,比如文件描述符达到上限时的处理,或者权限不足的情况,但用户的问题主要关注成功的流程,所以可能不需要详细讨论错误处理。 最后,总结整个过程,强调用户态与内核态通过文件描述符和file结构体的关联实现文件操作的隔离和抽象。</think>在Linux内核中,将`file`结构体关联到进程的文件描述符表的过程是通过以下步骤实现的: --- ### **核心步骤** 1. **分配文件描述符** - 内核通过`get_unused_fd_flags(flags)`函数,在进程的**文件描述符表**中寻找一个**最小的可用整数**作为文件描述符(例如`3`)。 - 文件描述符表的本质是一个数组,索引是文件描述符(如`0,1,2,3...`),每个元素指向一个`struct file`。 2. **绑定`file`结构体** - 通过`fd_install(fd, file)`函数,将新创建的`struct file`指针存入文件描述符表的`fd`位置。 - 此时,用户态获得的`fd`成为操作该文件的句柄。 --- ### **详细过程** #### **1. 文件描述符表的本质** - 每个进程的`task_struct`(进程描述符)包含一个`files`字段(`struct files_struct *files`)。 - `files_struct`中有一个**文件描述符数组**(`struct fdtable`),其核心成员是`struct file **fd`指针数组。 例如: ```c struct files_struct { struct fdtable fdtab; // 包含文件描述符表 // ... }; struct fdtable { struct file **fd; // 文件描述符数组指针 unsigned int max_fds; // 当前数组容量 // ... }; ``` #### **2. 分配文件描述符的规则** - **最小可用原则**:内核总是选择当前未被占用的最小整数作为新`fd`。 例如:若`fd=0`(stdin)、`fd=1`(stdout)、`fd=2`(stderr)已被占用,新分配的`fd`为`3`。 - **动态扩展**:若当前文件描述符表已满(`max_fds`不足),内核会调用`expand_files()`扩展数组容量(通常翻倍)。 #### **3. 关键内核函数** - **`get_unused_fd_flags()`** - 遍历文件描述符表,找到第一个空闲位置。 - 检查资源限制(如`ulimit -n`设置的最大文件数),失败返回`-EMFILE`(Too many open files)。 - **`fd_install()`** - 将`struct file`指针写入`files->fd[fd]`。 - 此操作是原子性的,确保多线程环境下的安全。 --- ### **示例流程** 1. 用户调用`open("/dev/device", O_RDWR)`。 2. 内核创建`struct file`,初始化其`f_op`(指向驱动提供的操作函数)。 3. 调用`get_unused_fd_flags(O_RDWR)`,假设返回`fd=3`。 4. 调用`fd_install(3, file)`,将`file`指针存入进程的文件描述符表的第3项。 5. 用户态收到`fd=3`,后续通过`read(3, ...)`等操作设备。 --- ### **关联示意图** ``` 用户态 +---------------------+ | 文件描述符 (fd=3) | ————索引————> 内核态进程的文件描述符表 +---------------------+ +---------+ | 0: NULL | | 1: NULL | | 2: NULL | | 3: *file| ——→ struct file (操作设备) +---------+ ``` --- ### **关键设计思想** 1. **抽象与隔离** - 用户态仅通过整数`fd`操作文件,无需感知内核的`struct file`细节。 - 所有设备、文件、网络套接字均通过统一的文件描述符接口访问。 2. **性能优化** - 文件描述符表使用数组实现,访问时间复杂度为$O(1)$。 - `struct file`由内核统一管理,支持引用计数(`f_count`),避免重复初始化。 --- ### **总结** 文件描述符(`fd`)是用户态与内核态交互的桥梁: - 用户态通过`fd`索引内核文件描述符表。 - 内核通过`fd`找到对应的`struct file`,进而调用驱动或文件系统的具体操作(如`read()`, `write()`)。 这一机制实现了**资源统一管理**和**权限隔离**,是Linux“一切皆文件”设计哲学的底层支撑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值