第1个linux驱动___给驱动模块上户口

我们已经成功地通过开发板的最小根文件系统的/mnt目录挂接了虚拟机ubuntu的/work/my_drivers/ko_file目录下的内容,我们只要在最小根文件系统环境中执行:insmod /mnt/first_drv.ko,即可把ubuntu中编译好的first_drv.ko安装到开发板的内核中,卸载执行:rmmod first_drv或rmmod first_drv.ko即可。

下面,先来写一个方便我们挂接到虚拟机ubuntu的/work/my_drivers/ko_file的脚本,在最小根文件系统的根目录下执行vi nfs.sh,输入以下内容:

#!/bin/sh
ifconfig eth0 192.168.2.100
mount -t nfs -o nolock 192.168.2.110:/work/my_drivers/ko_file /mnt

当然,上面的IP地址都不是固定的,你需要根据你的虚拟机的IP地址来进行修改,而且一旦哪次虚拟机的IP地址变动,你也要相应地修改这个脚本文件里面的IP地址参数。

以后,我们启动开发板,在内核成功挂接了最小根文件系统后,你只要执行./nfs.sh即可实现/mnt和/work/my_drivers/ko_file的挂接。

这是第一个小技巧,还有一个小技巧,是对于Makefile的。

之前说过,我们是在/work/my_drivers/first_drv/1th目录下执行make生成first_drv.ko,再将first_drv.ko拷贝到/work/my_drivers/ko_file目录下的。

既然每次都要执cp first_drv.ko /work/my_drivers/ko_file,那为什么不把这句命令写到Makefile中呢,要知道,我们当初写Makefile本身就是为了避免手动输入过长的命令的,单纯就是为了偷懒,既然这样那就偷懒到底吧。

将我们的Makefile改为下面这样:

#KERN_VER = $(shell uname -r)
#KERN_DIR = /lib/modules/$(KERN_VER)/build

KERN_DIR = /work/kernel/linux-2.6.22.6

all:
    make -C $(KERN_DIR) M=`pwd` modules 
        
cp:
    cp *.ko /work/my_drivers/ko_file
        
clean:
    make -C $(KERN_DIR) M=`pwd` modules clean
    rm -rf Module.symvers
        
obj-m += first_drv.o

我们增加了cp目标,这样以后只要执行make cp就会调用到:

cp *.ko /work/my_drivers/ko_file

这两个小改进后有利于我们开发效率的提高,现在正式开始本篇的内容,给驱动模块上户口。

一个可以被应用程序调用的驱动模块必定是在内核找得到主设备号的,在内核空间中有一个数组,该数组中有255个元素,每个元素都是一个结构体,他们是struct file_operations类型的结构体,在之前的博文中我们提到过file_operations,当时我们为了形象地理解整个系统各层次之间是如何协调工作的,于是我们把它类比为了一本书,在应用层要调用一个驱动的时候,内核会去file_operations这本书中找这驱动,现在我们知道了这本书一共有255面,每一面都记载了一个驱动程序的所有信息,那么对于一个驱动程序而言,假设它在这本书的第100面被内核详细记录了信息,那么只要内核得知应用程序要找的那个驱动模块的主设备号是100,那么内核就会去file_operations这本书的第100面找到该驱动模块所涉及的各种函数,并根据应用层的需求来调用其中的函数。

我们先把驱动程序中一些基本信息按照file_operations类型结构体的格式来进行填充,举个例子来说,就是first_drv这个人要去民政局上户口,工作人员首先会拿他一份表叫他填写的姓名、出生年月、手机号码、住址等信息,要填什么是由这份表来决定的,那么这份表就具备一定的类目和格式,然后工作人员拿着这份填好的表,再在电脑上输入进行最终的注册,这样,系统里就有了这个人,就相当于这人被正式写进了file_operations这本书的第100页,这个人的户口本编码就是100,以后如果有什么事情公安局、交通局这些机构要找这人通过file_operations这本书的第100页马上就能找到他。

好的,那我们现在就来填这份表吧,就像你在百度上搜索“上户口要填的表是什么样子的”,我们可以在在linux-2.6.22.6内核源码工程里搜索"file_operations",看看别人上户口时填的表是什么样子的,咱们照着填就行。

例如我搜索到的有:

static const struct file_operations cafe_v4l_fops = {

.owner = THIS_MODULE,

.open = cafe_v4l_open,

.release = cafe_v4l_release,

.read = cafe_v4l_read,

.poll = cafe_v4l_poll,

.mmap = cafe_v4l_mmap,

.ioctl = video_ioctl2,

.llseek = no_llseek,

};

不知道这是哪位哥们的填的表,看起来很复杂,可能这哥们家庭情况很复杂,结过很多次婚,又离过很多次婚,可能外面还有些私生子,当然管他怎么乱七八糟,反正别人的信息是不能用来办我们的户口的,我们来仿照着填写我们的信息:

static const struct file_operations first_drv_fops = {

.owner          = THIS_MODULE,

.open           = first_drv_open,

.release        = first_drv_release,

};

可以看到,咱们家世清白,三代单传,家庭成员较少,寥寥几行笔墨即可搞定,我们暂时只对owner、open、release三个类目进行填写,别的可以认为属于可填可不填的。

在这份表中,我们定义了first_drv_fops这个结构体变量,它的open和release两个成员变量是函数指针,分别指向first_drv_open、first_drv_release这两个函数,这两个函数当然是咱们驱动程序中的函数啦,但是显然咱们还没有写这两个函数。

怎么写呢?我们注意到之前那哥们的open成员指向了一个叫做cafe_v4l_open的函数,我们来搜索一下这个函数:

static int cafe_v4l_open(struct inode *inode, struct file *filp)
{
struct cafe_camera *cam;
cam = cafe_find_dev(iminor(inode));
if (cam == NULL)
return -ENODEV;
filp->private_data = cam;
mutex_lock(&cam->s_mutex);
if (cam->users == 0) {
cafe_ctlr_power_up(cam);
__cafe_cam_reset(cam);
cafe_set_config_needed(cam, 1);
/* FIXME make sure this is complete */
}
(cam->users)++;
mutex_unlock(&cam->s_mutex);
return 0;
}

我们不管它内部代码实现了什么,反正我们不关心,道不同不相为谋嘛,因此我们把内部代码剔除,并且加上我们first_drv_open函数的名字,顺便把一个局部变量的名字filp改成file(这不是必须的,只是看起来习惯些),如下所示:

static int first_drv_open(struct inode *inode, struct file *file)
{
return 0;
}

同样的方法我们可以仿照得到first_drv_release函数:

static int first_drv_release(struct inode *inode, struct file *file)
{
return 0;
}

那么这两个函数要做什么事情呢?虽然不能像别人那样写的那样复杂,但也不能什么也不写吧,我们可以在两个函数中各添加一行打印信息。

今天这篇博文已经足够长了,剩下的内容将在下一篇博文中进行说明,我们将添加了打印信息的完整的驱动程序first_drv.c附在最后:

#include <linux/module.h> 
#include <linux/init.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)
{   
    printk(KERN_INFO"hello world!\n");
    return 0;
}

static int __exit first_drv_exit(void)
{   
    printk(KERN_INFO"goodbye world...\n");
    return 0;
}

module_init(first_drv_init);
module_exit(first_drv_exit);

MODULE_LICENSE("GPL");
时间: 2024-10-13 09:48:05

第1个linux驱动___给驱动模块上户口的相关文章

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

从这篇博文开始,我们终于可以给我们的驱动模块first_drv一个正式的编制了,现在他已经填好了自己的户口登记信息表,距离拿户口本儿只有一步了! 内核内部有自己的一套安装驱动模块的方法,就像是民政局工作人员知道怎么给一个人办户口一样,对于一个普通公民来说,工作人员做了哪些操作办好了户口本,并不是普通公民需要关心的,我们最关心的是如何把我们填好的户口信息登记表交给工作人员,如何向工作人员表明我们有办户口的请求. 在linux系统中,对于字符设备驱动,我们通过register_chrdev这个函数来

第1个linux驱动___安装驱动模块之内核再爱我一次

在上一篇博文中,我们已经通过Makefile编译得到first_drv.ko文件,这是一个可以被安装到ubuntu中的驱动模块,要怎样做呢? 在/work/my_drivers/first_drv/1th/目录下执行:insmod first_drv.ko 如果你是在普通用户状态下执行的这条命令,可以看到系统提醒我们:insmod: error inserting 'first_drv.ko': -1 Operation not permitted 这是因为安装驱动模块需要超级权限,你可以在普通

第1个linux驱动___应用程序才是大Boss

我们的驱动模块已经可以被自动分配主设备号了,可以说到目前为止,一个驱动模块所具备的一些"基础设施"它都具备了,就像是养兵前日,用兵一时,已经可以让first_drv这个驱动模块出去打仗了. 那么说是出去打仗,总得有个发号施令的首长吧,在linux系统中,这位调兵遣将的首长就是应用程序,应用程序才是大Boss,我们"一直精雕细琢的驱动模块"说白了只是个跑腿的. 这就是为什么我们的专题是第1个linux驱动却要讲应用程序的原因,因为应用程序是让驱动程序能够被应用的,而驱

第1个linux驱动___搭建环境,蓄势待发

如何开始写最简单的linux驱动? 在阅读本文前,你需要对linux的基本知识.ubuntu虚拟机的安装与命令行操作有基本的了解. 环境搭建:我在windows电脑上使用VMware打开安装好的虚拟机ubuntu9.0,ubuntu是运行linux内核的linux发行版. 我习惯secureCRT通过SSH远程登录到虚拟机上操作,当然你也可以直接在虚拟机环境下操作. 在根目录下建立一个目录树: cd / mkdir -p /work/my_drivers/frist_drv/1th 进入底层目录

第1个linux驱动___整个系统是怎样工作的

接着上一篇博文中,我们已经在first_drv.c中写好了空壳驱动程序. 但是编译这个空壳驱动程序之前,我们需要了解整个系统是怎么工作的. 不着急,首先,我们先来了解一点linux驱动的基本概念: linux驱动分为字符设备驱动.块设备驱动.网络设备驱动. 这是按照驱动程序对不同的硬件的操作方式差异而划分的: [ 字符设备驱动 ] 以字节级别的数据量来对硬件设备进行操作,涉及的硬件如:LED.LCD.串口.触摸屏等. [ 块设备驱动 ] 则操作的数据量较大,如读写存储芯片的驱动就是块设备驱动,一

第1个linux驱动___打印&quot;hello world&quot;

为了方便后续的深入,我们在驱动程序中用printk( )函数来打印"hello world",printk( )是内核中自带的函数,专门用于在打印内核信息. 在安装驱动模块到内核中的时,需要进行驱动模块的初始化,初始化具体做什么我们先不提,我们暂时只用printk( )打印"hello world": int first_drv_init(void) { printk("hello world!\n"); return 0; } · 关于prin

第1个linux驱动___投靠NFS网络文件系统

之前我们一直是在虚拟机ubuntu环境中测试我们的first_drv驱动模块,但是这不是我们的开发方向,在刚开始的学习中我们避免搭建过多的环境,因此选择了只在ubuntu中测试驱动. 我们的模式是: [first_drv.c]-->[使用ubuntu内核源码编译]-->[first_drv.ko(可运行于虚拟机)]--> [ 安装到虚拟机中 ]-->[ hello world! ]-->[ 从虚拟机中卸载 ]-->[ goodbye world... ] 但是我们的最终

第1个linux驱动___编译空壳驱动程序

在上一篇博文中我们已经了解到整个系统中各个层次之间是如何协调如何工作的,应用层发出的命令经由内核下达到驱动层,从而达到操作硬件层的目的. 我们先回顾上上篇博文最后成型的"空壳驱动程序"first_drv.c中的代码: #include <linux/module.h> #include <linux/init.h> static int __init first_drv_init(void) {        printk(KERN_INFO"hell

第1个linux驱动___自动分配主设备号

本篇博客内容不会很多,主要是对这个驱动程序进行一点优化,每次都要cat /proc/devices去查看还有哪些主设备号可用太麻烦了,linux如此受到热捧,显然我们不是每次都得这样做. 其实register_chrdev函数可以帮我们从1~255里面找到一个可用的主设备号,然后将这个主设备号返回给我们,当然这次我们同样要给register_chrdev函数传一个数字进去,只不过这次不是传100了,而是传0进去,当我们传0的时候,register_chrdev函数就会去查找还有哪个主设备号还可以