Linux内核随处可见面向对象思想,但就是不用 C++ 来写,这里面是不是有什么说法?
这个问题,其实从 Linux 之父林纳斯本人的口中就能找到答案,他曾经多次公开吐槽过 C++ 是糟糕的语言。
首先,我们来看下内核有哪些面向对象思想?
/* * NOTE: * read, write, poll, fsync, readv, writev, unlocked_ioctl and compat_ioctl * can be called without the big kernel lock held in all filesystems. */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 (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); int (*readdir) (struct file *, void *, filldir_t); unsignedint(*poll)(struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); 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 (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsignedlong(*get_unmapped_area)(struct file *, unsignedlong, unsignedlong, unsignedlong, unsignedlong); int (*check_flags)(int); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); int (*setlease)(struct file *, long, struct file_lock **); };
比如最典型的 struct file_operations 结构体,这是Linux 虚拟文件系统中用于操作文件的“对象”。结构体里面几乎都是指针,更像是C++中的“虚函数表“,定义了open、read、write、release等一系列操作。具体的文件系统会提供自己的实现,并将这些函数指针赋值给这个结构体。
内核没有 C++ 的“继承”语法,但通过将父结构体作为子结构体的成员,实现了类似继承的效果。
/* * The pci_dev structure is used to describe PCI devices. */struct pci_dev { struct list_head bus_list; /* node in per-bus list */ struct pci_bus *bus; /* bus this device is on */ struct pci_bus *subordinate; /* bus this device bridges to */ void *sysdata; /* hook for sys-specific extension */ struct proc_dir_entry *procent; /* device entry in /proc/bus/pci */ struct pci_slot *slot; /* Physical slot this device is in */ // ....#ifdef CONFIG_PCIEASPM struct pcie_link_state *link_state; /* ASPM link state. */#endif pci_channel_state_t error_state; /* current connectivity state */ struct device dev; /* Generic device interface */ int cfg_size; /* Size of configuration space */ /* * Instead of touching interrupt line and base address registers * directly, use the values stored here. They might be different! */ unsigned int irq; struct resource resource[DEVICE_COUNT_RESOURCE]; /* I/O and memory regions + expansion ROMs */ // ....};
最典型的就是 struct device 和 struct pci_device 结构体,pci_device 里面包含了 device 结构体。面向对象最核心的思想,应该就是多态,在内核里面,也有很多多态的案例。比如文件系统中的struct file_operations。对于一个struct file,无论它代表什么样的文件,内核都通过 file->f_op->read() 来调用其读操作。f_op这个函数指针表指向了不同文件系统各自实现的read函数,从而实现了“同一个接口,多种行为”的多态性。
说完了面向对象特性,再来看看为什么内核不直接用 C++ 来写。
第一点,异常处理,这也是林纳斯最反感的一点。C++的异常处理机制在内核这种底层、资源敏感的环境中会带来巨大的不确定性和不可预测性。内核需要精确控制每一处错误路径,而异常处理隐藏了控制流,使得代码行为难以预测,对内核的稳定性是致命的
第二点,隐藏的内存分配。比如构造函数,会在开发者不知情的情况下进行内存分配。在内核中,内存是极其宝贵的资源,任何“隐形”的操作都可能成为性能瓶颈或导致不可预知的失败。
第三点,性能与可预测性。C++的诸多高级特性(如虚函数、模板、RTTI等)会引入额外的运行时开销,这些开销在内核这种对性能要求苛刻的场景下是不可接受的。使用C,代码的行为几乎完全透明,开发者可以精确地知道每一行代码会生成什么样的机器指令。
第四点,C++还是太复杂了。在一个拥有数千万行代码、由全球数千名开发者共同维护的项目中,这种复杂性会成为沟通和理解的巨大障碍。
大家觉得,还有哪些原因导致内核不用C++来写,欢迎在评论区交流。
实战延伸___
以上只是 Linux 工程中的一个小技巧,如果你也想真正写出工程级代码,我整理了几个完整项目,这个暑期可以一对一带着做,从0到可运行项目,适合做简历提升。
品牌宣传 | 企业培训 | 技术推广 | 职业提升