Linux_7th_第1个linux驱动___给驱动模块上户口(二)

从这篇博文开始,我们终于可以给我们的驱动模块first_drv一个正式的编制了,现在他已经填好了自己的户口登记信息表,距离拿户口本儿只有一步了!


内核内部有自己的一套安装驱动模块的方法,就像是民政局工作人员知道怎么给一个人办户口一样,对于一个普通公民来说,工作人员做了哪些操作办好了户口本,并不是普通公民需要关心的,我们最关心的是如何把我们填好的户口信息登记表交给工作人员,如何向工作人员表明我们有办户口的请求。


在linux系统中,对于字符设备驱动,我们通过register_chrdev这个函数来向内核注册驱动模块,我们希望在执行insmod first_drv.ko,也就是在调用first_drv_init函数的时候就完成驱动模块的注册,毕竟这就是驱动的初始化函数,在这个函数里“完成向内核注册驱动模块的任务”不正是它的职责所在吗。


我们来看一下之前的first_drv_init是怎样写的吧:


static int __init first_drv_init(void)
{   
    printk(KERN_INFO"hello world!\n");
    return 0;
}


我们知道将在里面添加一个register_chrdev函数,但是我们还不了解这个函数的用法,在Source Insight的linux-2.6.22.6内核源码工程里搜索一下,看看别人是怎么用的吧,如我搜到的例子:


int ret;

···

ret = register_chrdev(VFC_MAJOR, vfcstr, &vfc_fops);

if(ret) {

  printk(KERN_ERR "Unable to get major number %d\n", VFC_MAJOR);

  kfree(vfc_dev_lst);

  return -EIO;

}

···

return 0;


我们重点来看有颜色的那一行,VFC_MAJOR是一个宏定义,为60;vfcstr是数组vfcstr[]="vfc"的首地址,&vfc_fops是结构体vfc_fops的地址。


再来查找一下的定义:


int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)

{

···

}


果然如此,和我们的分析完全吻合,第一个参数major指主设备号,是unsigned int类型的数;第二个参数name是字符串类型数据的指针类型,而const修饰符说明它是输入型参数,其中这个字符串应该是设备的名字,;第三个参数fops是指向file_operations类型结构体变量的指针类型,并且也是输入型参数。 


我们在里面加上register_chrdev函数,改动如下:


static int __init first_drv_init(void)
{   
    int ret = -1;
    register_chrdev(100, "first_drv", &first_drv_fops);
    if(ret != 0)
    {
        printk(KERN_INFO"Register_chrdev failure!\n")
        return -1;
    }
    printk(KERN_INFO"Register_chrdev success...\n");
    return 0;
}


我们的主设备号暂定为100,设备名字可以随便起,我们起为:"first_drv",我们之前定义了first_drv_fops结构体:


static const struct file_operations first_drv_fops = {

        .owner          = THIS_MODULE,

        .open           = first_drv_open,

        .release        = first_drv_release,

};


我们通过&first_drv_fops来将它的地址传进register_chrdev函数中去。


既然有了注册驱动模块的函数register_chrdev,那么也应该有卸载驱动函数,如果一个人死了几百年年,户籍系统出于方便管理的原因,可能会销毁这个人的数据,那么也就是说,一旦我们不再需要这个驱动模块在系统中工作时,我们要做的第一件事就是把这个驱动所占用的资源回收回来,回收资源的意思就是指:将file_operations这本书的第100面用橡皮擦擦干净,抹去该驱动模块的一切信息。


在字符设备驱动中,我们使用unregister_chrdev函数来完成驱动模块的卸载,理所应当地把unregister_chrdev函数放到first_drv_exit函数中:


static void __exit first_drv_exit(void)

{

     int ret = -1;

    ret = unregister_chrdev(100, "first_drv");

    if(ret != 0)

    {

        printk(KERN_INFO"Unregister_chrdev failure!\n");

    }

    printk(KERN_INFO"Unregister_chrdev success...\n");

}


不同于register_chrdev函数,unregister_chrdev只需要指定驱动模块的主设备号和名字就可以找到该驱动并卸载。


注意到不论是register_chrdev函数还是unregister_chrdev函数中,我们都使用了一个变量ret来记录这两个函数的返回值,当注册或卸载成功时,这两个函数就会返回0,当注册或卸载失败时,会返回一个负数,根据这个特点,我们可以打印出注册或卸载成功/失败的信息。


那么什么时候会注册、卸载失败呢,如果file_operations这本书的100面已经提前登记了别的驱动模块的信息,那么我们去申请100这个主设备号就会失败。


有的同学可能知道通过在最小根文件系统下执行cat /proc/devices可以查看当前系统中已经安装的驱动以及它们的主设备号,如果发现100这个主设备号已经有驱动模块在使用了,那就找一个空缺的、未被使用的主设备号,来把我们的驱动程序中的100改成这个主设备号,这个方法是可以,但是有些繁琐。


其实内核是可以自动帮我们分配主设备号的,我们在下一篇博文再来介绍。


下面附上我们完整的first_drv.c驱动程序:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>

static int first_drv_open(struct inode *inode, struct file *file)
{
    printk(KERN_INFO"It is in first_drv_open.\n");
    return 0;
}

static int first_drv_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO"It is in first_drv_release.\n");
    return 0;
}

static const struct file_operations first_drv_fops = {
        .owner          = THIS_MODULE,
        .open           = first_drv_open,
        .release        = first_drv_release,
};

static int __init first_drv_init(void)
{
    int ret = -1;
    
    ret = register_chrdev(100, "first_drv", &first_drv_fops);
    
    if(ret < 0)
    {
        printk(KERN_ERR"Register_chrdev failure!\n");
        return -1;
    }
    printk(KERN_INFO"Register_chrdev success...\n");
    return 0;
}

static void __exit first_drv_exit(void)
{
    int ret = -1;
    
    ret = unregister_chrdev(100, "first_drv");
    
    if(ret < 0)
    {
        printk(KERN_INFO"Unregister_chrdev failure!\n");
    }
    printk(KERN_INFO"Unregister_chrdev success...\n");
}

module_init(first_drv_init);
module_exit(first_drv_exit);

MODULE_LICENSE("GPL");




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值