• 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
    ");
    	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的学习能力能有更大提升。

  • 相关阅读:
    [整理]修改git 默认编辑器为vim
    [转]如何清空Chrome缓存和Cookie
    [整理]docker内部时区修改的两种方法
    [译]10个有关SCP的命令
    [译]在python中如何有效的比较两个无序的列表是否包含完全同样的元素(不是set)?
    通过设计表快速了解sql语句中字段的含义
    [整理]什么是排序算法的稳定性,为什么它很重要?
    pyinstaller打包自己的python程序
    [问题解决]ps aux中command命令相同,如何找出自己要的进程号?
    [常识]Windows系统里休眠和睡眠的区别?
  • 原文地址:https://www.cnblogs.com/yx20145312/p/6107735.html
Copyright © 2020-2023  润新知