Linux字符设备驱动编程
1. 基本介绍
linux中设备主要分为三类,字符设备,块设备,网络设备。本节我们主要进行字符设备驱动程序的编写。
2. 编写整体流程图
3. 源码分析
进行字符设备驱动程序的编写我们需要进行3个文件的实现
- simple_char_driver.c 字符驱动程序的整体实现
- Makefile 编译simple_char_driver.c文件生成.ko文件
- app.c 对编写的字符设备驱动程序进行测试
simple_char_driver.c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#define DEVICE_NAME "simple_char"
static dev_t major_number;
static char buffer[256]; //内核空间的一个buf
static struct cdev simple_char_cdev;
//读操作
static ssize_t simple_char_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
//将buffer中的数据copy到buf中。同下文写操作
ssize_t bytes_read = simple_read_from_buffer(buf, count, f_pos, buffer, sizeof(buffer));
return bytes_read;
}
//写操作
static ssize_t simple_char_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
//使用该函数将应用层的传过来的buf中的内容拷贝到(内核空间)的buffer中
//memcpy(buffer,buf); //不行,因为两者不在一个地址空间中
ssize_t bytes_written = simple_write_to_buffer(buffer, sizeof(buffer), f_pos, buf, count);
//到这里我们就成功把用户空间的数据转移到内核空间了
//真正的驱动中,数据从应用层复制到驱动中后,我们就需要根据这个数据去写硬件完成硬件的操作
//下面应该是操作硬件的代码
return bytes_written;
}
//打开设备 file_operatios结构体变量中填充的函数指针open的实体
static int simple_char_open(struct inode *inode, struct file *filp)
{
//这个函数中需要放置的是打开这个设备的硬件操作代码部分
return 0;
}
//关闭设备 file_operatios结构体变量中填充的函数指针release的实体
static int simple_char_release(struct inode *inode, struct file *filp)
{
//这个函数中需要放置的是关闭这个设备的硬件操作代码部分
return 0;
}
//自定义一个file_operations结构体变量,并填充
static struct file_operations fops = {
.owner = THIS_MODULE, //惯例,所有的驱动都有这一个,这也是结构体中唯一一个不是函数指针的元素
.read = simple_char_read, //将来应用read打开时实际调用的函数,下面同理
.write = simple_char_write,
.open = simple_char_open,
.release = simple_char_release,
};
//模块安装函数
//在module_init宏调用函数中去注册字符设备驱动
static int __init simple_char_init(void)
{
int ret;
//动态分配设备号
ret = alloc_chrdev_region(&major_number, 0, 1, DEVICE_NAME);
printk(KERN_INFO "major number %d ,%d\n",MAJOR(major_number),MINOR(major_number));
if (ret < 0) {
printk(KERN_ERR "Failed to allocate major number for %s\n", DEVICE_NAME);
return ret;
}
//初始化cdev
cdev_init(&simple_char_cdev, &fops);
//注册cdev
ret = cdev_add(&simple_char_cdev, major_number, 1);
if (ret < 0) {
printk(KERN_ERR "Failed to add cdev for %s\n", DEVICE_NAME);
unregister_chrdev_region(major_number, 1);
return ret;
}
printk(KERN_INFO "Loaded %s with major number %d %d\n", DEVICE_NAME, MAJOR(major_number),MINOR(major_number));
return 0;
}
// 模块卸载函数
static void __exit simple_char_exit(void)
{
cdev_del(&simple_char_cdev);
unregister_chrdev_region(major_number, 1);
printk(KERN_INFO "Unloaded %s\n", DEVICE_NAME);
}
module_init(simple_char_init); //insmod时调用
module_exit(simple_char_exit); //rmmod时调用
//添加设备的描述信息
MODULE_LICENSE("GPL");
MODULE_AUTHOR(" Name");
MODULE_DESCRIPTION("A simple character device driver");
MODULE_VERSION("0.1");
Makefile
obj-m += simple_char_driver.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
app.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define FILE "/dev/simple_char"
int main(void) {
//打开文件
int fd = open(FILE,O_RDWR);
if(fd < 0){
printf("open %s error.\n",FILE);
return -1;
}
printf("open %s success..\n",FILE);
//注意:当我们写入数据之后读取数据printf("The content of the simple_char is %s \n",buf);
//会为空,这是因为文件指针发生了偏移。所以进行读操作测试时可以将写操作代码进行注释。
//写入数据
const char *data = "hello_device";
ssize_t bytes_written = write(fd,data,strlen(data));
if(bytes_written == -1) {
printf("Failed to write to the FIle\n");
close(fd);
return -1;
}
//读取数据
static char buf[100];
ssize_t bytes_read = read(fd,buf,sizeof(buf)-1);
if(bytes_read == -1) {
printf("Failed to read from the file\n");
close(fd);
return 1;
}
buf[bytes_read] = '\0'; //添加字符串结束符
printf("The content of the simple_char is %s \n",buf);
close(fd);
return 0;
}
4. 操作说明
1.将上文中的源码文件名为(simple_char_driver.c和Makefile)放在Linux系统的同一路径下
2. 终端执行命令cd
到文件所在位置
3. 执行make
之后我们会在文件所在目录中发现一个.ko文件。
4. 执行指令 sudo insmod simple_char_driver.ko
5. 执行 sudo dmesg
查看insmod simple_char_driver.ko
是否被加载到内核。
如果成功被加载到内核会出现Load simple_char with major number MAJOR MINOR
注:MAJOR MINOR 就是我们使用alloc_chrdev_region()函数后系统自动分配给我们的主设备号和从设备号。
下一步操作我们会用到。
- 到
/dev
目录下执行:mknod simple_char c MAJOR MINOR
之后我们在/dev目录下使用 ls |grep simple_char进行查看设备节点是否建立成功 - 将
app.c
文件放在第一步操作时的路径下,进行gcc app.c
操作 - 进行
sudo ./a.out
操作 - 这时我们使用
cat /dev/simple_char
命令可以看到该文件中已有内容 - 测试成功后使用
rm
指令将设备节点删除