LDD3_NO.2:Kernel Modules Versus Applications

本文探讨了内核模块与应用程序的区别,包括它们的初始化、卸载过程、并发执行问题及内存空间的不同。同时介绍了内核模块如何扩展内核功能,并强调了在内核编程中考虑并发的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

深入学习之前,比较内核模块和应用程序。

大多数的中小型应用程序都是单一的任务,自始至终。而内核模块首先要注册,目的是为以后提供某些服务。它的初始化也非常简短,迅速。换句话说,内核模块的初始化是为了以后能够提供某种服务的,貌似告诉系统,我已经在这里,我可以提供一些服务!

当内核模块要卸载(unload)的时候,其退出函数会被调用,表明:我现在不在这里了,以后别找我!

从这点看,内核模块的编程有点像事件驱动event-driven ),但是应用程序有时候不是事件驱动。内核模块代码和事件驱动的应用程序代码之间有一个最大的不同,就是他们的退出处理。应用程序可能对退出处理不是很担心,甚至不做什么清理工作,而内核模块必须要小心地处理初始化建立起来的那些东西,否则,他们会一直遗留到系统重新启动。

这样做的好处是,我们可以连续的测试新内核模块,不需要频繁的重启系统。

 

在应用程序中,我们可以调用程序之外的没有定义的函数(使用函数库),在链接阶段,会检查它的外部的引用。但是内核模块编程只能调用内核所提供的函数,不能链接到库。正因为如此,在内核模块代码中,不能包含外部库文件。只能使用我们自己建立和配置好的内核代码树的头文件,一般这些文件放在: include/linux and include/asm,一些子目录中的include文件夹也有。

 

1. User Space and Kernel Space

内核模块在内核空间运行,而应用程序在用户空间运行。

操作系统的任务就是给程序提供一种计算机硬件的一致的视角,而且操作系统也要防止对内存的越权存取。

现代的处理器都有这样的功能,在CPU内部采用不同的操作等级。这些等级有不同的任务,在较低级的等级中,有一些操作时禁止的。程序代码通过gate在各个等级之间进行切换。UNIX操作系统利用了这个硬件特征,使用了两种等级。目前的处理器,至少会提供两个等级,有的处理器,比如X86系列会有更多的等级,这时,UNIX就使用最高和最低的等级。

对于内核空间和用户空间这两个概念,当我们提到他们时,不仅仅指他们不同的权限,还意味着他们拥有不同的内存映射(内存地址空间)。

UNIX中,当应用程序使用了系统调用或硬件中断时,会从用户空间转换到内核空间。系统调用是在调用它的进程的上下文中运行的,可以访问进程的地址空间;而中断处理是异步操作,与进程无关。

模块的角色是扩展内核的功能。有时,在系统调用中使用驱动模块,有时在中断中使用。

 

2.Concurrency in the Kernel

内核编程和传统应用程序的一个很大的不同是并发问题。大部分应用程序,除了多线程,都是从开始到结束顺序的执行,不需要担心发生什么事情,改变运行环境。内核代码不会这样简单的,即使最简单的内核模块都必须要考虑到某时刻会立即发生什么事情。

在内核编程中,有些并发执行的来源。通常,LINUX系统运行多个进程,可能会有多个进程在同一时刻要访问驱动。大部分驱动都能够中断处理器;中断处理程序异步执行,并且在驱动程序试图做其他事情的时候,中断可以被唤醒。有些精确的软件比如内核定时器也是异步执行。当然,LINUX可以在对称多处理器系统上运行,也就是说,驱动程序可能在不止一个CPU上并发执行。而且,2.6内核是抢占式内核,这样就导致了单一处理器和多处理器具有一样的并发执行问题。

因而LINUX内核代码和驱动代码都必须是“可重入”的,必须要能在同一时刻在不同上下文中运行。数据结构要小心的设计,使得多线程的分离,代码要小心的访问数据共享区域。

很多驱动程序员的一个共同错误就是:假定只要代码段没有进入睡眠状态,并发执行就不是一个问题。即使在以前的非抢占式内核中,这样的假设对于多处理器系统也是错误的。在2.6中,内核从来不会假定它可以一直占用处理器。所以在编写内核代码时,必须要有并发执行思想。

 

3. The Current Process

虽然内核不会像应用程序那样顺序执行,但是大部分内核执行都是代表某个进程的。内核代码通过全局变量current来访问当前进程。current指针指向当前正在运行的进程。

在类似open,read等系统调用的时候,当前进程指使用这些系统调用的进程。内核可以通过current指针来获取进程的更多信息。

 

4. A Few Other Details

应用程序被放置到一个非常大的堆栈区的虚拟内存空间。堆栈用来保存历史函数调用和正在使用的函数中的自动变量。而内核代码只有很小的一块堆栈区,甚至是单一的一个4096字节的页面。函数必须要和内核空间所有的调用链分享这一堆栈区。因而不要声明一个大的自动变量,如果需要大型数据结构,可以在调用的时候动态分配。

当我们查看内核API时,发现有些函数用双下划线__开始,这些函数都是低级组件接口,要谨慎使用。“如果你调用这些函数,你必须要知道你在做什么!”

内核代码不支持浮点操作,使用浮点操作需要保存和恢复处理器的入口和出口的状态,没有必要。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值