2017-2018-1 《信息安全系统设计基础》实验四报告
————————CONTENTS————————
任务一 学习《嵌入式Linux应用程序开发标准教程》第十一章
学习第十一章,主要掌握了以下内容:
- Linux 设备驱动的基本概念;
- Linux 设备驱动的基本功能;
- Linux 设备驱动的运作过程;
- 常见设备驱动接口函数;
- LCD设备驱动程序编写步骤;
- 键盘设备驱动程序编写步骤
康奈尔笔记如下:
『问题一』struct file_operations结构有什么作用?如何使用这一内核数据结构?
『问题一解决』
查阅资料了解到,file_operations是把系统调用和驱动程序关联起来的关键数据结构。这个结构的每一个成员都对应着一个系统调用。读取file_operations中相应的函数指针,接着把控制权转交给函数,从而完成了Linux设备驱动程序的工作。
在系统内部,I/O设备的存取操作通过特定的入口点来进行,而这组特定的入口点恰恰是由设备驱动程序提供的。通常这组设备驱动程序接口是由结构file_operations结构体向系统说明的,它定义在include/linux/fs.h中。
除此之外,还有两个重要的内核数据结构:file和inode。
struct file代表一个打开的文件,在执行file_operations中的open操作时被创建,这里需要注意的是与用户空间inode指针的区别,inode在内核,而file指针在用户空间,由c库来定义。
struct inode被内核用来代表一个文件,注意和struct file的区别,struct inode代表文件,struct file代表打开的文件。
『问题二』/proc中的进程与ps命令的什么选项显示的结果相对应?
『问题二解决』
/proc 文件系统是一个伪文件系统,它是一种内核和内核模块用来向进程发?信息的机制。这个伪文件系统让用户可以和内核内部数据结构进行交互,获取有关系统和进程的有用信息,在运行时通过改变内核参数来改变设置。
与其他文件系统不同,/proc 存在于内存之中而不是在硬盘上。通过ls命令查看/proc文件系统的内容如下:
可以发现一些是以数字命名的目录,它们是进程目录。系统中当前运行的每一个进程都有对应的一个目录在/proc 下,以进程的PID 号为目录名,它们是读取进程信息的接口。我们查看其中一个进程目录:
这使我们联想到ps命令也可以显示进程,ps的常用选项有哪些呢?
- 1)ps a 显示现行终端机下的所有程序,包括其他用户的程序。
- 2)ps -A 显示所有进程。
- 3)ps c 列出程序时,显示每个程序真正的指令名称,而不包含路径,参数或常驻服务的标示。
- 4)ps -e 此参数的效果和指定"A"参数相同。
- 5)ps e 列出程序时,显示每个程序所使用的环境变量。
- 6)ps f 用ASCII字符显示树状结构,表达程序间的相互关系。
- 7)ps -H 显示树状结构,表示程序间的相互关系。
- 8)ps -N 显示所有的程序,除了执行ps指令终端机下的程序之外。
- 9)ps s 采用程序信号的格式显示程序状况。
- 10)ps S 列出程序时,包括已中断的子程序资料。
- 11)ps -t 指定终端机编号,并列出属于该终端机的程序的状况。
- 12)ps u 以用户为主的格式来显示程序状况。
- 13)ps x 显示所有程序,不以终端机来区分。
除此之外,还可以使用top命令,显示运行中系统的动态实时视图;使用pstree以树装显示正在运行的进程等。
『问题三』块设备驱动编程与字符设备的差异?
『问题三解决』
Linux 的一个重要特点就是将所有的设备都当做文件进行处理,这一类特殊文件就是设备文件。Linux 系统的设备分为3 类:字符设备、块设备和网络设备。
字符设备通常指像普通文件或字节流一样,以字节为单位?序读写的设备, 如并口设备、虚拟控制台等。字符设备可以通过设备文件节点访问,它与普通文件之间的区别在于普通文件可以被随机访问(可以前后移动访问指针),而大多数字符设备只能提供?序访问,因为对它们的访问不会被系统所缓存。但也有例外,例如缓存(framebuffer)是一个可以被随机访问的字符设备。
块设备通常指一些需要以块为单位随机读写的设备,如IDE 硬盘、SCSI 硬盘、光驱等。块设备也是通过文件节点来访问,它不仅可以提供随机访问,而且可以容纳文件系统(例如硬盘、缓存等)。Linux 可以使用户态程序像访问字符设备一样每次进行任意字节的操作,只是在内核态内部中的管理方式和内核提供的驱动接口上不同。
通过文件属性可以查看它们是哪种设备文件(字符设备文件c或块设备文件b)。
块设备驱动程序的编写流程同字符设备驱动程序的编写流程很类似,每个块设备物理实体由一个gendisk 结构体来表示(在<linux/genhd.h>中定义),每个gendisk 可以支持多个分区。
每个gendisk 中包含了本物理实体的全部信息以及操作函数接口。整个块设备的注册过程是通过gendisk 来展开的。在驱动程序中需要初始化的gendisk 的一些成员如下所示
与字符设备驱动程序一样,块设备驱动程序也包含一个在<linux/fs.h>中定义的block_device_operations 结构,其定义如下所示
任务二 完成第十一章的test实验
『一、实验目的』:该实验是编写最简单的字符驱动程序,这里的设备也就是一段内存,实现简单的读写功能,并列出常用格式的Makefile 以及驱动的加载和加载脚本。读者可以熟悉字符设备驱动的整个编写流程。
『二、实验内容』:该实验要求实现对虚拟设备(一段内存)的打开、关闭、读写的操作,并要通过编写测试程序来测试虚拟设备及其驱动运行是否正常。
『三、实验步骤』:
(1)编写代码
驱动程序的源代码参考书中相关章节即可,在此不再赘述。主要分为以下几个模块:
- 定义全局变量
- 读函数
- 写函数
- 打开函数
- 关闭函数
- 创建、初始化字符设备,并注册到系统
- 定义虚拟设备的file_operations结构
- 模块注册入口
- 卸载模块
(2)编译代码
虚拟设备的驱动程序的Makefile 如下所示:
ifeq ($(KERNELRELEASE),)
KERNELDIR ?= /lib/modules/$(shell uname -r)/build /*内核代码编译路径*/
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
.PHONY: modules modules_install clean
else
obj-m := test_drv.o /* 将生成的模块定义为test_drv.ko*/
endif
make之前要先进行make clean
(3)加载和卸载模块
通过以下两个shell脚本,分别实现驱动模块的加载和卸载:
加载脚本test_drv_load 如下所示:
#!/bin/sh
module="test_drv"
device="test_dev"
mode="664"
group="david"
# remove stale nodes
rm -f /dev/${device}
# invoke insmod with all arguments we got
# and use a pathname, as newer modutils don't look in . by default
/sbin/insmod -f ./$module.ko $* || exit 1
major=`cat /proc/devices | awk "\\$2==\"$device\" {print \\$1}"`
mknod /dev/${device} c $major 0
# give appropriate group/permissions
chgrp $group /dev/${device}
chmod $mode /dev/${device}
卸载脚本test_drv_unload 如下所示:
#!/bin/sh
module="test_drv"
device="test_dev"
# invoke rmmod with all arguments we got
sudo /sbin/rmmod $module $* || exit 1
# remove nodes
rm -f /dev/${device}
exit 0
注意:运行脚本前,需要先使用chmod +x
增加可执行权限。
(4)编写测试代码
测试代码的主要编写思路如下:
- 打开设备文件:fd = open(TEST_DEVICE_FILENAME, O_RDWR);
- 向设备写入数据:(write(fd, buff, strlen(buff));
- 从设备读取数据:(read(fd, buff, BUFF_SZ);
- 关闭设备文件:close(fd);
再根据函数返回值进行错误处理即可。详细代码可参考相关章节。
『四、实验结果』:
首先在虚拟设备驱动源码目录下编译并加载驱动模块:
$ make clean
$ make
$ ./test_drv_load
/proc/devices文件列出了字符和块设备的主设备号,以及分配到这些设备号的设备名称。此时可以在/proc文件夹下的devices中,查看到已加载的test_dev:
我们还知道,/dev主要存放与设备(包括外设)有关的文件(unix和linux系统均把设备当成文件),因此同样可以在/dev中查看已加载的设备:
使用“ls -l”并结合管道,查看该设备的详细信息(c表示字符设备):
接下来,编译并运行测试程序:
$ gcc –o test test.c
$ ./test
运行结果如下图所示,可以显示从内核读取的数据:
最后,卸载驱动程序:
$ ./test_drv_unload
再次查看/proc/devices文件,可以发现该设备已被卸载:
此时通过dmesg命令可以查看内核打印的信息:
实验感想与体会
- 在之前的实验中,我们了解了嵌入式Linux应用程序的开发,这些都是处于用户空间的内容。而本次实验进入到Linux的内核空间,初步学习了嵌入式Linux设备驱动的开发。驱动的开发流程与之前的编程习惯并不相同,定义了许多全新的数据结构与函数,在学习过程中需格外注意,以避免混淆。
- 此次实验第一次使用康奈尔笔记记录自学的过程,与之前“误打误撞”的自学方式相比,学习的条理性有了很大的提升。尤其是疑问(CUES)与总结(Summary)部分,既能在学习时抓住学习重点,有的放矢,又能在复习时理清学习主线,拓展思维。
- 需要注意的一点是,第一遍跟着教材自学,可能并不清楚作者的行文思路,虽然已经把书中的关键词悉数总结在笔记中,但此时对于内容与内容之间的先后主次关系也并不明朗。这就需要在第一遍学习结束之后,及时巩固相关知识,并辅以实践。本次实验准备时间较为仓促,虽然实验前按照第十一章的step by step教程整理了一遍,实践了一遍,仍然对相关概念和原理模棱两可。直到写总结博客时,回顾实验内容和笔记,才对驱动的开发流程等有了更加全面深刻的理解。由此可见,学习知识无非“learn->review->improve”,并没有什么捷径可走;使用再巧妙的学习方法,如果不及时巩固学到的知识,最终也还是一知半解。