20145312《信息安全系统设计基础》实验四 驱动程序设计

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\n");
	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\n");
	return count;
}
/*************************************************************************************/
static int demo_ioctl(struct inode *inode, struct file *file, 
                 unsigned int cmd, unsigned long arg)
{
	printk("ioctl runing\n");
	switch(cmd){
		case 1:printk("runing command 1 \n");break;
		case 2:printk("runing command 2 \n");break;
		default:
			printk("error cmd number\n");break;
	}
	return 0;
}
/*************************************************************************************/
static int demo_open(struct inode *inode, struct file *file)
{
	sprintf(drv_buf,"device open sucess!\n");
	printk("device open sucess!\n");
	return 0;
}
/*************************************************************************************/
static int  demo_release(struct inode *inode, struct file *filp)
{
	MOD_DEC_USE_COUNT;
	printk("device release\n");
	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\n");
	return 0;
}

/*************************************************************************************/
static void __exit  demo_exit(void)
{
    unregister_chrdev(demo_MAJOR, "demo");
    //kfree(demo_devices);
	printk(DEVICE_NAME " unloaded\n");
}

/*************************************************************************************/
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!\n");
	printk("device open sucess!\n");
	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\n");
	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\n");
	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\n");
	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\n");
	switch(cmd){
		case 1:printk("runing command 1 \n");break;
		case 2:printk("runing command 2 \n");break;
		default:
			printk("error cmd number\n");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的学习能力能有更大提升。

posted @ 2016-11-27 22:37  20145312袁心  阅读(190)  评论(0编辑  收藏  举报