OOC

Object-oriented design patterns in the kernel 1,多态部分
Object-oriented design patterns in the kernel 2,继承部分

下载保存

https://www.cs.princeton.edu/courses/archive/spring21/cos217/
https://www.cs.princeton.edu/courses/archive/spring21/cos217/lectures/

1 封装

  • 参考文件操作 FILE*
  • 使用不完全类型
  • 头文件分成 2 个,一个对外接口,一个用来给子类嵌套继承

2 多态

两种实现方式,常用第 2 种,文章对内核种第 2 种使用的特殊情况做了讨论

  1. 最简单的方法是结构体包含函数指针,函数第一个参数为结构体指针。bar->foo(bar, …args)。但在对象很多时由于内存占用比下面的 vtable 多,这种 linux 内核不常用
  2. 再复杂是将函数指针组成表,virtual function table (vtable) ,一般命名为 XXX_operations、XXX_ops,对象中包含这个表的指针
    • vtable 为 NULL 的 2 个原因
      1. 新添加了一个方法,但还没实现
      2. 该方法对这种情况无意义,临时的或长期的
      • 但作者认为总可避免使用 NULL(个人观点:会掩盖 Bug)
        • NULL 是少数时,实现默认方法,利用 C99 多次初始化,将默认值写在前面
        • NULL 是多数时,且性能很重要,在调用前判断 flag 的 bit,出现特殊情况才调 vtable 中的函数
    • vtable 还可包含模块名称和链表 struct list_head,便于上层查找注册的设备
    • vtable 第一个参数不是结构体指针的情况
      • 结构体指针放在了最后一个参数
      • 只有一个对象,所以不需要指针,这个对象就是全局的
      • 多重继承,Mixin,提供某个功能,但不以这个对象为主,需要多个对象
      • 子类的操作放入父类的 vtable 中,避免数量多的结构体过大,节省内存,以 struct page 举例

3 继承 is

派生类包含基类 基类使用 void *private 指针 基类包含派生类
定义 基类是第一个派生类成员 基类和派生类互相指 基类和派生类定义到一个文件
派生成员 union 实现
把每个使用 union 的地方看作继承
创建 调派生类 ctor,派生类 ctor 再调基类 ctor 先调基类 ctor,基类 ctor 再调派生类 ctor 先调基类 ctor,再调派生类 ctor
使用 对外提供基类指针,用多态方式调派生类接口
派生类接口中用 container_of() 将基类指针转成派生类
/ /
优点 多数情况 适用于需要切换子类的类型时 /
缺点 / 两次内存分配
性能不好,内存占用和解引用花费时间
void * 可读性差,看不出含义
浪费内存
  • mixin 类型继承,例如链表,提供某种服务

  • 一个对象只有一个引用计数(在最内层基类中)管理生命周期,内核不使用垃圾回收,因此可用是否有引用计数将单继承与 mixin 继承区分开

  • TODO,下一步看 linux 内核加密部分、openssl 部分代码,复习 ooc 代码,仿写出 modbus 代码

  • 重点在第 6 章,后续章要么太难,要么与主题无关,所以基本没看。此外那个计算器是个有趣、有难度的例子。这本书使用 void* 而没用不完全类型(即前向声明)是个小瑕疵,否则代码会更清晰

  • 添加新函数时,基于 vtable 基类 Class 扩展新的函数,定义好后,new 一个新的 vtable 对象(这其实分两步,见第 6 章 initPoint() 代码),所谓类描述符。
    添加新成员时,基于 Object 扩展,然后用刚刚的 vtable 对象(即类描述符)创建对象(因为刚刚创建的 vtable 包含了构造函数和对象大小)。因此创建对象要先创建类描述符。
    因此对于成员和函数要有两个基类,vtable 因为只有一个且储存了对象大小信息所以叫 Class(为避免歧义称 Class 类为 vtable 类,或者叫类描述符类),而包括 vtable 类在内的所有类都继承自 Object 类,Object 类里面只有一个 Class 指针成员,表明所有对象都属于某个类。

  • 还要静态创建 Class 和 Object 类的类描述符,用于子类的 ctor 和 dtor 中调用,初始化基类成员部分。对于 Object 的 ctor 什么都不用做, Class 的 ctor 初始化函数指针。

  • 改进和个人思考:这里静态创建的 Class 和 Object 类的类描述符,用来作为 ctor 调父类 ctor 的终点,完成继承关系即 新类的 vtable -> Class -> Object 。新的类描述符要 new 出来,不如静态定义清晰,因为类不会像对象创建很多(虽然逻辑上确实是用 Class 这个 vtable 基类,创建新的类,相当于 new 新的类描述符对象,但这种对象不会很多)。如静态定义就不必再调 Class 的 ctor,也不必静态创建 Class 的类描述符。而且 Class 不必继承 Object,否则每个对象,成员部分继承 Object 有 Class 指针,函数部分也继承 Object,即 Class 部分还要有 Object,Object 里面又有 Class。Class 不继承 Object(即不包含 Object),自己本身就作为 vtable 形式的基类,与 Object 是平等的关系。新类的 ctor 没必要再调父类 Object 的 ctor,可以仅为了继承逻辑,struct Class Object = {初始化},新类指向 &Object

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    struct Object {
    const struct Class * class; // 没变化
    };

    struct Class {
    // 这里去掉 Object 继承,const struct Object _;
    const char * name;
    const struct Class * super;
    size_t size;
    void * (* ctor) (void * self, va_list * app);
    void * (* dtor) (void * self);
    int (* differ) (const void * self, const void * b);
    int (* puto) (const void * self, FILE * fp);
    };

    struct Class Object = {.name = "Object"}; // 只定义 Object 的类描述符,不再定义 Class 的类描述符

    struct Point {
    const struct Object _;
    int x, y;
    };

    struct PointClass { // 静态初始化类描述符
    const struct Class _;
    void (* draw) (const void * self);
    } Point = {_.name = "Point", _.super = &Object, _.size = sizeof(struct Point), _.ctor = Point_ctor, .draw = Point_draw};

    p = new(Point, 1, 2); // new 里面按照 Point 的大小申请内存和调构造函数
  • 进一步举例如何应用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    struct DeviceClass {
    struct Class parent;
    void (* fun1) (const void * self);
    } DeviceDesc = {这个就是类描述符,创建对象时需要} ;

    struct Device {
    struct Object parent;
    int x, y;
    };

    struct SubDeviceClass {
    struct DeviceClass parent;
    void (* draw) (const void * self);
    } SubDeviceDesc = {这里可以先继承父类的函数指针(.parent = DeviceDesc),然后在后面来覆盖(.parent.parent.ctor = SubDeviceClass_ctor),因为根据 C99 多次初始化最后那个有效};

    struct SubDevice {
    struct Device parent;
    int z;
    };

4 异常处理

用对应的库,用 setjmp 和 longjmp 实现。

还能实现线程切换,已经放入 Dropbox 书 https://www.cs.princeton.edu/courses/archive/spring04/cos217/lectures/Exceptions.pdf

5 单元测试

试试 Google Test ? https://stackoverflow.com/questions/65820/unit-testing-c-code

6 内存管理

7 安全

cppcheck