内核与设备驱动编程 三.docx

上传人:b****5 文档编号:8026027 上传时间:2023-01-28 格式:DOCX 页数:15 大小:155.61KB
下载 相关 举报
内核与设备驱动编程 三.docx_第1页
第1页 / 共15页
内核与设备驱动编程 三.docx_第2页
第2页 / 共15页
内核与设备驱动编程 三.docx_第3页
第3页 / 共15页
内核与设备驱动编程 三.docx_第4页
第4页 / 共15页
内核与设备驱动编程 三.docx_第5页
第5页 / 共15页
点击查看更多>>
下载资源
资源描述

内核与设备驱动编程 三.docx

《内核与设备驱动编程 三.docx》由会员分享,可在线阅读,更多相关《内核与设备驱动编程 三.docx(15页珍藏版)》请在冰豆网上搜索。

内核与设备驱动编程 三.docx

内核与设备驱动编程三

内核与设备驱动编程

091180083刘浩通信工程

一、实验目的

1.学习Linux操作系统下内核程序的编写和应用

2.学习可编程接口芯片8253的编程控制方法

3.学习异步串行接口(UART16550)驱动的编写,实现双机通信

二、实验原理简介

1.内核模块

Liux内核提供以下功能,进程管理、内存管理、文件系统、设备控制和网络功能。

Linux内核提供的这些功能可以在系统运行时利用可加载模块来进行扩展,功能的模块化使Linux内核更加精简,灵活和方便。

设备驱动程序是内核模块的一个重要部分,此外很多其他功能,如文件系统也可实现模块化。

一个内核模块的入口函数(在模块加载insmod时被调用):

缺省函数原型为intinit_module(),可以用module_init(函数名)来声明从而更改函数名。

这个入口函数的作用是为以后调用模块函数做准备。

模块的第二个入口函数(在模块卸载rmmod时被调用)是:

缺省函数原型voidcleanup_module(),同样可以用module_exit(函数名)来声明从而改变函数名,这个函数的作用是告诉内核,模块要离开了,不能再提供什么功能了。

其中这些声明包含在头文件

这里简单说明一下内核模块和应用程序之间的区别,应用程序的入口函数是main()函数,从头到尾执行一个单个任务,而内核模块是先注册自己然后服务于以后的每个请求,这在后面的设备驱动程序中表现得相当明显。

此外应用程序和内核模块在函数调用时也有很大区别,应用程序可以链接到外部,而模块只能链接到内核,只能调用由内核导出的那些函数。

例如,应用程序可以调用定义在libc中的printf,而模块不能,模块使用printk,是内核定义的函数。

另外,一个很重要的区别是内核模块运行在内核空间,而应用程序运行在用户空间。

2.设备文件及设备驱动

Linux中设备可分为三类:

字符设备,块设备和网络设备。

字符设备和块设备都可以通过文件节点的方式被访问,设备以文件形式表示后,可以像普通文件一样来open,close,read,write。

在/dev目录下可以查看系统中已有的一些设备。

用ls–l命令可以查看字符设备用c表示,块设备用b表示,例如:

crw-rw-rw-1rootroot1,3Feb231999null。

其中设备文件名是null,它前面的是设备文件建立时间,再前面的两个数字表示的设备号,1是主设备号,3是次设备号。

驱动程序对应的是主设备号,主设备号相同的设备由同一个驱动程序来驱动。

此设备号用来区分不同的设备,当一个设备驱动程序驱动好几个设备时,次设备号就发挥作用了。

在驱动程序初始化中,通过注册设备来分配主设备号。

关于字符设备的注册和注销。

注册:

intregiser_chrdev(unsignedintmajor,constchar*name,structfile_operations*fops)。

对这个函数进行说明。

函数的第二个参数是函数名,第三个参数是与文件相关的操作,这个指针指向对这个设备进行操作的一组函数,是file数据结构中的一个很重要的结构体。

第一个参数若不为0,则为设备的主设备号,此时若设备注册成功,函数返回值为0;注册失败,函数返回一个负值。

若第一个参数major是0,则为动态分配主设备号,当正确注册时,返回的是动态分配的设备号,若注册失败,返回一个负数。

当驱动程序注册到内核表中,它的操作就与指定的主设备号对应起来,当我们对一个主设备号与之相对应的设备文件进行操作时,内核将从fop中找到并调用正确的函数,进行操作。

注销:

任何被注册的设备在模块卸载时都必须释放主设备号,对设备进行注销的函数:

intunregister_chrdev(unsignedintmajor,constchar*name)。

关于建立文件节点。

设备驱动程序为用户程序屏蔽掉了底层的硬件信息,用户程序对字符设备的操作就像操作普通的文件一样。

要对设备进行操作,需要建立设备文件节点,当用户程序操作设备节点时(如打开,关闭,读写),得到设备文件的主设备号,进而找到对应的驱动程序,进而对设备进行fop中对应的各种操作。

所以设备文件节点是联系用户程序和设备驱动程序的纽带。

建立设备文件节点用以下命令:

mknod/dev/namecmajorminor–m666。

其中name表示设备名,c表示建立的是字符设备文件,major是主设备号,minor是次设备号,-m666表示建立文件的读写权限为可读可写文件。

在卸载设备驱动程序时有的时候需要删除设备文件:

rm/dev/name。

3.文件操作

打开的设备在内核中以file结构标识,内核使用file_operations结构访问驱动程序中的函数。

关于file_operations结构体中的函数,以及file结构的说明见课本P246——P250。

4.可编程接口芯片8253介绍

一片8253可编程定时/计数器中有三个完全独立的减法计数器,每个计数器有六种不同的工作方式。

具体原理见课本P123——126。

这里主要介绍一下它的工作方式二和工作方式三,这两种工作方式是不需要硬件触发就能产生连续波形的方式,方式二成为频率发生器,方式三称为方波发生器。

方式二只有当计数减到1时输出一个时钟周期的低电平。

而方式三是减二计数,当减到0时输出状态翻转,从而使输出波形接近方波。

在8253控制扬声器驱动程序中我们采用的是8253的工作方式三而不是工作方式二,也是因为这个区别,工作方式二短暂的低电平,这样会使得驱动的功率太小,扬声器发出声音太小而影响实验效果。

早期的个人计算机中,有一片8253,作为系统的硬件时钟设备,占用端口为40H~43H。

输入时钟为1.19MHz。

T/C0承担系统日时钟,输出接8259的IR0,作为系统的计时中断信号。

T/C1承担动态存储器的时钟刷新,T/C2控制着系统的扬声器,产生声音信号。

它与扬声器的硬件接口电路如下:

要使扬声器发声,8255的PB0和PB1必须为高电平,初始化后B口为输出方式,用于一些控制信号的输出。

8255的分配端口为60H——63H。

B口的地址为61H,8255控制口的地址为63H。

对8255B口写03H,用汇编语言:

outb_p(0x03,0x61)。

写8253的控制字,outb_put(0xb6,0x43)。

0xb6转换为二进制数为10110110,根据下面的控制字格式我们知道,它表示:

选择计数器2、先读\写低字节,后读\写高字节、选用方式三、采用2进制计数。

给计数器赋初值。

由于计数器的时钟频率是1.19MHz,设给计数器赋值为x,计数器是减法计数的,则输出信号的频率为(1.19/x)MHz。

所以通过给8253的初值寄存器赋不同的值就可以得到不同频率的输出信号。

本实验中要求编写8253控制扬声器发出音乐,通过计算转换可以得到下面的音调与频率及计数初值之间的对应表格。

音符

1

2

3

4

5

6

7

低音

频率

131

147

165

175

196

220

247

数据

0x237B

0x1F9F

0x1C2C

0x1A90

0x17B7

0x1521

0x12D1

中音

频率

262

294

330

349

392

440

494

数据

0x11BD

0x0FCF

0x0E16

0x0D51

0x0BDB

0x0A90

0x0968

高音

频率

523

587

659

698

784

880

987

数据

0x08E3

0x07E8

0x070D

0x06A8

0x05ED

0x0548

0x04B5

 

5.异步串行接口(UART)介绍

UART是一种非常古老但是却一直保存在现有计算机系统中的接口,它可以把处理器对数据的并行处理转换成为串行的数据加以传输,这种接口非常简单但是依然特别常用。

串行卡从8250到16450,16550不断发展,串行通信性能不断提高。

目前个人计算机中不再有独立的UART芯片,同8253等一样被集成了,但操作方式没有什么改变。

标准配置下,个人计算机的串行端口的基地址为3F8H,2F8H,3E8H,对串口的编程兼容16550标准。

以下是16550的寄存器地址。

下面介绍通信线路控制寄存器LCR的作用:

DLAB

SBRK

SPB

EPS

PEN

STB

WLS1

WLS0

DLAB:

寻址位,为0时正常工作,为1时设置波特率;D6位:

中止位设置。

D5是奇偶校验位附加位,为0时无效。

D4D3=01时,为奇校验,为11时,是偶校验。

D1和D0位设定字符的长度,00表示5位,01表示6位,10表示7位,11表示8位。

还有其他很多寄存器,在课本p117页有具体详细的介绍。

这里不再说明。

计算机系统中的串口芯片16550通常和RS232C标准的9/25阵串口连接器连接。

下面将给出其接口:

下面介绍RS232C25针的几个重要的管脚:

20是~DTR数据终端准备好信号;4是~RTS请求发送信号;8是载波检测;6是数据通信设备准备好;5是容许发送或清除信号;2是发送数据端;3是接收数据端。

三、实验内容,问题及分析解决

1.简单内核模块的编写

利用课本上的“hello,world”模块程序代码,实现编译模块,加载模块

卸载模块整个过程。

程序代码:

#defineMODULE

#define__KERNEL__//说明:

这两个预定义有的时候是多余的,在2.6的内核中通常会警告出被重定义了。

#include//编写内核模块必须添加的头文件

MODULE_LICENSE(“GPL”);//这个宏用来告诉内核该模块采用的是自由许可证,没有这个宏时没和在加载模块的时候会抱怨如:

hello:

modulelicense“unspecified”taintskernel。

所以建议添上这个宏。

intinit_module(void)//模块的入口函数

{

printk(<1>”hello,world\n”);//注意<1>表示的是消息的优先级,printk根据这优先级的所表示的严重程度对消息进行分类。

以采用宏来表示优先级。

在下面将具体介绍。

return0;

}

voidcleanup_module(void)//模块的退出函数

{

printk(<1>”goodbye,world\n”);

}

printk的八种消息级别。

KERN_EMERG(紧急事件消息,用于系统崩溃之前的提示消息),KERN_ALTER(用于需要立即采取动作的行动),KERN_CRIT(临界状态,通常面临严重的硬件或者软件操作失败),KERN_ERR(用于报告错误状态),KERN_WARNING(用于报告警告信息),KERN_NOTICE(有必要进行提示的正常信息),KERN_INFO(提示性信息,很多驱动程序用来打印他们找到的硬件信息),KERN_DEBUG(用于调试信息)。

这八种级别用尖括号中的整数0—7来表示,整数越小,优先级别越高。

在编写驱动程序时,通常需要通过printk打印信息来对驱动程序进行调试。

实验中我一直希望能在终端中打印出提示信息,可是没有成功,通过查阅资料,知道信息有优先级的分别,终端也有缺省的优先级设置,只有当printk中的优先级比终端的设置要高时才能打印出来。

后来查看/var/log/messages看到了加载和卸载时打印的消息。

此外通过dmesg也可以查看信息。

关于模块的编译。

2.6内核模块的编译和2.6内核模块的编译有很大的区别。

在2.6的内核版本中,使用下面的Makefile就会很容易的编译内核模块。

Ifneq($(KERNELRELEASE),)//如果已经定义了KERNELRELEASE,则说明是从内核构造系统调用的。

obj_m:

=hello.o//上面的赋值语句说明一个模块从目标文件hello.o中构造,得到的目标模块名称为hello.ko,如果要构建的模块名称为module.ko,并且由两个源文件生成(设为file1.c,file2.c)则这一行应该写为:

obj_m:

=module.ko和module-objs:

=file1.ofile2.o这两行。

这对于我们来说感觉很疑惑,怎么这一行就能完成整个编译过程呢。

经阅读课外资料,得知这是利用GNUmake的扩展语法,其余的问题都是由内核构造系统来完成的。

else//若从命令行调用,这时要调用内核构造系统

KERNELDIR=:

/lib/modules/$(shelluname-r)/build//内核构造系统的,目录,其中$(shelluname-r)是获取当前的内核版本

PWD:

=$(shellpwd)//当前目录

default:

$(MAKE)–C$(KERNELDIR)M=$(PWD)modules//两次运行make命令,

第一次从进入内核构造系统,第二次进入当前路径编译。

endif

编译及运行过程:

将内核模块源代码hello.c和Makefile放到同一个目录下,然后进入这个目录,执行make,可以看到make的过程。

生成了hello.ko文件,这就是编译好的内核模块。

执行下面命令进行内核加载:

/sbin/insmodhello.ko。

加载成功后,用lsmod可以看到hello这个模块及其使用计数。

也可以在/proc/modules中看到hello这个模块。

用dmesg可以看到加载时打印出来的hello,world。

这些都说明模块已经正确加载。

当卸载模块时,用命令:

/sbin/rmmodhello.ko。

lsmod及cat/proc/modules都看不到模块了,用dmesg可以看到卸载时打印的信息:

goodbye,world。

说明模块已经被成功卸载。

在这里说明一下,insmod和rmmod都市超级用户权限的命令,在个人电脑上,如果不是以root用户登录的话,需要用命令sudoinsmodhello.ko。

而在实验室电脑上,由于已经被赋予这个权限,所以不需要sudo。

但insmod命令是在sbin目录下。

这点需要注意一下。

到这里,关于模块的编写流程,编译过程,加载及卸载过程已经了解得很清楚了。

这为下面编写设备驱动程序打下了坚实的基础。

2.简单的读写字符设备驱动程序的编写及测试

在这个过程中将建立一个虚拟的字符设备,编写驱动程序,实现用户空间和内核空间的数据交换。

在这个程序中要学习如何编写file_operations结构中的函数,如open,release,write,read及ioctl等。

目前我们暂时掌握这5个函数的编写。

以后有机会还应该关注这个结构体中其他函数的编写,如llseek,poll(poll和select的系统调用,是并行执行的很重要的系统调用),mmap(将设备内存空间映射到进程空间的系统调用)等这些函数对于驱动程序功能的扩展有很大作用。

下面介绍前面几个函数的编写。

本实验中的open方法:

这个实验编得很简单,open函数只是递增了使用计数,防止两个文件同时被打开。

intdevicevalue=0;//自定义的计数变量

intdev_open(structinode*inode,structfile*file)

{if(devicevalue)return-EBUSY;

devicevalue++;

return0;

}

说明:

open函数一般需要完成更多的功能:

如检查设备特定的错误,如果设备是第一次打开,则对其进行初始化。

如果驱动程序驱动的设备不止一种,不止一个,则需要识别次设备号,更新fop指针。

分配并填写file->private_data中的数据结构。

这些功能在课本上提供的SKULL驱动的open中有很好的体现。

关闭release方法:

本实验中的release只是递减使用计数。

一般release

方法还应该完成的工作是:

释放由open分配的在file->private_data中的空间。

关闭设备。

intdev_close(structinode*inode,structfile*file)

{

devicevalue--;

return0;

}

关于read和write方法:

主要是用户空间和内核空间传递数据。

ssize_tdev_read(structfile*file,char*buf,size_tcount,loff_t*offset)

{char*message=“hello,world\0”;

copy_to_user(buf,message,count);

returncount;

}//从内核空间读取数据,向用户空间写数据,

ssize_tdev_write(structfile*file,char*buf,size_tcount,loff_t*offset)

{char*message;

copy_from_user(message,buf,count);

returncount;

}//从用户空间读数据,写进内核空间

说明:

这里的读写都是相对内核空间而言的。

函数参数中的buf指的是用户空间中的buf,是由用户程序系统调用read(fd,buf,count)或这write(fd,buf,count)中的buf传递过来的参数,count也对应于用户程序中的count。

而file指针也和用户程序中的文件描述符fd相对应。

structfile_operationsfops={

.open=dev_open,//也可以用open:

dev_open,

.release=dev_close,

.read=dev_read,

.write=dev_write,

};

说明:

这个声明采用的是标准C的标记化结构初始化语句。

这种语法使得驱动程序在结构的定义发生变化时更具可移植性,并且使得代码紧凑易读。

这个结构体的作用是:

当用户空间执行某种操作时,会由内核找到这个数据结构fops,进而找到相应的系统调用,从而调用驱动程序中编写的对应函数。

程序的最后是编写驱动的入口和退出函数,主要的作用是注册设备和注销设备,此外还有可能需要分配内存和释放内存空间。

intdev_init(void)

{intret;

ret=register_chrdev(MAJOR_NUM,name,&fops);

return0;

}

voiddev_exit(void)

{

unregister_chrdev(MAJOR_NUM,name);

}

module_init(dev_init);

module_exit(dev_exit);

MODULE_LICENSE("GPL");

到这里一个较为简单的字符设备驱动程序已经编写成功。

然后就是编写简单的应用程序来测试一下这个驱动程序。

这里应用程序略去了。

其中有很简单的函数调用:

fd=open(“/dev/name”,O_RDWR);read(fd,buf,4);write(fd,string,4);

整个加载编译测试的流程。

像模块的操作一样先编译加载模块。

创建设备节点mknodrdwrc13477-m666。

这里需要注意的是,这里的主设备号和设备名称必须和驱动程序中定义的一致。

然后编译应用程序。

最后执行。

结果可以实现用户空间和内核空间之间的数据交换。

设备节点创建后可以用cat/proc/devices命令来查看这个设备的使用情况。

3.编写8253内核模块直接操作在扬声器相关端口。

#defineMODULE

#define__KERNEL__

#include

#include

#include//其中包含outb_p,inb_p等函数

intfreq;

MODULE_PARM(freq,"i");//用insmod改变模块参数之前必须先对这些参数进行声明。

上面的宏中带有两个参数,一个是变量名,一个是描述变量类型的字符串。

目前模块参数支持五种类型。

b—一个字节,h—短整形,i—整形,l—长整型,s—字符串。

intinit_module()

{

intdiv,divh,divl;

div=1193182/freq;

divh=div/256;//初值的高字节

divl=div-divh*256;//初值的低字节

outb_p(0xb6,0x43);//写8253命令字

outb_p(divl,0x42);//先送低字节

outb_p(divh,0x42);//后送高字节

outb_p(0x03,0x61);//打开8255的控制信号,开始计数

return0;

}

voidcleanup_module()

{

outb_p(0,0x61);//关闭8255的控制信号,停止计数,扬声器停止发声

}

MODULE_LICENSE("GPL");

MODULE_DESCRIPTION("Insertmodulebyinsmod8253.ofreq=");//宏——模块使用时的说明

说明:

这个模块的加载过程跟第一个模块有点区别,因为加载过程需要设置模块的参数。

/sbin/insmodspk.kofreq=300时,听到声音。

/sbin/rmmodspk.ko声音停止。

改变加载时传递的频率大小,可以发现声音的音调不一样。

4.编写8253设备驱动程序和应用程序,实现音乐播放功能

上面已经通过直接编写8253的内核模块实现了扬声器的发声。

在这个过程中将继续编写设备驱动程序来控制扬声器。

设备驱动是一种更好的方式,可以供应用程序来系统调用,结合应用程序可以实现更加丰富的功能。

这里只介绍release和write函数,因为其他函数跟前面所编写的程序是差不多的。

staticintspeaker_release(structinode*inode,structfile*file)//释放设备

{

Device_Open--;

outb_p(0,0x61);//关闭扬声器

}

staticssize_tspeaker_write(structfile*file,constchar*buffer,size_tlength,loff_t*offset)

{char*str;

copy_from_user(str,buffer,2);

outb_p(0xb6,0x43);//1011,0110初始化

outb_p(str[0],0x42);//写入计数初值

outb_p(str[1],0x42);

outb_p(3,0x61);//打开扬声器

return0;

}

用户程序的编写:

这里只是介绍其中比较重要的部分。

要实现音乐播放,首先要清楚音乐的几个因素:

一是音调,也就是对应于频率,二是音阶,这对应于响音的长度,三是响度。

在这个实验中,我们能控

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 总结汇报 > 学习总结

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1