文件名称:
The Linux Kernel Module Programming Guide
开发工具:
文件大小: 1mb
下载次数: 0
上传时间: 2010-09-26
详细说明: 1、通过lsmod来获得内核已加载了那些模块,这个命令是读取/proc/modules文件的内容来获得信息的。 2、内核模块管理守护进程kmod执行modprobe去加载内核模块。modprobe的功能和insmod类似,但是它除了装入指定模块外,还同时装入指定模块所依赖的其他模块。 3、如果内核中打开了CONFIG_MODVERSIONS选项,则为某个指定版本内核编译的模块将不能被另一版本的内核加载。所以在开发的工程中,最好将内核中的这个选项关闭。 4、建议在控制台下输入文档中的范例代码,编译然后加载模块,而不是在X下。这个可及时读取加载模块时的日志信息。 5、模块初始化函数(module_init)应该返回值为0,非0则表明初始化失败,该模块将不能被加载。 6、任一个内核模块需要包含linux/module.h。我们仅仅需要包含linux/kernel.h当需要使用printk()记录级别的宏扩展时KERN_ALERN。 7、printk()并不是设计用于用户交互的,它实际上用来为内核提供日志功能,记录模块信息和给出警告。它定义了八个优先级。我们可以使用KERN_ALERT这样的高优先级,来确保printk()将信息输出到控制台而不是添 加到日志中。 8、关于宏__init和__exit。它们负责“初始化”和“清理收尾”的函数定义处的变化。如果模块是被编译到内核,而不是动态加载,__init会使初始化完成后丢弃该函数并收回所占的内存(__initdata的作用与__init类似,只不过对变量有效),__exit则将会忽略该收尾函数。 9、如果一个模块未定义清除函数,则内核不允许卸载该模块。 10、#include linux/sched.h> 最重要的头文件之一。包含驱动程序使用的大部分内核API的定义,包括睡眠函数以及各种变量声明。 ------------------------------------------------------------------------ struct task_struct *current;当前进程。current->pid、current->comm:当前进程的进程ID和命令名。 ------------------------------------------------------------------------ #include linux/module.h> 必需的头文件,必须包含在模块源代码中。 ------------------------------------------------------------------------ #include linux/moduleparam.h> module_param(variable, type, perm);//perm表示该变量的用户许可。 用于创建模块参数的宏,用户可在装载模块时调整这些参数的值。参数类型可以是:bool、charp、int、invbool、long、short、ushort、uint、ulong或者intarray。 ------------------------------------------------------------------------ #include linux/kernel.h> int printk(const char * fmt, ...); 函数printf的内核代码。 ------------------------------------------------------------------------ #include linux/types.h> dev_t是内核中用来表示设备编号的数据类型。 ------------------------------------------------------------------------ #include 该头文件声明了在内核代码和用户空间之间移动数据的函数。 unsigned long copy_from_user(void *to, const void *from, unsigned long count); unsigned long copy_to_user(void *to, const void *from, unsigned long count); 11、kmalloc/kfree() 原型:void kmalloc(size_t size, int priority); 最大可开辟128k内存。priority可为GFP_KERNEL表示等待,GFP_ATOMIC表示不等待,如果分配不成功,返回0. 12、结构体file_operations在头文件linux/fs.h中定义,用于存储驱动模块提供的对设备各种操作的函数指针。C99有这个结构体的扩展,EX: struct file_operations xxx_fops = { .read = xxx_read, .write = xxx_write, .open = xxx_open, .release = xxx_release }; ----------------------------------------------------------------------------------- struct file结构体:每一个设备文件都代表着内核中的一个file结构体,该结构体在头文件linux/fs.h中定义。指向该结构体struct file的指针一般命名为filp。它内核在open时创建,并传递给在该文件上进行操作的所有函数,直到最后的close函数。 ----------------------------------------------------------------------------------- struct inode结构体:内核用inode结构在内部表示文件,而file结构体表示打开的文件描述符。它包含了大量的有关文件的信息,但是只有以下两个字段对编写驱程有关: dev_t i_rdev;//对表示设备文件的inode结构,包含了真正的设备编号。 struct cdev *i_cdev;//表示字符设备的内核的内部结构。 13、请求一个字符设备编号。 int register_chrdev_region(dev_t first, unsigned int count, char *name); first是要分配的设备编号范围的起始值。count是所请求的连续设备编号的个数。name是和该编号范围关联的设备名称,它将出现在/proc/devices和sysfs中。 和大部分内核函数一样,register_chrdev_region的返回值在分配成功返回0.失败时返回一个负的错误码。 ------------------------------------------------------------------------ 设备编号的动态分配 int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name); dev为仅用于输出的参数。firstminor是被请求的第一个次设备号,通常为0. ------------------------------------------------------------------------ 释放设备编号 void unregister_chrdev_region(dev_t first, unsigned int count); 通常我们在模块的清除函数中调用该函数。 ----------------------------------------------------------------------------------- 一个通俗获取主设备号的代码如下: if (xxx_major){ dev = MKDEV(xxx_major, xxx_minor); result = register_chrdev_region(dev, xxx_nr_devs, "xxx"); } else { result = alloc_chrdev_region(&dev, xxx_minor, xxx_nr_devs, "xxx"); xxx_major = MAJOR(dev); } if (result printk(KERN_WARNING "xxx: can't get major %d\n", xxx_major); return result; } 14、字符设备的注册。我们代码中应该包含头文件linux/cdev.h。其中的一个例子如下: static void xxx_setup_cdev(struct xxx_dev *dev, int index) { int err, devno = MKDEV(xxx_major, xxx_minor + index); cdev_init(&dev->cdev, &xxx_fops); dev->cdev.owner = THIS_MODULE; dev->cdev.ops = &xxx_fops; err = cdev_add(&dev->cdev, devno, 1); if (err) printk(KERN_NOTICE "Error %d adding xxx%d", err, index); } 至于早期那种经典注册一个字符设备驱动程序则不应该再使用。 int register_chrdev(unsigned int major const char *name, struct file_operations *fops); int unregister_chrdev(unsigned int major, const char *name); 15、信号量与互斥体,头文件asm/semaphore.h。一个信号量本质上是一个整数值,它和一对函数联合使用,这对函数通常成为P和V。希望进入临界区的进程将在相关信号量上调用P。如果信号量的值大于0,则该值会减一,而进程得以继续;如果信号量的值为0,进程必须等待直到其他人释放该信号量。对信号量的解锁通过调用 V完成;该函数对信号量的值做加一操作,并在必要时唤醒等待的进程。 信号量的初始化: DECLARE_MUTEX(name);//一个称为name的信号量被初始化为1 DECLARE_MUTEX_LOCKED(name);一个称为name的信号量被初始化为0 P函数称为down: void down(struct semaphore *sem);//down减少信号量的值,并在必要时一直等待 int down_interruptible(struct semaphore *sem);//down_interruptible完成相同工作,它允许等待在某个信号量上的用户空间进程可被用户中断 int down_trylock(struct semaphore *sem);//down_trylock不会休眠,如果信号量在调用时不可获得,会立即返回一个非零值 作为通常的规则,我们不应该使用非中断操作。使用down_interruptible需要小心,如果操作被中断,该函数会返回非零值,而调用者不会拥有该信号量。对down_interruptible的正确使用需要始终检查返回值,并作成相应响应。 V函数称为up: void up(struct semaphore *sem); 16、自旋锁,头文件linux/spinlock.h。和信号量不同,自旋锁可在不能休眠的代码中使用,比如中断处理例程(因为信号量会引起休眠)。如果锁可用,则“锁定”位被设置,而代码继续进入临界区;相反,如果锁被其他人获得,则代码进入忙循环并重复检查该锁,直到该锁可用为止。“测试并设置”的操作必须以原子的方式完成。 自旋锁的初始化: spinlock_t xxx_lock = SPIN_LOCK_UNLOCKED; 自旋锁的获得: void spin_lock(spinlock_t *lock);//自旋锁的等待本质上不可中断,一旦调用spin_lock,在获得锁之前将一直处于自旋状态 void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);//在获得锁之前禁止中断,而之前的中断状态保存到flags void spin_lock_irq(spinlock_t *lock);//确保释放自旋锁时应该启用中断 void spin_lock_bh(spinlock_t *lock);//在获得锁之前禁止软件中断,但是会让硬件中断保持打开 自旋锁的释放: void spin_unlock(spinlock_t *lock); void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags); void spin_unlock_irq(spinlock_t *lock); void spin_unlock_bh(spinlock_t *lock); 适用自旋锁的核心规则是:任何拥有自旋锁的代码都必须是原子的,它不能休眠。事实上,它不能因为任何原因放弃处理器,除了服务中断以外(某些情况下此时也不能放弃处理器)。任何时候,只要内核代码拥有自旋锁,在相关的处理器上的抢占就会被禁止。自旋锁必须在可能的最短时间内拥有。 17、休眠。永远不要在原子上下文中进入休眠。这意味着:我们的驱动程序不能在拥有自旋锁、seqlock或者RCU锁时休眠;如果我们已经禁止了中断,也不能休眠。同时我们对唤醒之后的状态不能做任何假定,因此必须检查以确保我们的等待的条件真正为真。 初始化一个等待队列头: DECLARE_WAIT_QUEUE_HEAD(name); 当进程休眠时,它将期待某个条件会在未来成真。当一个休眠进程被唤醒时,它必须再次检查它所等待的条件的确为真。wait_event: wait_event(queue, condition);//进程将被置于非中断休眠,通常不是我们所期望的。 wait_event_interruptible(quene, condition);//可被中断信号打断。这可返回一个整数值,非零值表示休眠被某个信号打断。 wait_event_timeout(queue, condition, timeout);//只会等待限定的时间 wait_event_interruptible_timeout(queue, condition, timeout);//只会等待限定的时间 我们的进程正在休眠中,用来唤醒休眠进程的基本函数是:wake_up void wake_up(wait_queue_head_t *queue); void wake_up_interruptible(wait_queue_head_t *queue); //wake_up会唤醒等待在给定queue上的所有进程,wake_up_interruptible只会唤醒那些可中断休眠的进程 ...展开收缩
(系统自动生成,下载前可以参看下载内容)
下载文件列表
相关说明
- 本站资源为会员上传分享交流与学习,如有侵犯您的权益,请联系我们删除.
- 本站是交换下载平台,提供交流渠道,下载内容来自于网络,除下载问题外,其它问题请自行百度。
- 本站已设置防盗链,请勿用迅雷、QQ旋风等多线程下载软件下载资源,下载后用WinRAR最新版进行解压.
- 如果您发现内容无法下载,请稍后再次尝试;或者到消费记录里找到下载记录反馈给我们.
- 下载后发现下载的内容跟说明不相乎,请到消费记录里找到下载记录反馈给我们,经确认后退回积分.
- 如下载前有疑问,可以通过点击"提供者"的名字,查看对方的联系方式,联系对方咨询.