我们已经成功地通过开发板的最小根文件系统的/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面,每一面都记载了一个驱动程序的所有信息。
假设内核在file_operations这本书的第100面记录了某个驱动程序的详细信息,那么只要内核得知应用程序要找的那个驱动模块的主设备号是100,那么内核就会去file_operations这本书的第100面找到该驱动模块所涉及的各种函数,并根据应用层的需求来调用其中的函数。
我们先把驱动程序中一些基本信息按照file_operations类型结构体的格式来进行填充。
举个例子来说,就是first_drv这个人要去民政局上户口,工作人员首先会拿他一份表叫他填写的姓名、出生年月、手机号码、住址等信息,要填什么是由这份表来决定的,那么这份表就具备一定的类目和格式,然后工作人员拿着这份填好的表,再在电脑上输入进行最终的注册,这样,系统里就有了first_drv这个人。
这就相当于这人被正式写进了file_operations这本书的第100页,first_drv这个人的户口本编码就是100,以后如果公安局、交通局这些机构有什么事要找first_drv的话,通过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 void __exit first_drv_exit(void)
{
printk(KERN_INFO"goodbye world...\n");
}
module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");
在/work/my_drivers/first_drv/1th目录下执行make后,系统报了一大堆错误和警告,这是什么原因呢?这些错误和警告基本上是在说我们所添加的file_operations结构体类型没有定义,那我们给它添加它所需要的头文件好了。
#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)
{
printk(KERN_INFO"hello world!\n");
return 0;
}
static void __exit first_drv_exit(void)
{
printk(KERN_INFO"goodbye world...\n");
}
module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");
在执行make,编译通过。但我们现在还只是填好了一份表,还要等工作人员把我们填好的表输入到户籍系统中,我们才算落户,成为正式的公民,那么“把表交给工作人员让他给我们登记户口”这个工作怎么做呢?下篇博文见分晓。