【嵌入式Linux内核驱动】02_字符设备驱动

本文详细介绍了Linux内核中字符设备驱动的开发过程,包括注册设备号、初始化字符设备、实现定制文件操作(如ioctl命令)以及读写文件操作。讲解了如何通过ioremap进行硬件映射,以及如何处理设备的打开、关闭、控制命令和数据传输。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

字符设备驱动

〇、基本知识

设备驱动分类

image-20220930092437853
(按共性分类方便管理)

1.字符设备驱动
字符设备指那些必须按字节流传输,以串行顺序依次进行访问的设备。它们是我们日常最常见的驱动了,像鼠标、键盘、打印机、触摸屏,还有点灯以及I2C、SPI、音视频都属于字符设备驱动。

字符设备不经过系统快速缓冲。

2.块设备驱动
就是存储器设备的驱动,比如 EMMC、NAND、SD 卡和 U 盘等存储设备,因为这些存储设备的特点是以存储块为基础,按块随机访问,可以用任意顺序进行访问,以块为单位进行操作,因此叫做块设备。数据的读写只能以块(通常是512B)的倍数 进行。与字符设备不同,块设备并不支持基于字符的寻址。

块设备经过设备缓冲

3.网络设备驱动
就是网络驱动,不管是有线的还是无线的,都属于网络设备驱动的范畴。按TCP/IP协议栈传输

网络设备面向数据包的接受和发送而设计,它并不对应文件系统的节点

注意:
块设备和网络设备驱动要比字符设备驱动复杂,就是因为其复杂所以半导体厂商一般都编写好了,大多数情况下都是直接可以使用的。

一个设备可以属于多种设备驱动类型,比如USB WIFI,其使用 USB 接口,属于字符设备,但是其又能上网,所以也属于网络设备驱动。

设备驱动框架

为了安全

image-20220930093630161

一切皆文件

为了标准化操作函数,方便对接工作

open read write close

字符设备框架

字符设备驱动编写三部曲

  1. 注册设备号
  2. 初始化字符设备
  3. 实现需要的文件操作

一、注册设备号

为了让内核知道这个设备是合法的,将构造的设备号注册到内核中,表明该设备号已经被占用,如果有其他驱动随后要注册该设备号,将会失败。

  • 主次设备号
  • MKDEV
  • register_chrdev_region

驱动部分

00_头文件
#include <linux/fs.h>	  //for MKDEV register_chrdev_region
01_主次设备号
#define LED_MA 500  //主设备号 用于区分不同种类的设备  
                    //某些主设备号已经静态地分配给了大部分公用设备。见Documentation/devices.txt 。 
#define LED_MI 0   //次设备号 用于区分同一类型的多个设备
#define LED_NUM 1  //有多少个设备
02_注册字符设备号
	dev_t devno = MKDEV(LED_MA, LED_MI); 
	int ret;

	ret = register_chrdev_region(devno, LED_NUM, "yhai_led");   /*注册字符设备号(静态分配),为了让内核认可
	   为一个字符驱动获取一个或多个设备编号
	   dev_id:       分配的起始设备编号(常常是0)
	   DEVICE_NUM:  请求的连续设备编号的总数(不能太大,避免别的主设备号冲突)
	   DEVICE_NAME: 是应当连接到这个编号范围的设备的名字 
	   alloc_chrdev_region  可进行动态分配                                           
                                                    */
	if (ret < 0) {
   
		printk("register_chrdev_region\n");
		return ret;
	}
03_取消注册
	dev_t devno = MKDEV(LED_MA, LED_MI);
	unregister_chrdev_region(devno, LED_NUM);  //取消注册

总程序

//led.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>	  //for MKDEV register_chrdev_region

#define LED_MA 500  //主设备号 用于区分不同种类的设备  
                    //某些主设备号已经静态地分配给了大部分公用设备。见Documentation/devices.txt 。 
#define LED_MI 0   //次设备号 用于区分同一类型的多个设备
#define LED_NUM 1  //有多少个设备


static int led_init(void)
{
   
	dev_t devno = MKDEV(LED_MA, LED_MI); 
	int ret;

	ret = register_chrdev_region(devno, LED_NUM, "yhai_led");   /*注册字符设备号(静态分配)
	   为一个字符驱动获取一个或多个设备编号
	   dev_id:       分配的起始设备编号(常常是0)
	   DEVICE_NUM:  请求的连续设备编号的总数(不能太大,避免别的主设备号冲突)
	   DEVICE_NAME: 是应当连接到这个编号范围的设备的名字 
	   alloc_chrdev_region  可进行动态分配                                           
                                                    */
	if (ret < 0) {
   
		printk("register_chrdev_region\n");
		return ret;
	}

	printk("led init\n");

	return 0; //返回值  0:成功   负值:失败
}

static void led_exit(void)
{
   
	dev_t devno = MKDEV(LED_MA, LED_MI);
	unregister_chrdev_region(devno, LED_NUM);  //取消注册
	printk("led exit\n");
}

module_init(led_init); //模块加载入口声明
module_exit(led_exit); //模块卸载入口声明
MODULE_LICENSE("GPL"); //模块免费开源声明

验证测试

# insmod led.ko     /*加载模块 
# rmmod  led         //卸载模块 

二、初始化字符设备

连接设备号对应的操作

  • file_operations
  • cdev_init 连接设备号对应的操作
  • cdev_add 添加到散列表,里面放着一堆字符设备。应用层open时根据设备号在散列表中找到设备,open返回的fd找到对应file结构,然后调用相应操作

驱动部分

00_头文件
#include <linux/cdev.h>  //字符设备头文件
01_字符设备初始化

struct file_operations led_fops 这部分全是函数指针

struct cdev cdev; //定义字符设备

static int  led_open(struct inode *inode, struct file *file)
{
   
  printk("driver led  open\n");
  return 0;
}
	
static int  led_release(struct inode *inode, struct file *file)
{
   
  printk("driver led  close\n");
  return 0;
}

struct file_operations  led_fops = {
    //文件操作(一切皆文件)
	.owner = THIS_MODULE,
	.open =  led_open,
	.release =  led_release,
};

	cdev_init(&cdev, & led_fops);//字符设备初始化
	ret = cdev_add(&cdev, devno, LED_NUM); //添加字符设备到系统中
	if (ret < 0) {
   
		printk("cdev_add\n");
		return ret;
	}
02_字符设备删除

这个删完,再取消注册,相当于把空间中的内容都清掉,再把空间释放

	cdev_del(&cdev)

应用部分

交叉编译aarch64-linux-gnu-gcc app.c

//app.c  
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>

int main(int argc, char **argv)
{
   
	int fd;

	fd = open("/dev/led", O_RDWR);
	if (fd < 0) {
   
		perror("open");
		exit(1);
	}
	printf("open led ok\n");  //注意要加\n 否则打印信息可能没有

	return 0;
}

总程序

//led.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>	  //for MKDEV register_chrdev_region
#include <linux/cdev.h>   //字符设备头文件

#de
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值