设备驱动: Linux系统下的字符设备驱动程序编程

一、实验目的
通过一个简单的设备驱动的实现过程。学会Linux中设备驱动程序的编写。
二、实验环境
Ubuntu20.04TSL,Linux5.10.0
三、实验内容
1、编写一个字符设备驱动程序,并在设备的打开操作中打印主次设备号;
2、编写一个用户测试程序,实现设备的读操作。
四、实验原理 实验中用到的系统调用函数(包括实验原理中介绍的和自己采用的),实验步骤
字符设备是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后顺序。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED设备等。每一个字符设备都在/dev目录下对应一个设备文件。linux用户程序通过设备文件(或称设备节点)来使用驱动程序操作字符设备。一个字符设备都有一个主设备号和一个次设备号。主设备号用来标识与设备文件相连的驱动程序,用来反映设备类型。次设备号被驱动程序用来辨别操作的是哪个设备,用来区分同类型的设备。
描述字符设备的数据结构
在Linux 2.6内核中的字符设备用cdev结构来描述,其定义如下:
struct cdev
{
struct kobject kobj; //类似对象类,驱动模块的基础对象
struct module *owner; //所属内核模块,一般为THIS_MODULE
const struct file_operations *ops; //文件操作结构
struct list_head list;
dev_t dev; //设备号,int 类型,高12位为主设备号,低20位为次设备号
unsigned int count;
};
2、字符设备驱动模块的编写
实现一个基本的字符驱动设备需要以下几个部分:字符设备驱动模块的加载、卸载函数和file_operations结构中的成员函数。具体步骤如下:
(1)分配和释放设备号
在设备驱动程序中,注册设备前首先要向系统申请设备号,
分配设备号有静态和动态的两种方法:
静态分配(register_chrdev_region()函数)
动态分配(alloc_chrdev_region())
通过 unregister_chrdev_region()函数释放已分配的(无论是静态的还是动态的)设备号。

(2)定义并初始化一个struct file_operations结构,并实现其中的操作函数
static struct file_operations cdrv_fops = {
.owner=THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open=cdrv_open,
.read=cdrv_read,
.write=cdrv_write,
};
static int cdrv_open(struct inode *inode, struct file *filp)
static ssize_t cdrv_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
static ssize_t cdrv_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)

(3)字符设备的注册

(4)删除字符设备

(5)注销设备号

(6)模块声明 MODULE_LICENSE(“GPL”);
(7)加载模块 module_init(cdrv_init);
(8)卸载模块module_exit(cdrv_exit);
3、编译模块Makefile文件
4、利用mknod命令在/dev目录下为字符设备生成对应的节点

五、实验结果分析(截屏的实验结果,与实验结果对应的实验分析)
字符设备驱动模块 device_driver.c
用户测试程序test.c
编写Makefile文件
设备驱动: Linux系统下的字符设备驱动程序编程_第1张图片

编译字符驱动内核模块
…………# make
执行发现编译错误,对函数被拼写错误,分号的中英文格式等问题进行修改
设备驱动: Linux系统下的字符设备驱动程序编程_第2张图片

根据提示成功修改后,再次执行make指令,则会产生一些系列模块文件

设备驱动: Linux系统下的字符设备驱动程序编程_第3张图片

…………# sudo insmod device_driver.ko
采用高级管理员权限插入模块到内核
查看系统信息
…………#dmesg
采用|管道配合grep过滤不需要的文件,筛选出刚出入的devic_driver模块的进程号为16384
在这里插入图片描述

插入的设备需要在/dev目录下生成对应的结点
…………# mknod /dev/demo_drv c 主设备号 0
设备驱动: Linux系统下的字符设备驱动程序编程_第4张图片

使用cat /proc/devices指令,查看当前系统设备上已经存在的设备号,查看到动态申请237号作为本次实验的主设备号
执行指令:sudo mknod /dev/demo_drv c 237 0
在这里插入图片描述

查看/dev目录情况
…………# ls /dev
在目录/dev下黄色标明文件名的文件,都是当前系统的设备;在这里也可以查看到刚刚插入的demo_drv字符设备
设备驱动: Linux系统下的字符设备驱动程序编程_第5张图片

编译用户测试程序
………# gcc test.c -o test
………#./test
在这里插入图片描述

………# dmesg
使用dmesg | tail查看最近的日记信息
设备驱动: Linux系统下的字符设备驱动程序编程_第6张图片
Makefile

ifneq ($(KERNELRELEASE),)
	obj-m+=device_driver.o
else
	KERNELDIR:=/lib/modules/$(shell uname -r)/build
	PWD:=$(shell pwd)
default:
	$(MAKE) -C $(KERNELDIR)  M=$(PWD) modules
clean:
	rm -rf *.o *.mod.c *.ko *.order *.symvers .*.cmd .tmp_versions
endif

Test.c

#include
#include
#include
#include

#define DEMO_DEV_NAME "/dev/demo_drv"
int main()
{
	char buffer[64];
	int fd;
	fd=open(DEMO_DEV_NAME, O_RDONLY);
	if(fd<0)
	{
		printf("open device %s failed\n", DEMO_DEV_NAME);
		return -1;
	}
	read(fd,buffer,64);
	printf("%s\n",buffer);
	close(fd);
	return 0;
}

Device_driver.c

#include
#include
#include
#include
#include
#include

#define DEMO_NAME "my_demo_dev" //设备名

static dev_t dev;
static struct cdev* demo_cdev;
static signed count=1;

static int demodrv_open(struct inode* inode, struct file* file)
{
	int major=MAJOR(inode->i_rdev);
	int minor=MINOR(inode->i_rdev);
	printk("%s: major=%d, minor=%d\n",__func__, major, minor);
	return 0;
}

static ssize_t demodrv_read(struct file* file, char __user* buf, size_t lbuf,loff_t* ppos)
{
	printk("%s enter\n",__func__);
	return 0;
}

static ssize_t demodrv_write(struct file* file, const char __user* buf, size_t count, loff_t* f_pos)
{
	printk("%s enter\n",__func__);
	return 0;
}

static const struct file_operations demodrv_fops={
	.owner=THIS_MODULE,
	.open=demodrv_open,
	.read=demodrv_read,
	.write= demodrv_write,
};

static int __init simple_char_init(void)
{
	int ret;
	ret=alloc_chrdev_region(&dev,0,count,DEMO_NAME); //动态申请设备号
	if(ret)
	{
		printk("failed to allocate char device region\n");
		return ret;
	}
	demo_cdev=cdev_alloc(); //分配设备空间
	if(!demo_cdev)
	{
		printk("cedv_alloc_failed\n");
		goto unregister_chrdev;
	}
	cdev_init(demo_cdev, &demodrv_fops); //设备空间进行初始化和赋值操作
	ret=cdev_add(demo_cdev, dev, count); //设备添加进系统函数
	if(ret)
	{
		printk("cdev_add failed\n");
		goto cdev_fail;
	}
	printk("successed register char device: %s\n", DEMO_NAME);
	printk("Major number=%d, Minor number=%d\n", MAJOR(dev), MINOR(dev));
	return 0;
cdev_fail:
		cdev_del(demo_cdev); //删除字符设备
unregister_chrdev:
		unregister_chrdev_region(dev, count); //注销设备号
		return ret;
}

static void __exit simple_char_exit(void)
{
	printk("removing device\n");
	if(demo_cdev)
		cdev_del(demo_cdev);
	unregister_chrdev_region(dev, count); //注销字符设备
	return ;
}

MODULE_LICENSE("GPL");  //模块声明
module_init(simple_char_init);  //加载模块
module_exit(simple_char_exit);  //卸载模块

你可能感兴趣的:(Linux,linux,驱动开发,运维)