linux内核设计与实现思想 – C风格的面向对象

原文链接:linux内核学习 – C风格的面向对象

 

 

linux内核大量使用面向对象的编码风格。然而linux内核是完全使用C写就。学习他们如何使用C模拟面向对象机制很有意思。这种做法很可能被人贬为扯淡,但是的确使用C模拟面向对象机制,使得程序员对类型构造/析构,拷贝/赋值等操作有了绝对的控制权,可以提高对效率的嗅觉,减少错误,同时也避免了对C++编译器各种不同类/对象实现机制的依赖。

类的多态特征是linux内核经常用到的。例如在驱动代码中常常使用函数指针来定义一组设备操作函数,从而模拟了多态的特点。


struct file_operations scull_fops = {
    .owner = THIS_MODULE,
    .llseek = scull_llseek,
    .read = scull_read,
    .write = scull_write,
    .ioctl = scull_ioctl,
    .open = scull_open,
    .release = scull_release,
};

上面的例子是Linux Device Driver中抄来的示例代码。很好地展示了file operation结构体如何使用这种机制来定义一组文件操作的方式。用这种方式,Linux很好地贯彻了所有的设备都是文件这种概念。不同的设备可以有不同的处理函数,但使用相同的接口,这样就把底层设备的差异在文件系统这一层隔离开来了。

Linux内核中也经常用到类的继承关系。这种关系使用C也很容易模拟,就是使用结构体嵌套。例如


struct scull_dev {
     struct scull_qset *data;   
     int quantum;               
     int qset;                  
     unsigned long size;    
     unsigned int access_key;  
     struct semaphore sem;     
     struct cdev cdev;      //内嵌linux内核定义的cdev结构体
};

这个例子同样来自LDD。注意在自定义的cdev字符设备结构体中包含了struct cdev cdev成员。这个成员同样是一个结构体,由内核定义,是字符设备描述符。使用这种方式,可以一定程度模拟C++的继承机制,当然有他的局限,例如他不能如同在C++中一样直接引用cdev的成员,而必须通过scull_dev.cdev来引用。

另一方面,这种方式也无法通过“基类”,即cdev的指针,访问“子类”,即scull_dev的成员。精彩的部分来了,linux通过一组宏,巧妙的实现了这一点。在文件处理的函数中,入参会给入inode指针,从这个指针可获得其cdev成员。如何从这个cdev成员获取包含它的“子类”对象,scull_dev的指针呢?

container_of(ptr, type, member)

使用这个宏,container_of(inode->i_cdev, struct scull_dev, cdev)就可获得包含cdev的scull_dev的地址。这个巧妙的宏是如何实现的呢?

#define container_of(ptr, type, member) ({ \
                const typeof( ((type *)0)->member ) *__mptr = (ptr); 
                (type *)( (char *)__mptr - offsetof(type,member) );})

这个宏首先定义一个指向结构体成员的指针__mptr = (ptr),他的类型是const typeof(...)。这里用到了C语言一个较新的关键字typeof,可以在编译期获得变量的类型。而这个类型是((type*)0)->member,这里type和member分别是宏传入的参数。这一行代码就比较清晰了。得到这个__mptr之后,将他向回移动一个offset,(char*)__mprt - offsetof(...),而这个offset恰好为member相对于type的偏移量,offsetof(type,member),则移动完毕__mptr就指向type类型的起始地址了,只需将其转换为type*类型就可以了,(type*)(...)

好了,这个宏已经看懂,神奇的地方就出在这个offsetof宏了,他是如何计算成员相对于结构体的偏移量呢?这里linux内核hacker们用了一个小小trick。

#define offsetof(s, m)   (size_t)&(((s *)0)->m)

是的,代码非常简单。其思想是,假如结构体处于0地址,获取其成员的地址。这个地址就是成员相对于结构体初始地址的偏移量了。没错0地址是不能运行时访问的,但这句代码只在编译期使用了0地址,因此是合法的。当然其实使用成员指针和结构体指针相减也可做到,但用这种方式可以减少一次运算,确保了这个宏可以在编译期求出结果。可谓是精益求精。

我说错了。即使使用减法也可以做到编译期求值,因为结构体和成员指针地址都是可以编译期得到的,常量数值计算应该可以做到编译期优化,计算完成。这种做法应该是
&((type*)0)->member - ((type*)0)
这样的代码的一个直觉性的优化,减0的话,何必还要减呢。事实上两句代码的运行时间是一样的,但这样做可以减轻编译时间。
在container_of宏中,也有一句减法计算。这个计算引用了运行时求值的__mptr,所以无法做到编译期求值。

类似这种用法,在linux内核中经常出现。深深佩服大牛们的创造力,并且深深的意识到了即使是C语言也是学无止境的。

 

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Linux内核设计实现是一本深入介绍Linux操作系统内核结构和实现原理的经典著作。该书由李梁在1999年出版,目前已经成为学习Linux内核的权威参考书籍。 《Linux内核设计实现》先介绍了Linux操作系统的基本架构和组成部分,包括进程调度、内存管理、文件系统、设备驱动等重要内容。接着讲述了Linux内核的启动过程,从加载引导程序开始,到初始化各个子系统和驱动,最终进入用户空间。作者逐层剖析了Linux内核实现原理和数据结构,使读者能够深入理解Linux内核运行的机制。 阅读本书有助于了解Linux内核设计的哲学和设计原则,包括模块化、可移植性、可扩展性等。通过学习本书,读者可以获得一种全面而深入的了解操作系统内核的能力,为今后编写和调试内核代码提供有力的支持。 此外,《Linux内核设计实现》还介绍了一些内核开发的工具和调试技巧,如kdb调试工具、系统跟踪技术等。这些内容对于进行内核开发和调试的读者尤为重要。 最后,本书还对Linux内核的未来发展进行了展望,介绍了一些新的技术和方向,如虚拟化、容器等。这些内容能够帮助读者跟踪内核技术的最新发展和趋势。 总而言之,《Linux内核设计实现》是一本全面深入的Linux内核学习资料,既适合想要了解Linux内核原理的初学者,也适合有一定经验的开发者。它可以帮助读者理解Linux操作系统的内部工作原理,提高操作系统开发和调试的技能,对于专注于Linux开发的人来说是一本必备的参考书。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值