本文目录
- 一、知识点
- 1. Linux设备分类
- 2. 设备号
- 3. Linux 字符设备的几种编程模型
- 二、杂项设备模型API
- 1. 杂项设备结构体
- 2. 注册杂项设备
- 3. 注销杂项设备
- 4. copy_from_user
- 5. copy_to_user
- 三、字符设备编程
查看:内核驱动程序编写环境搭建。
一、知识点
1. Linux设备分类
在 Linux 操作系统下有三类设备:一是字符设备,二是块设备,三是网络设备。
●字符设备特点:是一个顺序的数据流设备,对这种设备的读写是按字符进行的,而且这些字符是连续地形成一个数据流。他不具备缓冲区,所以对这种设备的读写是实时的。比如串口,i2c,等。
●块设备特点: 是一种具有一定结构的随机存取设备,对这种设备的读写是按块进行的,他使用缓冲区来存放暂时的数据,待条件成熟后,从缓存一次性写入设备或者从设备一次性读到缓冲区。 ddr 内存条,sd 卡,u 盘,emmc,nandflash 这种,它们的传输是有缓冲的。
●网络设备特点:络设备是面向数据包的接收和发送而设计的。它并不对应于文件系统(/dev 目录下)的节点,而是由系统分配一个唯一的名字(如 eth0)。以太网,wifi,蓝牙,2/3/4G 模块。一种器件可以同时是 2 种设备,例如蓝牙,它是网络设备,也是字符设备。
除了网络设备外,字符设备和块设备都是通过文件系统的系统调用接口 open()、close()、write()、read()等函数既可以访问,应用程序可以通过打开设备文件来访问该设备。
2. 设备号
每个设备文件都都有其文件属性,表示是字符设备(c)还是块设备(b)。
每个设备文件都有一个设备号,由两部分组成:主设备号和次设备号。其中主设备号用于标识驱动程序,次
设备号用于标识使用同一个设备驱动程序的不同的硬件设备,比如有两个串口,就可以用从设备号来区分他们。
3. Linux 字符设备的几种编程模型
在目前版本 Linux 3.5 版本内核中,字符设备驱动程序的编程模型有三种:杂项设备驱动模型
、早期经典字符设备驱动模型
和 Linux 2.6 版本中新出现的 Linux 2.6 标准字符设备驱动模型
。本章将具体介绍杂项设备驱动模型来编写字符设备。其余两个模型配置复杂,不常用。
二、杂项设备模型API
在Linux操作系统中,杂项设备是一类设备文件,用于处理各种非标准或非主要类别的设备。杂项设备通常不属于特定的主要设备类别(如块设备、字符设备等),但它们提供了一种灵活的方式来支持各种特殊硬件或虚拟设备。 头文件:#include <linux/miscdevice.h>
。
主设备号:固定是 10。
次设备号:0~255, 当传递 255 时候表示自动分配次设备号。
1. 杂项设备结构体
在Linux内核中,struct miscdevice结构体用于定义和管理杂项设备。
struct miscdevice {
int minor; //次设备号,使用MISC_DYNAMIC_MINOR表示系统自动分配其次设备号。
const char *name; //设备名称,通常会在/dev目录下创建一个对应的设备文件。
const struct file_operations *fops;//指向文件操作结构的指针,该结构定义了设备文件的操作函数,如打开、关闭、读、写、ioctl等。
///* 下面的成员是供内核使用 ,驱动编写不需要理会,我们只需要配置上面三个成员即可*
struct device *parent;
struct device *this_device;
const struct attribute_group **groups;
const char *nodename;
umode_t mode;
};
在杂项设备结构体中struct file_operations 是一个非常重要的结构体,它定义了文件操作接口。这些接口允许用户空间程序与内核模块进行交互,通过这些操作可以对设备进行读写、打开、关闭、控制等操作。我们用到哪个就实现哪个函数就行。
struct file_operations {
struct module *owner; // 指向模块的指针,通常设置为 THIS_MODULE,用于模块引用计数
loff_t (*llseek) (struct file *, loff_t, int); // 移动文件位置指针,类似于用户空间的 lseek
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); // 读操作函数,从设备读取数据
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); // 写操作函数,向设备写入数据
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *); // 迭代读操作,适用于较新的文件IO框架
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *); // 迭代写操作,适用于较新的文件IO框架
int (*iterate) (struct file *, struct dir_context *); // 文件目录迭代,用于读取目录内容
int (*iterate_shared) (struct file *, struct dir_context *); // 文件目录迭代的共享版本
unsigned int (*poll) (struct file *, struct poll_table_struct *); // 轮询操作,用于非阻塞IO
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); // 设备控制操作,无需大内核锁
long (*compat_ioctl) (struct file *, unsigned int, unsigned long); // 32位系统上与 ioctl 兼容的操作
int (*mmap) (struct file *, struct vm_area_struct *); // 内存映射操作,将设备内存映射到用户空间
int (*open) (struct inode *, struct file *); // 打开操作
int (*flush) (struct file *, fl_owner_t id); // 刷新操作,在文件关闭之前调用
int (*release) (struct inode *, struct file *); // 释放操作,在文件关闭时调用
int (*fsync) (struct file *, loff_t, loff_t, int datasync); // 同步操作,将文件缓冲区的数据同步到存储设备
int (*fasync) (int, struct file *, int); // 异步IO操作的设置
int (*lock) (struct file *, int, struct file_lock *); // 文件锁操作
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); // 发送页面数据
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); // 获取未映射区域,用于内存映射
int (*check_flags)(int); // 检查文件打开标志
int (*flock) (struct file *, int, struct file_lock *); // 文件锁操作
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); // 将数据从管道写入文件
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); // 将数据从文件读入管道
int (*setlease)(struct file *, long, struct file_lock **, void **); // 设置文件租约
long (*fallocate)(struct file *, int, loff_t, loff_t); // 为文件分配空间
void (*show_fdinfo)(struct seq_file *m, struct file *); // 显示文件描述符的信息
};
2. 注册杂项设备
返回值:0:表示注册成功;负数:注册失败
int misc_register(struct miscdevice * misc)
//传入杂项设备结构体。
3. 注销杂项设备
不需要判断返回值。
int misc_deregister(struct miscdevice *misc)
//传入杂项设备结构体。
4. copy_from_user
是 Linux 内核中用于从用户空间拷贝数据到内核空间的函数。这个函数是确保内核空间和用户空间之间数据安全传输的重要机制之一。因为直接访问用户空间内存是不安全的,内核必须通过这些函数来处理数据的传递,以确保不会因为非法内存访问导致系统崩溃。
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);
//to: 指向内核空间的目标缓冲区。
//from: 指向用户空间的源缓冲区。
//n: 要拷贝的字节数。
5. copy_to_user
用于将数据从内核空间拷贝到用户空间。这个函数用于内核模块和设备驱动程序中,以安全的方式将数据传递给用户空间。
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
//to: 指向用户空间的目标缓冲区。
//from: 指向内核空间的源缓冲区。
//n: 要拷贝的字节数。
三、字符设备编程
编写杂项设备驱动节点给用户使用,编写应用层app.c
测试驱动功能是否实现。
hello.c
#include <linux/module.h> //模块
#include <linux/kernel.h> //内核
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#define KBUF_SIZE (1024)
#define MIN(x,y) ( (x)<(y)? (x):(y) ) //判断哪个小,选哪个
char kernel_buff[KBUF_SIZE];
int misc_open(struct inode * node, struct file * fp)
{
return 0;
}
int misc_release(struct inode *node, struct file *fp)
{
return 0;
}
ssize_t misc_read(struct file *fp, char __user *ubuf , size_t size, loff_t *offset)
{
int ret;
int length=strlen(kernel_buff);
ret=copy_to_user(ubuf, kernel_buff, length);
if(ret !=0){
pr_err("copy_to_user error\r\n");
return -EINVAL;
}
return length; //返回成功读取的字节数
}
ssize_t misc_write(struct file *fp, const char __user *ubuf, size_t size, loff_t *offset)
{
int ret;
ret=copy_from_user(kernel_buff, ubuf, MIN(KBUF_SIZE,size));
if(ret !=0){
pr_err("copy_from_user error\r\n");
return -EINVAL;
}
pr_err("%s\r\n",kernel_buff); //打印出用户写来的数据
return MIN(KBUF_SIZE,size); //返回成功写入的字节数
}
//描述一个文件操作集
const struct file_operations misc_fops={
.owner=THIS_MODULE, //文件操作集的拥有者是 本模块。
.open =misc_open, //应用层对本模块的驱动节点open时触发的函数
.release=misc_release, //应用层对本模块的驱动节点close时触发的函数
.read=misc_read, //应用层对本模块的驱动节点read时触发的函数
.write=misc_write, //应用层对本模块的驱动节点write时触发的函数
};
//描述一个杂项设备
struct miscdevice misc={
.minor=MISC_DYNAMIC_MINOR, //由内核自动分配次设备号,misc的主设备号为10.
.name ="qjl", //在/dev下生成的驱动节点的名称,不能有空格!
.fops =&misc_fops,
};
static int __init misc_init(void)
{
int ret;
ret=misc_register(&misc); //向内核注册一个杂项设备
if(ret <0){
pr_err("misc_register error\r\n");
return -1;
}
return 0;
}
static void __exit misc_exit(void)
{
misc_deregister(&misc); //从内核注销一个杂项设备
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL");
app.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
int fd;
ssize_t w_size;
ssize_t r_size;
char w_buff[128] = "hello world!"; // 向内核写入的数据
char r_buff[128] = {0}; // 读取的数据缓冲区,初始化为0
fd = open("/dev/qjl", O_RDWR);
if (fd < 0) {
perror("open error");
return -1;
}
w_size = write(fd, w_buff, strlen(w_buff));
printf("w_size: %ld\n", w_size);
// 移动文件指针到文件开头
// lseek(fd, 0, SEEK_SET);
r_size = read(fd, r_buff, sizeof(r_buff) - 1); // 预留一个字节用于 null 终止符
if (r_size < 0) {
perror("read error");
close(fd);
return -1;
}
r_buff[r_size] = '\0'; // 确保字符串以 null 终止
printf("r_size: %ld data: %s\n", r_size, r_buff);
close(fd);
return 0;
}
●insmod hello.ko