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
    评论
### 回答1: 《离散数学及其应用》是一本写给大学生学习离散数学的教材,由肯尼思·罗森(Kenneth Rosen)编写的第七版。这本教材广泛被全球大学使用,被认为是离散数学的经典教材之一。 该书的主要内容包括逻辑、集合论、证明技巧、算法、图论、组合学、递归等等。它的目标是帮助学生培养解决离散数学问题的能力,并应用到计算机科学、信息科学等领域。 《离散数学及其应用》的第七版相对于之前的版本进行了更新和改进。这本书的特点是深入浅出、结构清晰,并通过大量的例题和习题来帮助读者理解和巩固所学的知识。此外,该书还提供了很多实际应用的案例,以帮助读者将理论知识应用到实际问题中。 这本教材的编写风格简单明了,循序渐进,适合初学者学习。它不仅介绍了基础概念和技术,还包含了一些前沿的研究成果,使读者能够了解到离散数学的最新进展。 总的来说, 《离散数学及其应用》是一本优秀且经典的教材,对于学习离散数学的学生和从事相关领域的研究者来说都是一本不可多得的学习和参考资料。无论是想深入了解离散数学理论知识,还是应用到实际问题中,这本书都将是一个非常有价值的工具。 ### 回答2: 《离散数学及其应用(第7版)》是一本经典的数学教材,由肯尼斯·罗森(Kenneth Rosen)编写。该书广泛应用于计算机科学、信息技术和工程等领域的离散数学课程。 该教材主要包括了离散数学的核心概念和方法,如集合论、命题逻辑、谓词逻辑、证明技巧、图论、组合数学、概率论等内容。每一章节都有大量的例子和习题,帮助读者理解和掌握相关概念和技巧。 与其他教材相比,这本书的特点之一是强调数学的应用。例如,在图论部分,书中介绍了图的应用于计算机网络、社交网络和电路设计等方面,并提供了相关的算法和技巧。此外,该书还涵盖了离散数学在密码学、信息安全和计算机算法等领域的应用。 另一个特点是该教材的详细性和全面性。编者在选材上做了周到的考虑,以确保教材尽可能地覆盖离散数学的各个方面。此外,书中内容的组织结构合理,逻辑严谨,帮助读者更好地理解和掌握知识。 总之,《离散数学及其应用(第7版)》是一本经典的离散数学教材,适用于计算机科学、信息技术和工程领域的学生和从业人员。通过学习这本教材,读者可以系统地理解离散数学的基本概念和方法,并能够应用于实际问题的求解。 ### 回答3: 《离散数学及其应用 第7版》是美国著名数学家Kenneth H. Rosen所著的一本教材,广泛应用于离散数学的学习和教学。这本教材被广泛认为是离散数学领域的经典教材之一,内容丰富全面,涵盖了离散数学的各个方面。 该书分为7个部分,分别是基础概念、结构和函数、归纳和递归、计数和概率、图论、树和算法、布尔代数和自动机。每个部分都由多个章节组成,侧重于讲解相应领域的核心概念和基本原理。该书的语言简单明了,循序渐进,既适合作为初学者的入门书,也适合作为高级学习者的参考书。 《离散数学及其应用 第7版》的特点之一是其丰富的例题和习题。每个章节都提供了大量的例题来帮助读者理解概念和应用,而且每章末尾还有大量的习题供读者练习。这些例题和习题的设计非常精细,既涵盖了基本知识点的掌握,也拓展了读者的思维和解题能力。此外,书中还提供了答案和解析供读者参考。 总体而言,Kenneth H. Rosen的《离散数学及其应用 第7版》是一本非常优秀的教材,对于学习离散数学的人来说是一个不可多得的宝库。不仅能够帮助读者建立坚实的数学基础,提高解决问题的能力,也能够培养读者的逻辑思维和分析能力。无论是学生、教师还是专业人士,都能从中获得巨大的收益。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值