Linux内核C语言中的面向对象

转载自”Blog of UnicornX” (http://unicornx.github.io/)

最新更新于:2016-01-06

主要参考了网上的文章

封装

封装的定义是在程序上隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别;将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“类”,其中数据和函数都是类的成员。

最早C++里面的封装是从C中的头文件规则衍生出来的。其关键在于对于一个公共数据操作方法的归类。如果一个公共变量,被很多个C 函数修改,如下图:

后期调试及维护时,带来极大的不便,因为根本不知道系统中到底有多少函数操作的这块数据,这块数据的状态什么时候,被哪个函数改变了状态。所以就演变成了下面的方式:

将操作此数据的函数放到同一个文件中去,任何其它改变或访问此数据的函数都需要通过这个文件中的函数来访问。

所以面向对象中的类,其实是C工程中总结出来的一系列管理方法在语言层面上的实现,并没有太多的新东西。

扫描二维码关注公众号,回复: 11072981 查看本文章

假设我们有类型foo, 其内含有a、b、c三个成员变量,并有操作方法 op_a(a)、op_b(b)、op_c(c)等。在面向对象语言中及其容易实现,在C语言中面临的最大问题是操作方法及其参数传递。

在Linux内核中通过函数表可以弥补语言机制上的缺陷:

struct foo_operations {
    void (*op_a) (struct foo *, loff_t, int);
    void (*op_b) (struct foo *, char __user *, size_t, loff_t *);
    void (*op_c) (struct foo *, const char __user *, size_t, loff_t *);
    ......
};

首先这是一个只含有方法的结构体,我们称之为函数表,传递的结构体参数foo为c++中默认编译器为类传递的this对象,这样,我们将结构体ops内嵌到结构体foo中,如下:

struct foo {
    int a;
    int b;
    int c;
    struct foo_operations ops;
};

将方法封装到了结构体中。在这里结构体foo相当于一个父类,而ops是作为父类公开给子类的接口。抽象出了接口,后面的多态也就很容易实现了。从这里模拟封装的过程,可以很容易发现接口的实质是父类提前为子类预留的占位符,是一种“高瞻远瞩”的行为。

多态:

多态是允许将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。
由多态的定义,最关键的地方在于运行时切换即在编译期不能确定需要执行的代码。
在内核中采用的方法如下:先声明两个子操作表ops1 、ops2,当需要使用ops2时,将 A->ops = ops2,即将子类实例化到父类,下面类A的具体操作就会采用ops2操作表中的方法。如图:

代码示例:

struct foo a;
a->ops = ops1;
a->ops->op_a(a);
//切换
a->ops = ops2;
a->ops->op_a(a);

继承:

特殊类(或子类、派生类)的对象拥有其一般类(或称父类、基类)的全部属性与服务,称作特殊类对一般类的继承。
继承的本质是保留父类的一些成员和函数,实质上是保留父类的存储区及跳转函数表,继承在C语言中也是采用内嵌结构体的方式来实现的。这里举内核中的链表结构体来说明,
我们通常所使用链表结构如下:

struct A_LIST {
    data_t        data; // 不同的链表这里的data_t类型不同。
    struct A_LIST    *next;
};

在这里我们发现,每一个特定类型的链表机构都需要为其增加一个next字段来保持链表的结构。还需要为每一个特定的链表类实现脱链、入链操作等。我们需要抽象出一个“基类”来实现链表的功能,其他链表类只需要简单的继承这个链表类就可以了。

Linux 内核的链表结构:

struct list_head {
    struct list_head *next, prev;
);

在这个结构体中,链表及作为一个单独的结构体,不再依附与任何对象。
同上面的例子,我们只需要声明

struct A_LIST {
    data_t            data;
    struct list_head    *list;
};

这样A_LIST即可作为一个链表对象,其链表操作均由其成员list代为实现,起到了继承基类的可复用方法的作用。实际过程如下图:

其作为一个连接件,只对本身结构体负责,而不需要关注真正归属的结构体。正如继承的特性,父类的方法无法操作也不需要操作子类的成员。关于链表结构的宿主指针获取方法,

获取结构类型TYPE里的成员MEMBER 在结构体内的偏移

#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)

通过指向成员member的指针ptr获取该成员结构体type的指针

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

至此,C在面向对象的三大特性的实现过程就结束了。

#linux #kernel #oob

发布了3 篇原创文章 · 获赞 5 · 访问量 2252

猜你喜欢

转载自blog.csdn.net/weixin_41866037/article/details/95341193