zoukankan      html  css  js  c++  java
  • 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”同它有关。

    嵌入式QQ交流群:127085086
  • 相关阅读:
    内网很安全?错错错!附攻击演示
    Fiddler无所不能——之测试开发攻城狮必备利器
    【橙子独创】【假设前置数据异常法】案列解析
    偶发异常BUG,如何高效精准分析排查定位?
    史上最全提现模块案例分解
    移动端推送测试涉及点
    模拟导入系统通讯录5000+手机号 校验批量数据处理是否正常?
    发散逆向思维之查询类列表测试范围的思考
    PICT工具一键生成正交试验用例
    据说黑白红客大多是出身测试行业,那么前戏如何做好呢?戳下
  • 原文地址:https://www.cnblogs.com/cslunatic/p/3028384.html
Copyright © 2011-2022 走看看