struct files_struct和struct fdtable

struct files_struct和struct fdtable的初始化
我们先来列出struct files_struct和struct fdtable的定义,为了讨论方面,下面的定义中略去了很少一部分的锁成员,下面的代码均摘自linux 2.6.24。
struct files_struct在<include/linux/fdtable.h>中定义如下:
struct files_struct {
    atomic_t count;
    struct fdtable *fdt;
    struct fdtable  fdtab;
   
    int next_fd;
    struct embedded_fd_set close_on_exec_init;
    struct embedded_fd_set open_fds_init;
    struct file * fd_array[NR_OPEN_DEFAULT];
};


struct fdtable定义如下:
struct fdtable {
    unsigned int max_fds;
    struct file ** fd; /* 当前fd_array */
    fd_set *close_on_exec;
    fd_set *open_fds;
    struct rcu_head rcu;
    struct files_struct *free_files;
    struct fdtable *next;
};
在struct files_struct中包括一个struct fdtable变量实例和一个struct fdtable类型指针,而struct files_struct中的成员变量close_on_exec,open_fds,fd又分别指向struct files_struct中的成员close_on_exec_init,open_fds_init和fd_array。即一个结构中嵌套了另一个结构,被嵌套结构中的成员又反过来引用了属主的成员,这样看起来有些重复和让人迷惑。
在files_struct结构的初始化时,能更清晰的看出这种重复。如内核第一个进程(即进程init)的files_struct静态初始化:
struct files_struct init_files = {
    .count      = ATOMIC_INIT(1),
    .fdt        = &init_files.fdtab,
    .fdtab      = {
        .max_fds        = NR_OPEN_DEFAULT,
        .fd               = &init_files.fd_array[0],
        .close_on_exec  = (fd_set *)&init_files.close_on_exec_init,
        .open_fds       = (fd_set *)&init_files.open_fds_init,
        .rcu            = RCU_HEAD_INIT,
    },
    .file_lock  = __SPIN_LOCK_UNLOCKED(init_task.file_lock),
};

下图展示了上述代码初始化后各结构成员之间的关系:

可以看出,files_struct的成员fdt在这里初始化为其内嵌的struct fdtable变量实例(成员fdtab)地址,内嵌的struct fdtable实例变量的fd,close_on_exec,open_fds又指向属主的相应变量。那么这个fdtab存在的必要意义是什么呢(在此处看不出来,即使去除了该变量,也足以表达files_struct)?
我们知道,linux系统中,一个进程打开的文件数是有初步限制的,即其文件描述符数初始时有最大化定量,即一个进程一般只能打开NR_OPEN_DEFAULT个文件,该值在32位机上为32个,在64位机上为64个。上面的files_struct这种初始化正是体现了进程初步所能打开文件的内核结构描述。这里需要说明的是,这不仅仅限于类似上面的静态初始化,当init进程fork一个子进程时,也是如此(此时是files_struct是动态分配的)。
见下面的代码:
static struct files_struct *alloc_files(void)
{
    struct files_struct *newf;
    struct fdtable *fdt;
    newf = kmem_cache_alloc(files_cachep, GFP_KERNEL);
    if (!newf)
        goto out;
    atomic_set(&newf->count, 1);
    spin_lock_init(&newf->file_lock);
    newf->next_fd = 0;
    fdt = &newf->fdtab;
    fdt->max_fds = NR_OPEN_DEFAULT;
    fdt->close_on_exec = (fd_set *)&newf->close_on_exec_init;
    fdt->open_fds = (fd_set *)&newf->open_fds_init;
    fdt->fd = &newf->fd_array[0];
    INIT_RCU_HEAD(&fdt->rcu);
    fdt->next = NULL;
    rcu_assign_pointer(newf->fdt, fdt);
out:
    return newf;
}

函数alloc_files的被调用流程是:
do_fork------>copy_process
    |------>dup_task_struct------>alloc_task_struct
    |------>copy_files------>dup_fd------>alloc_files
即先调用alloc_task_struct分配一个task_struct结构实例,然后使用alloc_files来分配并初始化files_struct变量实例。
上面的 files_struct初始化和上面例子中的静态初始化并无本质差别:
语句fdt = &newf->fdtab取出newf的struct fdtable实例变量fdtab的指针,然后通过rcu_assign_pointer函数将其赋值给newf->fdt,那么newf->fdt还是指向其自身中的struct fdtable实例变量,fdt的成员close_on_exec、open_fds和fd也是如此。

struct files_struct扩充
当进程打开的文件数超过NR_OPEN_DEFAULT时,就要在对上面的已初始化的struct files_struct(更准确的说是其所包含的数据对象)进行扩充。
上面对于struct files_struct和struct fdtable这种互相引用及其冲突讨论了很多,其实内核中之所以还使用了struct fdtable,根本目的是为了应对这种扩充。
当进行struct files_struct扩充时,会分配一个新的struct fdtable,为了叙述方便,下面该变量用指针用nfdt来表示。另外还分配了满足扩充要求的fd数组(即struct file数组),以及与fd相对的bitmap描述close_on_exec,open_fds的存储空间。
然后将新分配的close_on_exec,open_fds,fd空间指针赋值给nfdt->close_on_exec,nfdt->open_fds和nfdt->fd。注意,这里的close_on_exec,open_fds和上面初始化时close_on_exec_init,open_fds_init的差别:
close_on_exec,open_fds的最大值按bit来说为__FDSET_LONGS,实际值为1024位,即文件描述符的最大数为1024个。但它们也是按需分配,并和file数组的大小一致,分配的实际值同时赋值给nfdt->max_fds。
分配并初始化新的struct fdtable变量后,原先指向fdtab的struct files_struct指针成员fdt,会调整为指向新分配的struct fdtable变量。这时,struct files_struct实例变量中就包含两个struct fdtable存储区:一个是其自身的,一个新分配的,用fdt指向。
执行完上面的操作后,其关系如下图:

注意上图中同颜色的存储区,代表的是两者内容是一致的。即执行完上述的操作后,还要将的结构存储区的内容拷贝到新的存储区,这包括files_struct自身所包含的close_on_exec,open_fds,fd到新分配的close_on_exec,open_fds,fd的拷贝。
执行完上述拷贝之后,就要释放旧的struct fdtable,但这里并不执行执行该项释放操作(下面的讨论中会说明何时才进行释放操作)。

struct files_struct扩充使用内核源码中的expand_files来实现,expand_files会调用expand_fdtable:
static int expand_fdtable(struct files_struct *files, int nr)
{
    struct fdtable *new_fdt, *cur_fdt;
    new_fdt = alloc_fdtable(nr);      //分配了一个fdtable
    cur_fdt = files_fdtable(files);   //files->fdt
    if (nr >= cur_fdt->max_fds) {
        /* Continue as planned */
        copy_fdtable(new_fdt, cur_fdt);   //拷贝了其中的3个变量:fd,open_fds,close_on_exec
        rcu_assign_pointer(files->fdt, new_fdt);  //将新分配的fdtable赋值给files的fdt
          if (cur_fdt->max_fds > NR_OPEN_DEFAULT)  //注意它第一次初始化为NR_OPEN_DEFAULT
            free_fdtable(cur_fdt);
     }
    return 1;
}

上面代码中包括:
先使用函数alloc_fdtable分配一个struct fdtable变量,以及close_on_exec,open_fds,file数组
因为我们这里讨论的是扩充,所以接下来的if判断为真,那么执行旧的数据区到新分配数据区的拷贝,即函数copy_fdtable,拷贝操作上面已经进行过讨论,在此不再赘述。这里重点说明旧的数据区的释放:
if (cur_fdt->max_fds > NR_OPEN_DEFAULT)  
    free_fdtable(cur_fdt);
此处的cur_fdt是旧的struct fdtable变量指针,它的文件描述符数初始化为NR_OPEN_DEFAULT,显然if判断为假,并不执行free_fdtable来进行旧的struct fdtable释放。那么何时才进行释放呢?
我们下面来分析free_fdtable函数,该函数会引起free_fdtable_rcu的调用,简化的free_fdtable_rcu函数代码如下:
void free_fdtable_rcu(struct rcu_head *rcu)
{
    struct fdtable *fdt = container_of(rcu, struct fdtable, rcu);
    struct fdtable_defer *fddef;
 
    if (fdt->max_fds <= NR_OPEN_DEFAULT) {
          kmem_cache_free(files_cachep,
                container_of(fdt, struct files_struct, fdtab)); //这里释放的是files_struct本身
        return;
    }
    //上面是最初的分配,下面的是扩展分配(本次是再次扩展),注意扩展分配时fd,open_fds都是动态分配的,
    //所以可以用free释放掉。
    //另外,这里并没有显式的释放close_on_exec指向的内存
    if (fdt->max_fds <= (PAGE_SIZE / sizeof(struct file *))) {
        kfree(fdt->fd);
        kfree(fdt->open_fds);
        kfree(fdt);
    } else {
            .....
    }
}
上面的代码中,开头的if (fdt->max_fds <= NR_OPEN_DEFAULT)判断语句为真,此时是struct files_struct没有进行扩充,而需要释放files_struct本身时,对free_fdtable_rcu的调用。这时,会执行kmem_cache_free,以container_of(fdt, struct files_struct, fdtab)为需要释放存储空间的指针参数,来完成释放。

下一个if语句块就是扩充后struct files_struct的释放,注意此时暗含的条件是fdt->max_fds大于NR_OPEN_DEFAULT,即进行扩充后的释放,但我们前面讨论的expand_fdtable不会执行到free_fdtable_rcu(第一次扩充),而此处的fdt->max_fds大于NR_OPEN_DEFAULT表示的是二次扩充,即在我们前面讨论的扩充基础上的再次扩充,此时就需要释放上面讨论的新分配的struct fdtable,以及open_fds和file array。
这里并没有看到释放close_on_exec,其实在函数alloc_fdtable中分配open_fds和close_on_exec时,是将二者统一分配的,所以上面的kfree(fdt->open_fds)语句用来释放这个统一分配的存储区,close_on_exec的分配空间一同释放。
最后列出空间分配函数alloc_fdtable的代码:
static struct fdtable * alloc_fdtable(unsigned int nr)
{
    struct fdtable *fdt;
    char *data;

    data = alloc_fdmem ( nr * sizeof ( struct file * ));     //这里可以看出fdt->max_fds是最大的文件描述
                                                      //符, 即分配了多少个file结构体
    fdt->fd = (struct file **)data;

    //下面乘2的意思是open_fds + close_on_exec
    data
= alloc_fdmem(max_t(unsigned int,
                 2 * nr / BITS_PER_BYTE, L1_CACHE_BYTES)); 
    fdt->open_fds = (fd_set *)data;
    data += nr / BITS_PER_BYTE;
    fdt->close_on_exec = (fd_set *)data;
    INIT_RCU_HEAD(&fdt->rcu);
    fdt->next = NULL;
    return fdt;
     ...
}






### Asterisk PJSIP Module Struct Definition and Usage In the context of configuring DTMF modes within an Asterisk environment using PJSIP, specific structures are utilized to manage endpoints effectively. The `ast_sip_endpoint` structure plays a crucial role in this process by holding configurations related to each SIP endpoint[^1]. Below is how such a structure might be defined: #### Structure Definitions The core structure used for managing SIP endpoints includes fields that store various settings including DTMF mode preferences. ```c struct ast_sip_endpoint { /* Other members */ int dtmf; }; ``` This snippet shows part of the `ast_sip_endpoint` structure where `dtmf` represents the configured DTMF handling method set through configuration files or API calls. For implementing handlers like those dealing with options from configuration files, functions similar to the following can be employed: ```c static int dtmf_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) { struct ast_sip_endpoint *endpoint = obj; int dtmf = ast_sip_str_to_dtmf(var->value); if (dtmf == -1) { return -1; } endpoint->dtmf = dtmf; return 0; } ``` Here, when processing configuration directives concerning DTMF (`dtmf_mode=rfc4733/inband`), the handler converts string representations into integer values suitable for internal use before assigning them back to the corresponding field inside the `ast_sip_endpoint` instance. Additionally, modules within Asterisk's architecture have their own definitions specifying behavior at different layers of communication. For example, the Endpoint Identifier module has its priority level specified as follows: ```c static pjsip_module endpoint_mod = { .name = {"Endpoint Identifier", 19}, .priority = PJSIP_MOD_PRIORITY_TSX_LAYER - 3, .on_rx_request = endpoint_lookup, }; ``` Such modular design allows flexibility while ensuring proper sequencing during message transactions between entities involved in call flows[^3]. When it comes to supporting dynamic updates without restarting services, Sorcery provides mechanisms enabling real-time modifications via database-backed tables linked directly to relevant sections of code responsible for maintaining state information about registered devices and users[^5]: ```ini [res_pjsip] endpoint=realtime,ps_endpoints auth=realtime,ps_auths aor=realtime,ps_aors domain_alias=realtime,ps_domain_aliases contact=realtime,ps_contacts ``` These entries define mappings connecting logical objects managed internally with external storage solutions facilitating seamless integration across diverse deployment scenarios requiring adaptability beyond traditional flat-file approaches alone. --related questions-- 1. How does one implement custom event listeners on top of existing PJSIP modules? 2. What changes occur under the hood upon upgrading from chan_sip to chan_pjsip regarding NAT traversal techniques? 3. Can you provide examples illustrating differences in memory management practices comparing legacy vs modern implementations found in newer versions of Asterisk? 4. In what ways do security features differ among varying releases focusing specifically on authentication methods supported natively out-of-the-box? 5. Describe best practices for optimizing performance metrics associated particularly around large-scale deployments leveraging clustering technologies alongside PJSIP?
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值