首页 / LINUX / linux字符设备驱动实验
linux字符设备驱动实验
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了linux字符设备驱动实验,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含12510字,纯文字阅读大概需要18分钟。
内容图文
![linux字符设备驱动实验](/upload/InfoBanner/zyjiaocheng/923/154252474b4c48b7a45ba39c23b64c9f.jpg)
![linux字符设备驱动实验 - 文章图片](/upload/getfiles/0001/2021/5/9/20210509063215224.jpg)
linux字符设备驱动实验
实验目录
- 一、Linux device driver 的概念
- 二、最简单的字符设备驱动程序
- 三、测试驱动程序:testRead.c
- 四、测试驱动程序:testWrite.c
- 五、Makefile文件
- 六、RedHat演示
- 1. 首先用rmmod把我之前insmod挂载的驱动testDriver.ko 卸载掉
- 2. 把内核日志清空dmesg -c
- 3. 用rm -f把我之前mknod的字符设备文件test删除
- 5. make编译Makefile生成testDriver.ko文件
- 6. insmod挂载驱动testDriver.ko
- 7. lsmod查看挂载的驱动
- 8. dmesg查看内核日志的主设备号test_major
- 9. mknod创建字符设备驱动文件test
- 10. chmod修改字符设备驱动文件test权限为777
- 11. 测试testRead -> gcc编译testRead.c生成testRead
- 12. 运行testRead,并查看内核日志信息
- 13. 测试testWrite -> gcc编译testWrite.c生成testWrite
- 14. 运行testWrite,并查看内核日志dmesg
- 15. 卸载驱动、删除设备文件
一、Linux device driver 的概念
系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。
设备驱动程序是内核的一部分,它完成以下的功能:
1. 对设备初始化和释放。2 .把数据从内核传送到硬件和从硬件读取数据。
(3. 读取应用程序传送给设备文件的数据和回送应用程序请求的数据。)
4.检测和处理设备出现的错误。
为什么要建立设备文件
我们的linux操作系统跟外部设备(如磁盘、光盘等)的通信都是通过设备文件进行的,应用程序可以打开、关闭、读写这些设备文件,从而对设备进行读写,这种操作就像读写普通的文件一样easy。linux为不同种类的设备文件提供了相同的接口,比如read(),write(),open(),close()。
所以在系统与设备通信之前,系统首先要建立一个设备文件,这个设备文件存放在/dev目录下。
linux设备文件类型
在Linux操作系统下有三类主要的设备文件类型:
一是字符设备,二是块设备,三是网络设备。
在linux中设备文件在/dev的目录下(要分清这些设备文件的类型)
linux设备文件类型判断方法
Linux文件类型和文件的文件名所代表的意义是两个不同的概念,在linux中文件类型与文件扩展名没有关系。它不像Windows那样是依靠文件后缀名来区分文件类型的,在linux中文件名只是为了方便操作而的取得名字。Linux文件类型常见的有:普通文件、目录、字符设备文件、块设备文件、符号链接文件等。
1,普通文件类型
2,目录文件类型
在linux中,它的思想是一切皆是文件,目录文件也就是Windows中的目录,也就是能用 cd 命令进入的。第一个属性为 [d],例如 [drwxr-xr-x]。
3,字符设备文件
即串行端口的接口设备,例如键盘、鼠标等等。第一个属性为 [c]。
4,块设备文件
即存储数据以供系统存取的接口设备,简单而言就是硬盘。例如一号硬盘的代码是 /dev/hda1等文件。第一个属性为 [b]。
5,套接字文件
这类文件通常用在网络数据连接。可以启动一个程序来监听客户端的要求,客户端就可以通过套接字来进行数据通信。第一个属性为 [s],最常在 /var/run目录中看到这种文件类型。
6,管道文件
FIFO也是一种特殊的文件类型,它主要的目的是,解决多个程序同时存取一个文件所造成的错误。FIFO是first-in-first-out(先进先出)的缩写。第一个属性为 [p]。
7,链接文件
类似Windows下面的快捷方式。第一个属性为 [l],例如 [lrwxrwxrwx]。
mknod命令创建设备文件
其实系统默认情况下就已经生成了很多设备文件,但有时候我们需要自己手动新建一些设备文件,这个时候就会用到像mknod这样的命令。
mknod 的标准形式为: mknod DEVNAME {b | c} MAJOR MINOR
1,DEVNAME是要创建的设备文件名,如果想将设备文件放在一个特定的文件夹下,
就需要先用mkdir在dev目录下新建一个目录;
2, b和c 分别表示块设备和字符设备:
b表示系统从块设备中读取数据的时候,
直接从内存的buffer中读取数据,而不经过磁盘;
c表示字符设备文件与设备传送数据的时候是以字符的形式传送,
一次传送一个字符,比如打印机、终端都是以字符的形式传送数据;
3,MAJOR和MINOR分别表示主设备号和次设备号:
为了管理设备,系统为每个设备分配一个编号,一个设备号由主设备号和次设备号组成。
主设备号标示某一种类的设备,次设备号用来区分同一类型的设备。
linux操作系统中为设备文件编号分配了32位无符号整数,
其中前12位是主设备号,后20位为次设备号,
所以在向系统申请设备文件时主设备号不好超过4095,次设备号不好超过2^20 -1。
下面,我们就可以用mknod命令来申请设备文件了。
mknod /dev/test c 128 512
二、最简单的字符设备驱动程序
需要用到make命令
安装GCC、G++后才有make命令
首先介绍一下头文件
#include <asm/uaccess.h>
#include <linux/module.h>
#define THIS_MODULE (&__this_module)
上面这一段定义了一些版本信息,虽然用处不是很大,但也必不可少。
#include <linux/types.h> //基本的类型定义
#include <linux/fs.h> //文件系统
#include <linux/mm.h> //内存管理
#include <linux/errno.h> //错误码
#include <asm/segment.h>// 汇编语言的一段程序
testDriver.c
#include <linux/types.h> //基本的类型定义
#include <linux/fs.h> //文件系统
#include <linux/mm.h> //内存管理
#include <linux/errno.h> //错误码
#include <asm/segment.h>// 汇编语言的一段程序
#include <asm/uaccess.h>
#include <linux/module.h>
#define THIS_MODULE (&__this_module)
unsigned int test_major = 0; //定义主设备号(通过系统自动获取主设备号)
//定义一个读函数,对应read系统调用。(static表示该函数在本文件有效)
char temp[64]= {0};
static int read_test(struct file *file, char *buf, int count, loff_t *offt)
{
int left;
if (access_ok(VERIFY_WRITE, buf, count) == -EFAULT ) //验证是否可以向buf中写入数据。
return -EFAULT;
for(left = count ; left > 0 ; left--)
{
__put_user(1, buf); //把内核空间的数据拷贝到用户空间buf中
buf++;
}
printk(KERN_EMERG "test: OK %d\n",count);
return count;
}
///
static int write_test( struct file *file, const char *buf, int count ,struct inode *inode)
{
if (access_ok(4, buf, count) == -EFAULT ) //验证是否可以向buf中写入数据。
return -EFAULT;
elseprintk("read_ok\n");
if(copy_from_user(temp,buf,count))
return -EFAULT;
else {
printk("kernelSpace temp is : %s\n",temp);
printk(KERN_EMERG "testwriter: OK %d\n",count);
}
return count;
}
static int open_test(struct inode *inode, struct file *file )
{
//MOD_INC_USE_COUNT; //注册到内核后,模块计数加一
try_module_get(THIS_MODULE);
return 0;
}
static void release_test(struct inode *inode, struct file *file )
{
//MOD_DEC_USE_COUNT; //模块数减一
module_put(THIS_MODULE);
}
//
struct file_operations test_fops =
{
.owner = THIS_MODULE,
.read = read_test,
.write = write_test,
.open = open_test,
.release = release_test
};
/
int init_module1(void)
//int module_iit(void)
{
int result;
result = register_chrdev(0, "test", &test_fops); //注册字符型设备到内核中
if (result < 0)
{
printk(KERN_INFO "test: can't get major number\n"); //KERN_INFO打印信息
return result;
} else {
printk("test get success major\n");
}
if (test_major == 0)
test_major = result; /* 获取系统默认的主设备号 */
if(test_major == 0) {
printk("test_major == 0\n");
} else {
printk("test_major != 0 test_major = %d \n",test_major);
}
return 0;
}
void cleanup_module1(void)
//void module_exit(void)
{
unregister_chrdev(test_major,"test");
// printk(“%u has been relased”,test_major);
}
module_init(init_module1);
module_exit(cleanup_module1);
每个程序都有入口和出口,我们写的C语言程序入口函数是main函数,那么驱动程序也有自己的入口和出口; module_init(init_module1);module_exit(cleanup_module1);
这两个函数就本别是驱动程序的入口和出口函数;
入口函数调用了init_module1()函数它的作用是初始化驱动程序,其做了两件事情:
1.注册字符型设备到内核中
2. 获取系统默认的主设备号
linux的设备驱动程序工作的基本原理
由于用户进程是通过设备文件同硬件打交道,对设备文件的操作方式不外乎就是一些系统调用,如 open,read,write,close…, 注意,不是fopen, fread,但是如何把系统调用和驱动程序关联起来呢?这需要了解一个非常关键的数据结构:struct file_operations test_fops =
{
.owner = THIS_MODULE,
.read = read_test,
.write = write_test,
.open = open_test,
.release = release_test
};
这个结构的每一个成员的名字都对应着一个系统调用。用户进程利用系统调用在对设备文件进行诸如read/write操作时,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数。这是linux的设备驱动程序工作的基本原理。
编写设备驱动程序的功能
既然是这样,则编写设备驱动程序的主要工作就是编写子函数,并填充file_operations的各个域。init_module1(初始化功能)
我们查看(dmesg查看内核日志)获取到的系统默认主设备号来mknod相应的设备文件; 这里注册的字符设备叫“test”
int init_module1(void)
//int module_init(void)
{
int result;
result = register_chrdev(0, "test", &test_fops); //注册字符型设备到内核中
if (result < 0)
{
printk(KERN_INFO "test: can't get major number\n"); //KERN_INFO打印信息
return result;
} else {
printk("test get success major\n");
}
if (test_major == 0)
test_major = result; /* 获取系统默认的主设备号 */
if(test_major == 0) {
printk("test_major == 0\n");
} else {
printk("test_major != 0 test_major = %d \n",test_major);
}
return 0;
}
read_test功能
read是用户把内核空间的内容读到用户空间中需要用到put_user函数
static int read_test(struct file *file, char *buf, int count, loff_t *offt)
{
int left;
if (access_ok(VERIFY_WRITE, buf, count) == -EFAULT ) //验证是否可以向buf中写入数据
return -EFAULT;
for(left = count ; left > 0 ; left--)
{
__put_user(1, buf); //把内核空间的数据拷贝到用户空间buf中
buf++;
}
printk(KERN_EMERG "test: OK %d\n",count);
return count;
}
linux中printf函数是把内容输出到标准输出设备中,printk则是把内容输出到内核日志中 ;
我们可以用dmesg命令查看内核日志的信息
Linux dmesg命令
语法
dmesg [-cn][-s <缓冲区大小>]
参数说明:
-c 显示信息后,清除ring buffer中的内容。
-s<缓冲区大小> 预设置为8196,刚好等于ring buffer的大小。
-n 设置记录信息的层级。
write_test功能
write是写,把用户空间的内容写到内核空间里,这里我没有用get_user函数,用的是copy_from_user(temp,buf,count)函数,其含义是把用户空间的buf的count个字符拷贝到内核空间的temp中。static int write_test( struct file *file, const char *buf, int count ,struct inode *inode)
{
if (access_ok(4, buf, count) == -EFAULT ) //验证是否可以向buf中写入数据。
return -EFAULT;
else printk("read_ok\n");
if(copy_from_user(temp,buf,count))
return -EFAULT;
else {
printk("kernelSpace temp is : %s\n",temp);
printk(KERN_EMERG "testwriter: OK %d\n",count);
}
return count;
}
三、测试驱动程序:testRead.c
这里的open打开的是字符设备驱动文件test文件
gcc 编译testRead.c生成可执行程序testRead
gcc testRead.c -o testRead
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int testdev;
int i;
char buf[10];
testdev = open("/dev/test",O_RDWR);
if ( testdev == -1 )
{
printf("Cann't open file \n");
exit(0);
}
read(testdev,buf,10);
for (i = 0; i < 10;i++)
printf("%d\n",buf[i]);
close(testdev);
return 0;
}
四、测试驱动程序:testWrite.c
这里的open打开的是字符设备驱动文件test文件
gcc 编译testWrite.c生成可执行程序testWrite
gcc testWrite.c -o testWrite
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int testdev;
int i;
char buf[20] = "Hello World!";
testdev = open("/dev/test",O_RDWR);
if ( testdev == -1 )
{
perror("error is :");
exit(0);
}
write(testdev,buf,strlen(buf));
close(testdev);
return 0;
}
五、Makefile文件
#KERNELDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
obj-m := -DEXPORT_SYMTAB
obj-m := testDriver.o
#在编绎内核本身export-objs中的文件时,make会增加一个"-DEXPORT_SYMTAB"编绎标志,它使源文件嵌入mo#dversions.h文件,将EXPORT_SYMBOL宏展开中的函数名字符串进行版本名扩展;同时,它也定义_set_ver()宏#为一空操作,使代码中的函数名不受其影响。
#在编绎模块时,make会增加"-include=linux/modversion.h -DMODVERSIONS"编绎标志,使模块中代码的函##数名得到相应版本扩展。
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
obj-m := testDriver.o可以自己寻找到testDriver.c但是要保证.c和.o的文件名字一样,并且Makefile和testDriver.c在同一目录下,不然make不会成功;
make编译完成以后会生成testDriver.ko 文件使用insmod命令把它挂到系统中,再创建字符驱动文件test,之后要chmod修改test的文件权限777;
六、RedHat演示
如果第一次做则不需要做第一步和第三步1. 首先用rmmod把我之前insmod挂载的驱动testDriver.ko 卸载掉
![在这里插入图片描述](https://www.icode9.com/i/ll/?i=20210504132045118.png?,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xhenlfR29hdA==,size_16,color_FFFFFF,t_70)
2. 把内核日志清空dmesg -c
会打印出来很多日志信息
3. 用rm -f把我之前mknod的字符设备文件test删除
查看test: ls /dev
删除它
5. make编译Makefile生成testDriver.ko文件
在Makefile和testDriver所在的目录中打开终端,或者cd到这个目录下
make编译
如果有警告不用理会
6. insmod挂载驱动testDriver.ko
7. lsmod查看挂载的驱动
8. dmesg查看内核日志的主设备号test_major
9. mknod创建字符设备驱动文件test
10. chmod修改字符设备驱动文件test权限为777
11. 测试testRead -> gcc编译testRead.c生成testRead
12. 运行testRead,并查看内核日志信息
13. 测试testWrite -> gcc编译testWrite.c生成testWrite
14. 运行testWrite,并查看内核日志dmesg
15. 卸载驱动、删除设备文件
到这里就全部演示完毕
内容总结
以上是互联网集市为您收集整理的linux字符设备驱动实验全部内容,希望文章能够帮你解决linux字符设备驱动实验所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。