文章目录
- 字符设备、块设备、网络设备基础概念
-
- 字符设备
- 块设备
- 网络设备
- 字符设备驱动框架(用户空间和内核空间)
-
- 设备号,设备节点
- 字符设备驱动框架
- 字符设备驱动
-
- 向系统申请—主设备号
-
- 示例-主设备号申请
- 测试
- 创建设备节点
-
- 手动创建
- 测试
- 自动创建(通过udev/mdev机制)
-
- 创建一个类
- 创建一个设备文件
- 销毁动作:
- 例—代码
- 测试
- 在驱动中实现文件io的接口,应用程序可以调用文件io
-
- 驱动中实现文件io操作接口:struct file_operations
-
- 例—驱动代码
- 应用程序如何去调用文件io去控制驱动–open,read,…
-
- 例—应用程序代码
- 例—Makefile
- 实验效果—应用程序控制驱动
- 应用程序需要传递数据给驱动
- 控制外设,其实就是控制地址,内核驱动中是通过虚拟地址操作
-
- 示例—led闪烁
-
- 驱动代码 chr_drv.c
- 应用程序 chr_test.c
- 参考
字符设备、块设备、网络设备基础概念
字符设备
-
举例
-
LCD屏,keyboard,I2C从设备
-
字符设备的特点
字符设备产生的数据是字符流
大部分的数据都是从寄存器中产生,速度较快,量不是很大
应用空间中读取数据按统一的接口(文件IO)来读取
块设备
-
块设备举例
存储设备(硬盘,SD卡,U盘,flash) -
块设备的特点
数据以块为单位
读取速度相对较慢,数据量很大
块设备分层管理,比如:文件管理层,缓冲区层等
网络设备
-
网络设备举例
以太网,WiFi -
网络设备的特点
网络设备走协议,所以linux中把网络设备全部做成socket套接字设备
按照socket统一的接口来管理
分层比较复杂
字符设备驱动框架(用户空间和内核空间)
设备号,设备节点
- 设备号和驱动相关联
- 设备号是一个ID,设备节点就是驱动文件
- 字符设备和块设备是独立的,虽然设备号可能相同,但却是不同的设备
字符设备驱动框架
作为字符设备驱动要素:1,必须有一个设备号,用在众多到设备驱动中进行区分2,用户必须知道设备驱动对应到设备节点(设备文件)linux把所有到设备都看成文件crw-r----- 1 root root 13, 64 Mar 28 20:14 event0crw-r----- 1 root root 13, 65 Mar 28 20:14 event1crw-r----- 1 root root 13, 66 Mar 28 20:14 event23,对设备操作其实就是对文件操作,应用空间操作open,read,write的时候实际在驱动代码有对应到open, read,write
字符设备驱动
- 注册获取设备号
- 初始化设备
- 操作设备 file_operations – open release read write ioctl…
- 两个宏定义 module_init module_exit 两个命令 insmod rmmod
- 注册设备号 register_chrdev_region
- cdev_init 初始化字符设备
- cdev_add 添加字符设备到系统
驱动是被动调用的,是被应用程序触发的
应用程序的 open 调用到驱动的file_operations 的 open
应用程序的 read 调用到驱动的file_operations 的 read
应用程序的 write 调用到驱动的file_operations 的 write
应用程序的 ioctl 调用到驱动的file_operations 的 ioctl
应用程序的 close 调用到驱动的file_operations 的 release
向系统申请—主设备号
作为驱动必须有一个主设备号–向系统申请
int register_chrdev(unsigned int major, const cha-r * name, const struct file_operations * fops)
参数说明:
-
参数1:主设备号
设备号(32bit–dev_t)==主设备号(12bit) + 次设备号(20bit)
主设备号:表示一类设备–camera
次设备号: 表示一类设备中某一个:前置,后置
给定到方式有两种:
1,动态–参数1直接填0
2,静态–指定一个整数,250 -
参数2: 描述一个设备信息,可以自定义
/proc/devices列举出所有到已经注册的设备 -
参数3: 文件操作对象–提供open, read,write
返回值: 正确返回0,错误返回负数
释放设备号
void unregister_chrdev(unsigned int major, const char * name)
参数说明:
参数1:主设备号
参数2: 描述一个设备信息,可以自定义
示例-主设备号申请
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>/*
设备号给定方式有两种:1,动态--参数1直接填02,静态--指定一个整数,250
*/
static unsigned int dev_major = 250;const struct file_operations my_fops = {
};static int __init chr_dev_init(void)
{//装载一般都是申请设备号资源//申请设备号int ret;ret = register_chrdev(dev_major, "chr_dev_test", &my_fops);if(ret == 0){printk("register ok\n");}else{printk("register failed\n");return -EINVAL;}return 0;
}static void __exit chr_dev_exit(void)
{//卸载一般都是释放资源unregister_chrdev(dev_major, "chr_dev_test");
}module_init(chr_dev_init);
module_exit(chr_dev_exit);
MODULE_LICENSE("GPL");
测试
模块装载前观察开发板所有设备号,并没有申请的设备号
装载chr_drv.ko ,观察开发板设备编号,发现申请设备号250出现了
创建设备节点
手动创建
缺点/dev/目录中文件都是在内存中,断电后/dev/文件就会消失mknod /dev/设备名 类型 主设备号 次设备号(主设备号要和驱动中申请的主设备号保持一致)比如:mknod /dev/chr0 c 250 0[root@farsight drv_module]# ls /dev/chr0 -lcrw-r--r-- 1 0 0 250, 0 Jan 1 00:33 /dev/chr0
测试
自动创建(通过udev/mdev机制)
创建一个类
struct class *class_create(owner, name)
- 参数1: THIS_MODULE
- 参数2: 字符串名字,自定义
- 返回一个class指针
创建一个设备文件
struct device *device_create(struct class * class, struct device * parent, dev_t devt, void * drvdata, const char * fmt,...)
-
参数1: class结构体,class_create调用之后的返回值
-
参数2:表示父亲,一般直接填NULL
-
参数3: 设备号类型 dev_t
dev_t devt#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) //获取主设备号#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) //获取次设备号#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) //生成设备号
-
参数4:私有数据,一般直接填NULL
-
参数5和6:表示可变参数,字符串,表示设备节点名字
销毁动作:
void device_destroy(devcls, MKDEV(dev_major, 0));
void class_destroy(devcls);
-
参数1: class结构体,class_create调用之后到返回值
-
参数2: 设备号类型 dev_t
-
参数1: class结构体,class_create调用之后到返回值
例—代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>/*
设备号给定方式有两种:1,动态--参数1直接填02,静态--指定一个整数,250
*/
static unsigned int dev_major = 250;
static unsigned int dev_minor = 0;
static struct class *devcls;
static struct device *dev;const struct file_operations my_fops = {};static int __init chr_dev_init(void)
{//装载一般都是申请设备号资源//申请设备号int ret;ret = register_chrdev(dev_major, "chr_dev_test", &my_fops);if(ret == 0){printk("register ok\n");}else{printk("register failed\n");return -EINVAL;}//自动创建设备节点devcls = class_create(THIS_MODULE, "chr_cls");dev = device_create(devcls,NULL,MKDEV(dev_major,dev_minor),NULL,"chr2");return 0;
}static void __exit chr_dev_exit(void)
{//卸载一般都是释放资源//销毁节点和类device_destroy(devcls,MKDEV(dev_major,dev_minor));class_destroy(devcls);//释放设备号unregister_chrdev(dev_major, "chr_dev_test");
}module_init(chr_dev_init);
module_exit(chr_dev_exit);
MODULE_LICENSE("GPL");
测试
在驱动中实现文件io的接口,应用程序可以调用文件io
驱动中实现文件io操作接口:struct file_operations
struct file_operations {struct module *owner;loff_t (*llseek) (struct file *, loff_t, int);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 (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);int (*iterate) (struct file *, struct dir_context *);unsigned int (*poll) (struct file *, struct poll_table_struct *);long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);long (*compat_ioctl) (struct file *, unsigned int, unsigned long);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 (*aio_fsync) (struct kiocb *, int datasync);int (*fasync) (int, struct file *, int);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 **);long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len);int (*show_fdinfo)(struct seq_file *m, struct file *f);}; //函数指针的集合,其实就是接口,我们写驱动到时候需要去实现const struct file_operations my_fops = {.open = chr_drv_open,.read = chr_drv_read,.write = chr_drv_write,.release = chr_drv_close,};
例—驱动代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>/*
设备号给定方式有两种:1,动态--参数1直接填02,静态--指定一个整数,250
*/
static unsigned int dev_major = 250;
static unsigned int dev_minor = 0;
static struct class *devcls;
static struct device *dev;ssize_t chr_drv_read(struct file *filep, char __user *buf, size_t count, loff_t *fpos)
{printk("---------%s-------------\n",__FUNCTION__);return 0;
}
ssize_t chr_drv_write(struct file *filep, const char __user *buf, size_t count, loff_t *fops)
{printk("---------%s-------------\n",__FUNCTION__);return 0;
}
int chr_drv_open(struct inode *inode, struct file *filep)
{printk("---------%s-------------\n",__FUNCTION__);return 0;
}
int chr_drv_close(struct inode *inode, struct file *filep)
{printk("---------%s-------------\n",__FUNCTION__);return 0;
}const struct file_operations my_fops = {.open = chr_drv_open,.read = chr_drv_read,.write = chr_drv_write,.release = chr_drv_close,
};static int __init chr_dev_init(void)
{//装载一般都是申请设备号资源//申请设备号int ret;ret = register_chrdev(dev_major, "chr_dev_test", &my_fops);if(ret == 0){printk("register ok\n");}else{printk("register failed\n");return -EINVAL;}//自动创建设备节点devcls = class_create(THIS_MODULE, "chr_cls");dev = device_create(devcls,NULL,MKDEV(dev_major,dev_minor),NULL,"chr2");return 0;
}static void __exit chr_dev_exit(void)
{//卸载一般都是释放资源//销毁节点和类device_destroy(devcls,MKDEV(dev_major,dev_minor));class_destroy(devcls);//释放设备号unregister_chrdev(dev_major, "chr_dev_test");
}module_init(chr_dev_init);
module_exit(chr_dev_exit);
MODULE_LICENSE("GPL");
应用程序如何去调用文件io去控制驱动–open,read,…
例—应用程序代码
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
//调用驱动
int fd;
int value = 0;
if((fd = open("/dev/chr2", O_RDWR)) < 0);
{perror("chr_test open");exit(1);
}read(fd,&value,4);
write(fd,&value,4);close(fd);return 0;
}
例—Makefile
ROOTFS_DIR = /nfs/rootfs#根文件系统路径APP_NAME = chr_test
CROSS_COMPILE = arm-none-linux-gnueabi-
CC = $(CROSS_COMPILE)gccifeq ($(KERNELRELEASE),)KERNEL_DIR = /home/linux/linux-3.14.79 #编译过的内核源码的路径
CPU_DIR = $(shell pwd) #当前路径all:make -C $(KERNEL_DIR) M=$(CPU_DIR) modules #把当前路径编成modules@#make -C 进入到内核路径@#M 指定当前路径$(CC) $(APP_NAME).c -o $(APP_NAME)clean:make -C $(KERNEL_DIR) M=$(CPU_DIR) cleanrm $(APP_NAME)install:sudo cp -raf *.ko $(APP_NAME) $(ROOTFS_DIR)/drv_module #把当前的所有.ko文件考到根文件系统的drv_module目录elseobj-m += chr_drv.o #指定内核要将哪个文件编译成koendif
实验效果—应用程序控制驱动
应用程序需要传递数据给驱动
//将数据从内核空间拷贝到用户空间,一般是在驱动中chr_drv_read()用
int copy_to_user(void __user * to, const void * from, unsigned long n)
参数1 to:to:目标地址(用户空间)应用驱动中的一个buffer
参数2 from:源地址(内核空间)
参数3 n :将要拷贝数据的字节数
返回:成功返回0,失败返回没有拷贝成功的数据字节数
//将数据从用户空间拷贝到内核空间,一般是在驱动中chr_drv_write()用
int copy_from_user(void * to, const void __user * from, unsigned long n)
参数1 to :目标地址(内核空间)
参数2 from:源地址(用户空间)
参数3 n:将要拷贝数据的字节数
返回:成功返回0,失败返回没有拷贝成功的数据字节数
控制外设,其实就是控制地址,内核驱动中是通过虚拟地址操作
//映射虚拟地址
void *ioremap(cookie, size)
参数1: 物理地址
参数2: 长度(连续映射一定长度的地址空间)
返回值: 虚拟地址
//去映射--解除映射
void iounmap(void __iomem *addr)
参数1: 映射之后到虚拟地址
示例—led闪烁
驱动代码 chr_drv.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/io.h>//物理地址
//#define GPX2_CON (*(volatile unsigned int *)0x11000C40) //此处是裸机驱动开发时的用法,此处不能用
#define GPX2_CON 0x11000C40
//GPX2_DATA就是GPX2_CON + 4
#define GPX2_SIZE 8//存放虚拟地址的指针
volatile unsigned long *gpx2_config;
volatile unsigned long *gpx2_data;/*
设备号给定方式有两种:1,动态--参数1直接填02,静态--指定一个整数,250
*/
static unsigned int dev_major = 250;
static unsigned int dev_minor = 0;
static struct class *devcls;
static struct device *dev;static int kernel_val = 555;ssize_t chr_drv_read(struct file *filep, char __user *buf, size_t count, loff_t *fpos)
{int ret;printk("---------%s-------------\n",__FUNCTION__);//从内核空间拷贝数据到用户空间ret = copy_to_user(buf, &kernel_val, count);if(ret > 0){printk("copy_to_user error\n");return -EFAULT; }return 0;
}
ssize_t chr_drv_write(struct file *filep, const char __user *buf, size_t count, loff_t *fops)
{int ret;int value;printk("---------%s-------------\n",__FUNCTION__);//从用户空间拷贝数据到内核空间ret = copy_from_user(&value, buf, count);if(ret > 0){printk("copy_from_user error\n");return -EFAULT; }//控制GPX2_7 I/O口电平变化if(value){*gpx2_data |= (0x01 << 7);}else{*gpx2_data &= ~(0x01 << 7);}return 0;
}
int chr_drv_open(struct inode *inode, struct file *filep)
{printk("---------%s-------------\n",__FUNCTION__);return 0;
}
int chr_drv_close(struct inode *inode, struct file *filep)
{printk("---------%s-------------\n",__FUNCTION__);return 0;
}const struct file_operations my_fops = {.open = chr_drv_open,.read = chr_drv_read,.write = chr_drv_write,.release = chr_drv_close,
};static int __init chr_dev_init(void)
{int ret;printk("---------%s-------------\n",__FUNCTION__);//装载一般都是申请设备号资源//申请设备号ret = register_chrdev(dev_major, "chr_dev_test", &my_fops);if(ret == 0){printk("register ok\n");}else{printk("register failed\n");return -EINVAL;}//自动创建设备节点devcls = class_create(THIS_MODULE, "chr_cls");dev = device_create(devcls,NULL,MKDEV(dev_major,dev_minor),NULL,"chr2");//地址映射gpx2_config = ioremap(GPX2_CON, GPX2_SIZE);gpx2_data = gpx2_config + 1;//配置GPIO的功能为输出*gpx2_config = (*gpx2_config & ~(0x0f << 28)) | (0x01 << 28);return 0;
}static void __exit chr_dev_exit(void)
{printk("---------%s-------------\n",__FUNCTION__);//卸载一般都是释放资源//去映射iounmap(gpx2_config);//销毁节点和类device_destroy(devcls,MKDEV(dev_major,dev_minor));class_destroy(devcls);//释放设备号unregister_chrdev(dev_major, "chr_dev_test");
}module_init
(chr_dev_init);
module_exit(chr_dev_exit);
MODULE_LICENSE("GPL");
应用程序 chr_test.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>int main(int argc, char *argv[])
{//调用驱动int fd;int value = 0;fd = open("/dev/chr2", O_RDWR);if(fd < 0){perror("chr_test open");exit(1);}read(fd,&value,4);printf("__USER__ : value = %d\n",value);while(1){value = 1;write(fd, &value, 4);sleep(1);value = 0;write(fd, &value, 4);sleep(1);}close(fd);return 0;
}
参考
https://blog.csdn.net/m0_37542524/article/details/86533273#233ioio_296