2017-2018-1 《信息安全系统设计基础》实验四报告

2017-2018-1 《信息安全系统设计基础》实验四报告

本小组成员:2015530320155213;实验四【码云链接

————————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”,并没有什么捷径可走;使用再巧妙的学习方法,如果不及时巩固学到的知识,最终也还是一知半解。

返回目录


参考资料

返回目录

posted @ 2017-12-02 17:40  0x14b7狄  阅读(665)  评论(5编辑  收藏  举报