linux 比较完整的字符型驱动(一)
本文是基于linux-2.6.37,有一些内核的API可能不完全一致,需要自己查找,解决,写本文的目的是方便自己以后查找。
本文的Code写了一个简单的字符串,实现了阻塞和非阻塞,自旋锁,信号量,并发,轮询,sysfs接口,proc接口,devfs设备接点,module_init调用过程,异步通知,内核定时器,内核模块参数,内核模块导出。
CSlunatic_drv.h
#ifndef __CSLUNATIC_H__ #define __CSLUNATIC_H__ #include <linux/cdev.h> #include <linux/semaphore.h> #include <linux/wait.h> #include <linux/fs.h> #define CSLUNATIC_DEVICE_NODE_NAME "CSlunatic" #define CSLUNATIC_DEVICE_FILE_NAME "CSlunatic" #define CSLUNATIC_DEVICE_PROC_NAME "CSlunatic" #define CSLUNATIC_DEVICE_CLASS_NAME "CSlunatic" #define MAX_FIFO_BUF 16 //缓冲区的大小 struct CSlunatic_char_dev { int val; int timer_interval; struct semaphore sem;/*信号量*/ struct timer_list timer;/*定时器*/ unsigned char buf[MAX_FIFO_BUF]; //缓冲区 unsigned int current_len; wait_queue_head_t r_wait;/*读等待队列*/ wait_queue_head_t w_wait; /*写等待队列*/ struct cdev dev;/*字符设备结构体*/ struct fasync_struct *async_queue;/*异步结构体指针,用于读*/ }; #endif
CSlunatic_drv.c
#include <linux/init.h> #include <linux/module.h> #include <linux/types.h> #include <linux/proc_fs.h> #include <linux/device.h> #include <linux/sched.h> #include <linux/poll.h> #include <asm/uaccess.h> #include "cslunatic_drv.h"/*主设备和从设备号变量*/ #define DEFAULT_INTERVAL 500 #define MEM_CLEAR 0x1 static int CSlunatic_major = 0; static int CSlunatic_minor = 0;/*设备类别和设备变量*/ static struct class *CSlunatic_class = NULL; static struct CSlunatic_char_dev *CSlunatic_dev = NULL;/*传统的设备文件操作方法*/ static int int_var = 0; static const char *str_var = "default"; static int int_array[6]; int narr; /*定时器处理函数*/ static void CSlunatic_timer_hander(unsigned long args) { int i; struct CSlunatic_char_dev *dev; printk("%s enter.\n", __func__); dev = (struct CSlunatic_char_dev *)args; /*同步访问*/ if(down_interruptible(&(dev->sem))) { return; } for(i =0; i < 4; i++){ dev->buf[i] = i; } dev->current_len += i; if(dev->current_len > MAX_FIFO_BUF){ dev->current_len = MAX_FIFO_BUF; } wake_up_interruptible(&dev->r_wait); if(dev->timer_interval > 0) mod_timer(&dev->timer, jiffies + dev->timer_interval);/*修改定时器的到期时间*/ up(&(dev->sem)); printk("%s leave.\n", __func__); } /*打开设备方法*/ static int CSlunatic_open(struct inode* inode, struct file* filp) { struct CSlunatic_char_dev *dev; /*将自定义设备结构体保存在文件指针的私有数据域中,以便访问设备时拿来用*/ dev = container_of(inode->i_cdev, struct CSlunatic_char_dev, dev); init_timer(&dev->timer);/*初始化定时器*/ dev->timer.function = CSlunatic_timer_hander; /*定时器操作函数*/ dev->timer.expires = jiffies + dev->timer_interval; /*定时时间,*/ dev->timer.data = (unsigned long)dev; /*传递给定时器操作函数函数的参数*/ add_timer(&dev->timer);/*将定时器加到定时器队列中*/ filp->private_data = dev; return 0; } /*处理FASYNC标志变更*/ static int CSlunatic_fasync(int fd, struct file *filp, int mode) { struct CSlunatic_char_dev *dev = filp->private_data; /*将该设备登记到fasync_queue队列中去,也可以从列表中删除*/ return fasync_helper(fd, filp, mode, &(dev->async_queue)); } /*设备文件释放时调用*/ static int CSlunatic_release(struct inode *inode, struct file *filp) { struct CSlunatic_char_dev *dev; dev = (struct CSlunatic_char_dev*)filp->private_data; del_timer(&dev->timer); /*将文件从异步通知列表中删除*/ CSlunatic_fasync(-1, filp, 0); printk("CSlunatic device close success!\n"); return 0; } /*读取设备的缓冲区的值 linux驱动中断屏蔽local_irq_disable(屏蔽中断),local_irq_enable(开中断) linux 驱动阻塞wait_event_interruptible()(睡眠) wake_up_interruptible() (唤醒), linux驱动的同步down_interruptible()(获取信号量) up(释放信号量), linux驱动的互斥spin_lock(获取自旋锁),spin_unlock(释放自旋锁), 进程尽量不要嵌套互斥锁(或者自旋锁) */ static ssize_t CSlunatic_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos) { ssize_t err = 0; struct CSlunatic_char_dev *dev = filp->private_data; /*同步访问 当获取信号量成功时,对信号量的值减一。else表示获取信号量失败,此时进程进入睡眠状态,并将此进程插入到等待队列尾部 */ if(down_interruptible(&(dev->sem))) { return -ERESTARTSYS; } while(dev->current_len == 0){ if(filp->f_flags & O_NONBLOCK){ err = -EAGAIN; goto out; } up(&(dev->sem)); /*为了将进程以一种安全的方式进入休眠,我们需要牢记两条规则: 一、永远不要在原子上下文中进入休眠。 二、进程休眠后,对环境一无所知。唤醒后,必须再次检查以确保我们等待的条件真正为真简单休眠 */ /*条件为真,当前进程的状态设置成TASK_INTERRUPTIBLE,然后调用schedule(), 而schedule()会将位于TASK_INTERRUPTIBLE状态的当前进程从runqueue 队列中删除。 从runqueue队列中删除的结果是,当前这个进程将不再参 与调度,除非通过其他函数将这个进程重新放入这个runqueue队列中 */ if (wait_event_interruptible(dev->r_wait, dev->current_len == 0)){ return -ERESTARTSYS; } if(down_interruptible(&(dev->sem))) { return -ERESTARTSYS; } } if(count > dev->current_len) count = dev->current_len; /*将缓冲区buf的值拷贝到用户提供的缓冲区*/ if(copy_to_user(buf, dev->buf, count)){ err = -EFAULT; goto out; } wake_up_interruptible(&dev->w_wait); /*产生异步读信号 这样后我们在需要的地方(比如中断)调用下面的代码,就会向fasync_queue队列里的设备发送SIGIO 信号,应用程序收到信号,执行处理程序 注意, 一些设备还实现异步通知来指示当设备可被写入时; 在这个情况, 当然, kill_fasnyc 必须被使用一个 POLL_OUT 模式来调用.*/ if(dev->async_queue) kill_fasync(&(dev->async_queue), SIGIO, POLL_IN);/*释放信号用的函数*/ err = count; out: up(&(dev->sem)); return err; } /*写设备的缓冲区buf*/ static ssize_t CSlunatic_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos) { struct CSlunatic_char_dev *dev = filp->private_data; ssize_t err = 0; /*同步访问*/ if(down_interruptible(&(dev->sem))) { return -ERESTARTSYS; } while(dev->current_len == MAX_FIFO_BUF){ if(filp->f_flags & O_NONBLOCK){ err = -EAGAIN; goto out; } up(&(dev->sem)); if (wait_event_interruptible(dev->w_wait, dev->current_len == 0)){ err = -ERESTARTSYS; goto out; } if(down_interruptible(&(dev->sem))) { return -ERESTARTSYS; } } if(count > (MAX_FIFO_BUF - dev->current_len)) { count = MAX_FIFO_BUF-dev->current_len; } /*将用户提供的缓冲区的值写到设备寄存器去*/ if(copy_from_user(dev->buf + dev->current_len, buf, count)) { err = -EFAULT; goto out; } dev->current_len += count; /*条件为真,且需要唤醒进程*/ wake_up_interruptible(&(dev->r_wait)); err = count; out: up(&(dev->sem)); return err; } /*驱动的ioctl实现*/ static long CSlunatic_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct CSlunatic_char_dev *dev = filp->private_data; switch(cmd){ case MEM_CLEAR: if(down_interruptible(&(dev->sem))) return -ERESTARTSYS; memset(dev->buf, 0, MAX_FIFO_BUF); up(&(dev->sem)); printk(KERN_INFO "globamem is set to zero \n"); break; default: return -EINVAL; } } /*非阻塞的时候轮询操作*/ static unsigned int CSlunatic_poll(struct file *filp, poll_table *wait) { unsigned int mask = 0; struct CSlunatic_char_dev *dev = filp->private_data; // 获得设备结构指针 if( down_interruptible(&(dev->sem))); return -ERESTARTSYS; /*加入这两句话是为了在读写状态发生变化的时候,通知核心层,让核心层重新调用poll函数查询信息。 也就是说这两句只会在select阻塞的时候用到。当利用select函数发现既不能读又不能写时,select 函数会阻塞,但是此时的阻塞并不是轮询,而是睡眠,通过下面两个队列发生变化时通知select*/ poll_wait(filp, &dev->r_wait, wait); // 加读等待队列头 poll_wait(filp, &dev->w_wait, wait); // 加写等待队列头 if(dev->current_len != 0) // 可读 { mask |= POLLIN | POLLRDNORM; // 标示数据可获得 } if(dev->current_len != MAX_FIFO_BUF) // 可写 { mask |= POLLOUT | POLLWRNORM; // 标示数据可写入 } up(&(dev->sem)); return mask; } /*读取寄存器val的值到缓冲区buf中,内部使用*/ static ssize_t __CSlunatic_get_val(struct CSlunatic_char_dev *dev, char *buf) { int val = 0; /*同步访问*/ if(down_interruptible(&(dev->sem))) { return -ERESTARTSYS; } val = dev->val; up(&(dev->sem)); return snprintf(buf, PAGE_SIZE, "%d\n", val); } /*把缓冲区buf的值写到设备寄存器val中去,内部使用*/ static ssize_t __CSlunatic_set_val(struct CSlunatic_char_dev *dev, const char *buf, size_t count) { int val = 0; /*将字符串转换成数字*/ val = simple_strtol(buf, NULL, 10); /*這個函数的功能就是获得信号量,如果得不到信号量就睡眠,此時沒有信號打斷打断,那么进 入睡眠。但是在睡眠过程中可能被信号量打断,打断之后返回-EINTR,主要用来进程间的互斥同步。 使用可被中断的信号量版本的意思是,万一出現了semaphore的死锁,还有机会用ctrl+c发出软中断, 让等待这个等待内核驱动返回的用用户态进程退出。而不是把整个系統都锁住了。*/ if(down_interruptible(&(dev->sem))) { return -ERESTARTSYS; } dev->val = val; up(&(dev->sem)); return count; } /*读取设备寄存器val的值,保存在page缓冲区中*/ static ssize_t CSlunatic_proc_read(char *page, char **start, off_t off, int count, int *eof, void *data) { if(off > 0) { *eof = 1; return 0; } return __CSlunatic_get_val(CSlunatic_dev, page); } /*把缓冲区的值buff保存到设备寄存器val中去*/ static ssize_t CSlunatic_proc_write(struct file *filp, const char __user *buff, unsigned long len, void *data) { int err = 0; char* page = NULL; if(len > PAGE_SIZE) { printk(KERN_ALERT"The buff is too large: %lu.\n", len); return -EFAULT; } page = (char*)__get_free_page(GFP_KERNEL); if(!page) { printk(KERN_ALERT"Failed to alloc page.\n"); return -ENOMEM; } /*先把用户提供的缓冲区值拷贝到内核缓冲区中去*/ if(copy_from_user(page, buff, len)) { printk(KERN_ALERT"Failed to copy buff from user.\n"); err = -EFAULT; goto out; } err = __CSlunatic_set_val(CSlunatic_dev, page, len); out: free_page((unsigned long)page); return err; } /*读取设备属性val*/ static ssize_t CSlunatic_val_show(struct device *dev, struct device_attribute *attr, char *buf) { struct CSlunatic_char_dev* hdev = (struct CSlunatic_char_dev*)dev_get_drvdata(dev); return __CSlunatic_get_val(hdev, buf); } /*写设备属性val*/ static ssize_t CSlunatic_val_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct CSlunatic_char_dev *hdev = (struct CSlunatic_char_dev*)dev_get_drvdata(dev); return __CSlunatic_set_val(hdev, buf, count); } /*函数宏DEVICE_ATTR内封装的是__ATTR(_name,_mode,_show,_stroe)方法,_show表示的是读方法,_stroe表示的是写方法。 sysfs接口*/ static DEVICE_ATTR(val, S_IRUGO | S_IWUSR, CSlunatic_val_show, CSlunatic_val_store); /*创建/proc/CSlunatic文件*/ static void CSlunatic_create_proc(void) { struct proc_dir_entry *entry; entry = create_proc_entry(CSLUNATIC_DEVICE_PROC_NAME, 0, NULL); if(entry) { // entry->owner = THIS_MODULE; entry->read_proc = CSlunatic_proc_read; entry->write_proc = CSlunatic_proc_write; } } /*删除/proc/CSlunatic文件*/ static void CSlunatic_remove_proc(void) { remove_proc_entry(CSLUNATIC_DEVICE_PROC_NAME, NULL); } /*访问设置属性方法*/ static struct file_operations CSlunatic_fops = { .owner = THIS_MODULE, .open = CSlunatic_open, .release = CSlunatic_release, .read = CSlunatic_read, .write = CSlunatic_write, .unlocked_ioctl = CSlunatic_ioctl,/*在kernel 2.6.37 中已经完全删除了struct file_operations 中的ioctl 函数指针,取而代之的是unlocked_ioctl */ .fasync = CSlunatic_fasync, .poll = CSlunatic_poll, }; /*初始化设备*/ static int __CSlunatic_setup_dev(struct CSlunatic_char_dev *dev) { int err; dev_t devno = MKDEV(CSlunatic_major, CSlunatic_minor); /*如果cdev 没有分配空间,可以调用cdev_alloc来分配空间*/ memset(dev, 0, sizeof(struct CSlunatic_char_dev)); cdev_init(&(dev->dev), &CSlunatic_fops); dev->dev.owner = THIS_MODULE; dev->dev.ops = &CSlunatic_fops; /*注册字符设备*/ err = cdev_add(&(dev->dev),devno, 1); if(err) { return err; } /*初始化信号量 linux 2.6.37 没有init_MUTEX()函数 #define init_MUTEX(sem) sema_init(sem, 1)*/ sema_init(&(dev->sem), 1); /*初始化等待队列*/ init_waitqueue_head(&(dev->r_wait)); init_waitqueue_head(&(dev->w_wait)); /*寄存器val的值*/ dev->val = 0; /*定时器间隔10 S*/ dev->timer_interval = 10 * HZ; return 0; } /*模块加载方法 insmod cslunatic_drv.ko int_var=100 str_var=hello int_array=100,200 * modinfo cslunatic_drv.ko 查看模块信息 * lsmod 查看已加载的模块 * modprobe 挂载新模块以及新模块相依赖的模块,若在载入过程中发生错误,在modprobe会卸载整组的模块。 * modprobe 命令是根据depmod -a的输出/lib/modules/version/modules.dep来加载全部的所需要模块。 * modprobe -r 模块名也可以卸载模块 * rmmod cslunatic_drv.ko 卸载模块 * dmesg | tail -8 查看输出的末尾8行信息 */ static int __init CSlunatic_init(void) { int err = -1; int i = 0; dev_t dev = 0; struct device *temp = NULL; printk(KERN_ALERT"Initializing CSlunatic device.\n"); printk(KERN_ALERT "int_var %d.\n", int_var); printk(KERN_ALERT "str_var %s.\n", str_var); for(i = 0; i < narr; i ++){ printk("int_array[%d] = %d\n", i, int_array[i]); } if (!CSlunatic_major) { /*动态设备号分配*/ err = alloc_chrdev_region(&dev, 0, 1, CSLUNATIC_DEVICE_NODE_NAME); if (!err) { CSlunatic_major = MAJOR(dev); CSlunatic_minor = MINOR(dev); } } else { /*静态设备号分配*/ dev = MKDEV(CSlunatic_major, CSlunatic_minor); err = register_chrdev_region(dev,1, CSLUNATIC_DEVICE_NODE_NAME); } if(err < 0) { printk(KERN_ALERT"Failed to alloc char dev region.\n"); goto fail; } /*分配CSlunatic设备结构体变量*/ CSlunatic_dev = kmalloc(sizeof(struct CSlunatic_char_dev), GFP_KERNEL); if(!CSlunatic_dev) { err = -ENOMEM; printk(KERN_ALERT"Failed to alloc CSlunatic_dev.\n"); goto unregister; } /*初始化设备*/ err = __CSlunatic_setup_dev(CSlunatic_dev); if(err) { printk(KERN_ALERT"Failed to setup dev: %d.\n", err); goto cleanup; } /*在/sys/class/目录下创建设备类别目录CSlunatic*/ CSlunatic_class = class_create(THIS_MODULE, CSLUNATIC_DEVICE_CLASS_NAME); if(IS_ERR(CSlunatic_class)) { err = PTR_ERR(CSlunatic_class); printk(KERN_ALERT"Failed to create CSlunatic class.\n"); goto destroy_cdev; } /* 在/dev/目录和/sys/class/CSlunatic目录下分别创建设备文件CSlunatic* 也可以手工(mknod /dev/fgj c 50 0) 创建设备节点 当使用udev制作的文件系统时,内核为我们提供了一组函数,可以用来在模块加载的时候自动在/dev目录下创建设备文件OR在卸载时自动删除设备文件。 主要涉及两个函数 class_create(owner, class_name) 用来在sysfs创建一个类,设备信息导出在这下面; struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...) 用来在/dev目录下创建一个设备文件; 加载模块时,用户空间的udev会自动响应device_create()函数,在sysfs下寻找对应的类从而创建设备节点。 */ temp = device_create(CSlunatic_class, NULL, dev, "%s", CSLUNATIC_DEVICE_FILE_NAME); if(IS_ERR(temp)) { err = PTR_ERR(temp); printk(KERN_ALERT"Failed to create CSlunatic device."); goto destroy_class; } /*在/sys/class/CSlunatic/CSlunatic目录下创建属性文件val*/ err = device_create_file(temp, &dev_attr_val); if(err < 0) { printk(KERN_ALERT"Failed to create attribute val."); goto destroy_device; } /*设置设备的私有数据dev_get_drvdata 获取设备的相关信息 如果在这里没有设置,也可以在open函数的时候设置*/ dev_set_drvdata(temp, CSlunatic_dev); /*创建/proc/CSlunatic文件*/ CSlunatic_create_proc(); printk(KERN_ALERT"Succedded to initialize CSlunatic device.\n"); return 0; destroy_device: device_destroy(CSlunatic_class, dev); destroy_class: class_destroy(CSlunatic_class); destroy_cdev: cdev_del(&(CSlunatic_dev->dev)); cleanup: kfree(CSlunatic_dev) ; unregister: unregister_chrdev_region(MKDEV(CSlunatic_major, CSlunatic_minor), 1); fail: return err; } /*模块卸载方法*/ static void __exit CSlunatic_exit(void) { dev_t devno = MKDEV(CSlunatic_major, CSlunatic_minor); printk(KERN_ALERT"Destroy CSlunatic device.\n"); /*删除/proc/CSlunatic文件*/ CSlunatic_remove_proc(); /*销毁设备类别和设备*/ if(CSlunatic_class) { device_destroy(CSlunatic_class, MKDEV(CSlunatic_major, CSlunatic_minor)); class_destroy(CSlunatic_class); } /*删除字符设备和释放设备内存*/ if(CSlunatic_dev) { cdev_del(&(CSlunatic_dev->dev)); kfree(CSlunatic_dev); } /*释放设备号*/ unregister_chrdev_region(devno, 1); } /*module_init中的初始化函数在系统启动过程中start_kernel()->rest_init()->kernel_init()->do_basic_setup()->do_initcalls()。 do_initcalls调用.initcall.init节的函数指针,初始化内核模块。 #define module_init(x) __initcall(x); #define __initcall(fn) device_initcall(fn) #define device_initcall(fn) __define_initcall("6",fn) 看出module_init是设备驱动的初始化,等级为6(也就是初始化的顺序为6) #define core_initcall(fn) __define_initcall("1",fn) #define postcore_initcall(fn) __define_initcall("2",fn) #define arch_initcall(fn) __define_initcall("3",fn) #define subsys_initcall(fn) __define_initcall("4",fn) #define fs_initcall(fn) __define_initcall("5",fn) #define device_initcall(fn) __define_initcall("6",fn) #define late_initcall(fn) __define_initcall("7",fn) */ module_init(CSlunatic_init); module_exit(CSlunatic_exit); /* * EXPORT_SYMBOL只出现在2.6内核中,在2.4内核默认的非static 函数和变量都会自动导入到kernel 空间的, 都不用EXPORT_SYMBOL() 做标记的。 * 2.6就必须用EXPORT_SYMBOL() 来导出来(因为2.6默认不到处所有的符号)。 * EXPORT_SYMBOL标签内定义的函数或者符号对全部内核代码公开,不用修改内核代码就可以在您的内核模块中直接调用,即使用 * EXPORT_SYMBOL可以将一个函数以符号的方式导出给其他模块使用。 * 这里要和System.map做一下对比: * System.map 中的是连接时的函数地址。连接完成以后,在2.6内核运行过程中,是不知道哪个符号在哪个地址的。 * EXPORT_SYMBOL的符号,是把这些符号和对应的地址保存起来,在内核运行的过程中,可以找到这些符号对应的地址。而模块在加载过程中,其本质就 * 是能动态连接到内核,如果在模块中引用了内核或其它模块的符号,就要EXPORT_SYMBOL这些符号,这样才能找到对应的地址连接。 * 使用方法 * 第一、在模块函数定义之后使用EXPORT_SYMBOL(函数名) * 第二、在调用该函数的模块中使用extern对之声明 * 第三、首先加载定义该函数的模块,再加载调用该函数的模块 * 另外,在编译调用某导出函数的模块时,往往会有WARNING: "****" [**********] undefined! * 使用dmesg 命令后会看到相同的信息。开始我以为只要有这个错误就不能加载模块,后来上网查了一下, * 发现这主要是因为在编译连接的时候还没有和内核打交 道,当然找不到symbol了,但是由于你生成的是一个内核模块, * 所以ld 不提示error,而是给出一个warning,寄希望于在 insmod的时 候,内核能够把这个symbol连接上。 */ void CSlunatic_fun(void) { printk("CSlunatic_fun \n"); } EXPORT_SYMBOL(CSlunatic_fun); /*_GPL版本的宏定义只能使符号对GPL许可的模块可用*/ void CSlunatic_fun_GPL(void) { printk("hello wold again\n"); } EXPORT_SYMBOL_GPL(CSlunatic_fun_GPL); /* * module_param() 和 module_param_array() 的作用就是让那些全局变量对 insmod 可见,使模块装载时可重新赋值。 * module_param_array() 宏的第三个参数用来记录用户 insmod 时提供的给这个数组的元素个数,NULL 表示不关心用户提供的个数 * module_param() 和 module_param_array() 最后一个参数权限值不能包含让普通用户也有写权限,否则编译报错。这点可参考 linux/moduleparam.h 中 __module_param_call() 宏的定义。 * 通过宏MODULE_PARM_DESC()对参数进行说明 * 通过宏module_param_array_named()使得内部的数组名与外部的参数名有不同的名字。 * 通过宏module_param_string()让内核把字符串直接复制到程序中的字符数组内。 * 字符串数组中的字符串似乎不能包含逗号,否则一个字符串会被解析成两个 */ module_param(int_var, int, 0644); MODULE_PARM_DESC(int_var, "A integer variable"); module_param(str_var, charp, 0644); MODULE_PARM_DESC(str_var, "A string variable"); module_param_array(int_array, int, &narr, 0644); MODULE_PARM_DESC(int_array, "A integer array"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("CSlunatic"); MODULE_DESCRIPTION("Linux Device Driver");
Makefile的实现:
#用+= 可以实现多个模块同时编译 #obj-m += mod.o obj-m += cslunatic_drv.o #设置你嵌入式开发板的内核源码路径,主要是内核的include目录下的头文件 KDIR = /opt/DM8168_DVRRDK_V03.00.00.00/ti_tools/linux_lsp/linux-psp-dvr-04.04.00.01/src/linux-04.04.00.01 #KDIR = libmodules$(shell uname -r)build PWD = $(shell pwd) default: $(MAKE) -C$(KDIR) CROSS_COMPILE=/opt/armgcc/bin/arm-none-linux-gnueabi- ARCH=arm SUBDIRS=$(PWD) modules # $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules clean: $(MAKE) -C$(KDIR) CROSS_COMPILE=/opt/armgcc/bin/arm-none-linux-gnueabi- ARCH=arm SUBDIRS=$(PWD) clean rm -rf Module.markers Modules.order Moduke.symvers
linux驱动的入口函数module_init的加载和释放:
#define module_init(x) __initcall(x); //include/linux/init.h #define __initcall(fn) device_initcall(fn) #define device_initcall(fn) __define_initcall("6",fn,6) #define __define_initcall(level,fn,id) / static initcall_t __initcall_##fn##id __used / __attribute__((__section__(".initcall" level ".init"))) = fn
如果某驱动想以func作为该驱动的入口,则可以如下声明:module_init(func);被上面的宏处理过后,变成__initcall_func6 __used加入到内核映像的".initcall"区。内核的加载的时候,会搜索".initcall"中的所有条目,并按优先级加载它们,普通驱动程序的优先级是6。其它模块优先级列出如下:值越小,越先加载。
#define pure_initcall(fn) __define_initcall("0",fn,0) #define core_initcall(fn) __define_initcall("1",fn,1) #define core_initcall_sync(fn) __define_initcall("1s",fn,1s) #define postcore_initcall(fn) __define_initcall("2",fn,2) #define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s) #define arch_initcall(fn) __define_initcall("3",fn,3) #define arch_initcall_sync(fn) __define_initcall("3s",fn,3s) #define subsys_initcall(fn) __define_initcall("4",fn,4) #define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s) #define fs_initcall(fn) __define_initcall("5",fn,5) #define fs_initcall_sync(fn) __define_initcall("5s",fn,5s) #define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs) #define device_initcall(fn) __define_initcall("6",fn,6) #define device_initcall_sync(fn) __define_initcall("6s",fn,6s) #define late_initcall(fn) __define_initcall("7",fn,7) #define late_initcall_sync(fn) __define_initcall("7s",fn,7s)
可以看到,被声明为pure_initcall的最先加载。
module_init除了初始化加载之外,还有后期释放内存的作用。linux kernel中有很大一部分代码是设备驱动代码,这些驱动代码都有初始化和反初始化函数,这些代码一般都只执行一次,为了有更有效的利用内存,这些代码所占用的内存可以释放出来。
linux就是这样做的,对只需要初始化运行一次的函数都加上__init属性,__init 宏告诉编译器如果这个模块被编译到内核则把这个函数放到(.init.text)段,module_exit的参数卸载时同__init类似,如果驱动被编译进内核,则__exit宏会忽略清理函数,因为编译进内核的模块不需要做清理工作,显然__init和__exit对动态加载的模块是无效的,只支持完全编译进内核。
在kernel初始化后期,释放所有这些函数代码所占的内存空间。连接器把带__init属性的函数放在同一个section里,在用完以后,把整个section释放掉。当函数初始化完成后这个区域可以被清除掉以节约系统内存。Kenrel启动时看到的消息“Freeing unused kernel memory: xxxk freed”同它有关。