Linux内核结构体“file_operations“ 初始化以及内存占用分析

文章探讨了Linux内核中使用designatedinitializer初始化结构体成员的方式,以字符设备驱动中的file_operations结构体为例。这种方式允许灵活地初始化结构体的部分成员,提高代码可维护性,避免因结构体成员变动导致大量代码修改。尽管只初始化部分成员,结构体实例的内存大小并不会因此减少,所有成员仍会占用空间。
摘要由CSDN通过智能技术生成

01🙆‍♂️起因

在学习字符设备驱动的时候,看到Linux内核中的结构体的指定初始化方式(designated initializer)使用方式,之前没有见过这种初始化形式,做一点探索。

/* 字符设备操作集 */
static struct file_operations gpio_fops={
    .owner      = THIS_MODULE,
    .open       = gpio_open,
    .release    = gpio_relase,
    .read       = gpio_read,
    .write      = gpio_write,
    .fasync     = gpio_fasync,
    .release    = gpio_release,
};

上面的结构体,

  1. 通过.open = gpio_open,的方式,进行初始化结构体的成员项。

  2. 将驱动注册到内核中。

  3. 然后,应用程序使用open 的时候,直接通过回调函数的形式来执行我们驱动实现的相关功能gpio_open

struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
	ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
	int (*iterate) (struct file *, struct dir_context *);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*mremap)(struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, loff_t, loff_t, int datasync);
	int (*aio_fsync) (struct kiocb *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
    ... // 省略
        
};

02🙆‍♂️好处分析:

  1. 使用灵活,可以指定某一项进行初始化,而不是将所有的成员变量来初始化。
  2. 代码易于维护,内核中,会有大量的代码都会使用这个file_operations结构体来定义变量,并且初始化,如果结构体后续,需要添加成员,或者调整成员顺序,那么会引起大量的文件都要重新编译,牵一发而动全身。通过这种方式,就不用有这个但心。

03🙆‍♂️内存大小的变化

既然可以指定初始化结构体里面的几个变量,但结构体实例化后的内存是不是也会又变化?

答案是结构体变量,无论初始化与否,成员变量都会占用内存空间的。

测试代码如下:

#include "stdio.h"

struct student{
    char name[20];
    int age;
    int height;
};

int main(void)
{
    struct student stu1={ "01", 20, 178 };
    printf("%s:%d:%d\r\n", stu1.name, stu1.age, stu1.height);
    printf("stu1 size: %d\r\n", sizeof(stu1));

    struct student stu2=
    {
        .name = "02",
        .age  = 28
    };
    printf("%s: %d\r\n",stu2.name,stu2.age);
    printf("stu2 size: %d\r\n", sizeof(stu2));

    return 0;
}

student结构体应该占据20+4+4个字节bytes的空间,实测如下

01:20:178    
stu1 size: 28 # 初始化三个成员变量
02: 28       
stu2 size: 28 # 初始化两个成员变量
 *  Press any key to close the terminal. 
参考资源链接:[Linux字符设备驱动开发:并发、竞态与内存交互](https://wenku.csdn.net/doc/1zh2ux6k2f?utm_source=wenku_answer2doc_content) 为了确保在Linux字符设备驱动开发中正确使用cdev结构体file_operations实现设备注册及并发控制,首先需要理解cdev结构体的作用及其与file_operations的关系。cdev结构体负责维护字符设备的内核状态,而file_operations则定义了设备操作的函数接口,允许内核与用户空间的程序进行交互。在并发环境下,开发者必须考虑到竞态条件,并采取适当的同步措施。这里推荐参考《Linux字符设备驱动开发:并发、竞态与内存交互》文档,以获得更深入的理解和指导。 实际开发过程中,通常会定义一个file_operations结构体,并在其中注册设备的各种操作函数,如read、write、open和release等。这些函数是用户空间程序访问设备时内核调用的实际操作。使用cdev_init函数初始化cdev结构体后,将其与file_operations结构体关联,并通过cdev_add函数注册到内核。注册后,设备会获得一个唯一的设备号,包括主设备号和次设备号,这允许用户空间程序通过设备文件与之交互。 对于并发和竞态条件的处理,开发者需要掌握锁的使用,例如自旋锁(spinlock)或信号量(semaphore),这些机制能够保证在多任务环境下对设备的访问不会发生冲突。例如,在file_operations结构体中的read和write函数内部,可以使用自旋锁来确保在读写过程中设备状态的一致性,避免竞态条件的发生。开发者应当根据具体情况选择合适的锁机制,并在文件系统层面,如VFS层面上,实现相应的锁定策略,以确保数据的一致性和系统的稳定性。 通过这些步骤,你可以有效地在Linux内核中注册字符设备,并且能够处理并发访问时的同步问题,保证设备驱动的稳定和高效运行。当你需要更深入地了解设备驱动开发的其他方面,如内存管理、设备文件的创建与销毁等,可以继续参阅《Linux字符设备驱动开发:并发、竞态与内存交互》文档,它将为你提供更为全面的知识和实用的解决方案。 参考资源链接:[Linux字符设备驱动开发:并发、竞态与内存交互](https://wenku.csdn.net/doc/1zh2ux6k2f?utm_source=wenku_answer2doc_content)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值