20145312《信息安全系统设计基础》实验四 驱动程序设计
实验目的与要求
- 学习在 LINUX 下进行驱动设计的原理
- 掌握使用模块方式进行驱动开发调试的过程
实验内容
- 在 PC 机上编写简单的虚拟硬件驱动程序并进行调试,实验驱动的各个接口函数的实现,
- 分析并理解驱动与应用程序的交互过程。
实验步骤
1. 搭建实验平台
- 连接arm开发板
- 建立超级终端
- 启动实验平台(redhat虚拟机)
- 配置同网段IP
- 安装arm编译器(bc共享文件夹)
- 配置环境变量(redhat虚拟机中)
2. 编译应用程序
- 将01_demo文件夹拷贝到bc共享文件夹中
- 在修改makefile文件后,采用交叉编译器即可进行编译。使用交叉编译器不需要建立设备节点
[root@BC 01_demo]#make
- 也可以使用gcc进行编译,通过下面的命令来建立设备节点
[root@BC src]#mknod /dev/demo c 254 0
3. 测试驱动程序
- 插入驱动模块demo.o,可以用lsmod 命令来查看模块是否已经被插入,在不使用该模块的时候还可以用rmmod 命令来将模块卸载
- 然后运行测试程序,和预期结果一致
代码分析
驱动接口的实现过程
源代码
demo.c
#include <linux/config.h>
#include <linux/module.h>
#include <linux/devfs_fs_kernel.h>
#include <linux/init.h>
#include <linux/kernel.h> /* printk() */
#include <linux/slab.h> /* kmalloc() */
#include <linux/fs.h> /* everything... */
#include <linux/errno.h> /* error codes */
#include <linux/types.h> /* size_t */
#include <linux/proc_fs.h>
#include <linux/fcntl.h> /* O_ACCMODE */
#include <linux/poll.h> /* COPY_TO_USER */
#include <asm/system.h> /* cli(), *_flags */
#define DEVICE_NAME "demo"
#define demo_MAJOR 254
#define demo_MINOR 0
static int MAX_BUF_LEN=1024;
static char drv_buf[1024];
static int WRI_LENGTH=0;
/*************************************************************************************/
/*逆序排列缓冲区数据*/
static void do_write()
{
int i;
int len = WRI_LENGTH;
char tmp;
for(i = 0; i < (len>>1); i++,len--){
tmp = drv_buf[len-1];
drv_buf[len-1] = drv_buf[i];
drv_buf[i] = tmp;
}
}
/*************************************************************************************/
static ssize_t demo_write(struct file *filp,const char *buffer, size_t count)
{
if(count > MAX_BUF_LEN)count = MAX_BUF_LEN;
copy_from_user(drv_buf , buffer, count);
WRI_LENGTH = count;
printk("user write data to driver
");
do_write();
return count;
}
/*************************************************************************************/
static ssize_t demo_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)
{
if(count > MAX_BUF_LEN)
count=MAX_BUF_LEN;
copy_to_user(buffer, drv_buf,count);
printk("user read data from driver
");
return count;
}
/*************************************************************************************/
static int demo_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
printk("ioctl runing
");
switch(cmd){
case 1:printk("runing command 1
");break;
case 2:printk("runing command 2
");break;
default:
printk("error cmd number
");break;
}
return 0;
}
/*************************************************************************************/
static int demo_open(struct inode *inode, struct file *file)
{
sprintf(drv_buf,"device open sucess!
");
printk("device open sucess!
");
return 0;
}
/*************************************************************************************/
static int demo_release(struct inode *inode, struct file *filp)
{
MOD_DEC_USE_COUNT;
printk("device release
");
return 0;
}
/*************************************************************************************/
static struct file_operations demo_fops = {
owner: THIS_MODULE,
write: demo_write,
read: demo_read,
ioctl: demo_ioctl,
open: demo_open,
release: demo_release,
};
/*
static struct file_operations demo_fops = {…}完成了将驱动函数映射为
标准接口,devfs_registe()和 register_chrdev()函数完成将驱动向内核注册。
*/
/*************************************************************************************/
#ifdef CONFIG_DEVFS_FS
static devfs_handle_t devfs_demo_dir, devfs_demoraw;
#endif
/*************************************************************************************/
static int __init demo_init(void)
{
#ifdef CONFIG_DEVFS_FS
devfs_demo_dir = devfs_mk_dir(NULL, "demo", NULL);
devfs_demoraw = devfs_register(devfs_demo_dir, "0", DEVFS_FL_DEFAULT,
demo_MAJOR, demo_MINOR, S_IFCHR | S_IRUSR | S_IWUSR,
&demo_fops, NULL);
#else
int result;
SET_MODULE_OWNER(&demo_fops);
result = register_chrdev(demo_MAJOR, "demo", &demo_fops);
if (result < 0) return result;
// if (demo_MAJOR == 0) demo_MAJOR = result; /* dynamic */
#endif
printk(DEVICE_NAME " initialized
");
return 0;
}
/*************************************************************************************/
static void __exit demo_exit(void)
{
unregister_chrdev(demo_MAJOR, "demo");
//kfree(demo_devices);
printk(DEVICE_NAME " unloaded
");
}
/*************************************************************************************/
module_init(demo_init);
module_exit(demo_exit);
重要函数分析
Open 方法
static int demo_open(struct inode *inode, struct file *file)
{
sprintf(drv_buf,"device open sucess!
");
printk("device open sucess!
");
return 0;
}
- Open 方法提供给驱动程序初始化设备的能力,从而为以后的设备操作做好准备,此外open 操作一般还会递增使用计数,用以防止文件关闭前模块被卸载出内核。在大多数驱动程序中 Open 方法应完成如下工作:
1. 递增使用计数
2. 检查特定设备错误。
3. 如果设备是首次打开,则对其进行初始化。
4. 识别次设备号,如有必要修改 f_op 指针。
5. 分配并填写 filp->private_data 中的数据。
Release 方法
static int demo_release(struct inode *inode, struct file *filp)
{
MOD_DEC_USE_COUNT;
printk("device release
");
return 0;
}
- 与 open 方法相反,release 方法应完成如下功能:
1. 释放由 open 分配的 filp->private_data 中的所有内容
2. 在最后一次关闭操作时关闭设备
3. 使用计数减一
Read 和 Write 方法
static ssize_t demo_write(struct file *filp,const char *buffer, size_t count)
{
if(count > MAX_BUF_LEN)count = MAX_BUF_LEN;
copy_from_user(drv_buf , buffer, count);
WRI_LENGTH = count;
printk("user write data to driver
");
do_write();
return count;
}
/*************************************************************************************/
static ssize_t demo_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)
{
if(count > MAX_BUF_LEN)
count=MAX_BUF_LEN;
copy_to_user(buffer, drv_buf,count);
printk("user read data from driver
");
return count;
}
-
read 方法完成将数据从内核拷贝到应用程序空间,write 方法相反,将数据从应用程序空间拷贝到内核。对于者两个方法,参数 filp 是文件指针,count 是请求传输数据的长度,buffer 是用户空间的数据缓冲区,ppos 是文件中进行操作的偏移量,类型为 64 位数。
-
由于用户空间和内核空间的内存映射方式完全不同,所以不能使用象 memcpy 之类的函数,必须使用如下函数:
unsigned long copy_to_user (void *to,const void *from,unsigned long count);
unsigned long copy_from_user(void *to,const void *from,unsigned long count);
-
Read 的返回值
1. 返回值等于传递给 read 系统调用的 count 参数,表明请求的数据传输成功。
2. 返回值大于 0,但小于传递给 read 系统调用的 count 参数,表明部分数据传输成功,根据设备的不同,导致这个问题的原因也不同,一般采取再次读取的方法。
3. 返回值=0,表示到达文件的末尾。
4. 返回值为负数,表示出现错误,并且指明是何种错误。
5. 在阻塞型 io 中,read 调用会出现阻塞。
- Write 的返回值
1. 返回值等于传递给 write 系统调用的 count 参数,表明请求的数据传输成功。
2. 返回值大于 0,但小于传递给 write 系统调用的 count 参数,表明部分数据传输成功,根据设备的不同,导致这个问题的原因也不同,一般采取再次读取的方法。
3. 返回值=0,表示没有写入任何数据。标准库在调用 write 时,出现这种情况会重复调用 write。
4. 返回值为负数,表示出现错误,并且指明是何种错误。
5. 在阻塞型 io 中,write 调用会出现阻塞。
ioctl 方法
static int demo_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
printk("ioctl runing
");
switch(cmd){
case 1:printk("runing command 1
");break;
case 2:printk("runing command 2
");break;
default:
printk("error cmd number
");break;
}
return 0;
}
-
ioctl 方法主要用于对设备进行读写之外的其他控制,比如配置设备、进入或退出某种操作模式,这些操作一般都无法通过 read/write 文件操作来完成.
-
用户空间的 ioctl 函数的原型为:
int ioctl(inf fd,int cmd,…)
- 其中的…代表可变数目的参数表,实际中是一个可选参数,一般定义为:
int ioctl(inf fd,int cmd,char *argp)
-
驱动程序中定义的 ioctl 方法原型为:
int (*ioctl) (struct inode *inode, struct file *file,unsigned int cmd, unsigned long arg)
- inode 和 filp 两个指针对应应用程序传递的文件描述符 fd,cmd 不会被修改地传递给驱动程序,可选的参数 arg 则无论用户应用程序使用的是指针还是其他类型值,都以unsigned long
的形式传递给驱动。 -
ioctl 方法的返回值
- ioctl 通常实现一个基于 switch 语句的各个命令的处理,对于用户程序传递了不合适的命名参数时,POSIX 标准规定应返回-ENOTTY,返回-EINVAL 是以前常见的方法。
- 不能使用与 LINUX 预定义命令相同的号码,因为这些命令号码会被内核 sys_ioctl 函数识别,并且不再将命令传递给驱动的 ioctl。Linux 针对所有文件的预定义命令的幻数为“T”。所以我们不应使用 TYPE 为”T”的幻数。
实验过程中遇到的问题
问题:
- 插入驱动模块失败如下:
[root@zxt 01_demo]# ./test_demo
[root@zxt 01_demo]#device open fail
解决
- 这个主要是因为,因为手动编译代码太为繁琐,我们选择了用make的方法,将Makefile稍微修改后就可以使用,但是我们错误的默认了make使用交叉编译,而实际上是用的gcc编译,所以缺少了设备节点的建立,补上这一步骤之后就成功了。
问题:Make编译失败
解决
- 经过查看指导书,发现可能是在/usr/src 下没有建立一个linux 连接,可以使用下面的命令,解决了问题。
[root@zxt 01_demo]# cd /usr/src/
[root@zxt src]# ln -sf linux-2.4.20-8 linux
[root@zxt src]# ls
debug linux linux-2.4 linux-2.4.20-8 redhat
-
对于ln指令:
- ln指令的用法是连接,使用格式是ln [options] source dist,这里我们用到的sf参数的含义是: - -f:链接时先将与dist同档名的档案删除 - -s:进行软链接。(软链接,又称符号链接,这个文件包含了另一个文件的路径名,特点是可以链接不同文件系统的文件,甚至可以链接不存在的文件。)
实验心得
本学期的Linux课程的学习从这次实验中有了很大的提高,理论知识,只有结合实践才会出真知。面对驱动程序这一陌生概念,对于本次实验确实不好理解,只是跟着实验步骤操作,遇到问题,解决问题的过程中才加深了对课本知识的理解。希望下次实验后,Linux的学习能力能有更大提升。