当前位置: 首页 > news >正文

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

字符设备驱动

〇、基本知识

设备驱动分类

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>   //字符设备头文件#define LED_MA 500  //主设备号 用于区分不同种类的设备  //某些主设备号已经静态地分配给了大部分公用设备。见Documentation/devices.txt。
#define LED_MI 0    //次设备号 用于区分同一类型的多个设备
#define LED_NUM 1   //有多少个设备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,
};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;}cdev_init(&cdev, & led_fops);//字符设备初始化ret = cdev_add(&cdev, devno, LED_NUM); //添加字符设备到系统中if (ret < 0) {printk("cdev_add\n");return ret;}printk("led init\n");return 0; //返回值  0:成功   负值:失败
}static void led_exit(void)
{dev_t devno = MKDEV(LED_MA, LED_MI);cdev_del(&cdev)unregister_chrdev_region(devno, LED_NUM);  //取消注册printk("led exit\n");
}module_init(led_init); //模块加载入口声明
module_exit(led_exit); //模块卸载入口声明
MODULE_LICENSE("GPL"); //模块免费开源声明

验证测试

$ make
$ aarch64-linux-gnu-gcc app.c  //编译应用程序,生成a.out
$ cp led.ko a.out /nfs/rootfs$ insmod led.ko
$ mknod /dev/led c 500 0  //创建设备文件,应用才能访问它. ( ls -l /dev 可以看到很多其它设备文件) 
$./a.out  //运行 成功可看到  open led ok  $ rmmod led.ko

三、实现定制文件操作

  • 幻数加密定义命令,防止不同驱动间命令错乱(内核与应用层间)
  • ioremap(内核与硬件间),不能直接操作硬件
  • goto语句,跳到对应err位置实现逆序释放

驱动部分

00_头文件

#include <asm/io.h>   //io操作的头文件(for  ioremap readl)

01_定制ioctrl操作命令部分

#define LED_MAGIC 'L'   //幻数:0~0xff的数。用于区分不同的驱动, 见Documentation/ioctl/ioctl-number.txt
#define LED_ON	_IOW(LED_MAGIC, 0, int)   //加幻数方式来定义命令,防止不同驱动间命令错乱
#define LED_OFF	_IOW(LED_MAGIC, 1, int)//ioctl 用于定制操作				
static long  led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{switch (cmd) {case LED_ON:led_on();break;case LED_OFF:led_off();break;default: //异常处理printk("no found this cmd =%d",cmd);return -1;}return 0;
}struct file_operations  led_fops = { //文件操作(一切皆文件).unlocked_ioctl =  led_ioctl,
};	

02_硬件控制部分

//电路连接
地线   //接 40pin 接口的40脚 gnd	
控制线 //接 40pin 接口的12脚   ->  管脚转换表 -> 电路图 -> 芯片手册 	#define GPIO3  	0x6000D200 // 第3个Bank GPIO 的基地址
#define CNF     0x04  //配置寄存器 (0:GPIO  1:SFIO)  偏移量
#define OE   	0x14  //输出使能寄存器 (1:使能 0:关闭)
#define OUT  	0x24  //输出寄存器(1:高电平 0:低电平)
#define MSK_CNF 0x84  //配置屏蔽寄存器(高位1:屏蔽 高位0:不屏蔽   低位1:GPIO模式 低位0:SFIO模式)
#define MSK_OE  0x94  //输出使能屏蔽寄存器(高位1:禁止写   低位1:使能)
#define MSK_OUT 0xA4  //输出屏蔽寄存器(高位1:禁止写   低位1:高电平)
#define PINMUX_AUX_DAP4_SCLK_0  0x70003150   //管脚复用设置unsigned char *gpio_base;
unsigned char *gpio_pinmux;//开灯
void led_on(void)
{writel(readl(gpio_base+OUT) | 1 << 7, gpio_base+OUT); //引脚输出高电平,点亮灯printk("out put high ,led on 输出高电平,点亮灯\n");
}//关灯
void led_off(void)
{writel(readl(gpio_base+OUT) & ~(1 << 7), gpio_base+OUT);  //引脚输出低电平,灭灯printk("out put low, led off 输出低电平,灭灯\n");
}static int led_init(void)
{//硬件初始化(成功可看到灯亮)//a.管脚复用的设置,设置做GPIO功能gpio_pinmux = ioremap(PINMUX_AUX_DAP4_SCLK_0, 8);  /*从物理地址PINMUX_AUX_DAP4_SCLK_0开始,映射8字节长度的空间到内核空间动态映射 物理地址 到内核虚拟地址phys_addr  起始物理地址size       映射范围大小,单位字节返回值      映射后的内核虚拟地址 */	if (gpio_pinmux == NULL) {printk("ioremap gpio_pinmux error\n");goto err3;}writel((readl(gpio_pinmux) & ~(1 << 4))|1, gpio_pinmux); /*管脚复用配置用于 GPIO1:0 I2S4B    PM: 0 = I2S4B 1 = RSVD1 2 = RSVD2  3 = RSVD3 设为非0,表示不用作I2S功能,则默认用做GPIO功能4 TRISTATE TRISTATE:   0 = PASSTHROUGH  1 = TRISTATE设为0,设为直通状态才能驱动外面的设备见 9.5.1 Per Pad OptionsTristate     高阻态 -> 与外界是断开的,默认启动设为高阻太,避免驱动影响外面的设备             passthrough  直通态 -> 才能驱动外面设备   *///b. 做GPIO功能时的内部配置gpio_base = ioremap(GPIO3, 0xFF); if (gpio_base == NULL) {printk("ioremap gpio_base error\n");goto err2;}writel(readl(gpio_base+CNF) | 1 << 7, gpio_base+CNF);   //配置引脚GPIO3_PJ.07 为 GPIO模式writel(readl(gpio_base+OE) | 1 << 7, gpio_base+OE);	  //使能引脚(7号)writel(readl(gpio_base+OUT) | 1 << 7, gpio_base+OUT); //输出高电平,点亮灯writel(readl(gpio_base+MSK_CNF) | 1 << 7, gpio_base+MSK_CNF); //取消对GPIO模下引脚的屏蔽writel(readl(gpio_base+MSK_OE) | 1 << 7, gpio_base+MSK_OE);  //取消引脚 使能屏蔽}

03_顺序申请,逆序释放

static int led_init(void)
{ret = cdev_add(&cdev, devno, LED_NUM);if (ret < 0) {printk("cdev_add\n");goto err1;}gpio_base = ioremap(GPIO3, 0xFF); if (gpio_base == NULL) {printk("ioremap gpio_base error\n");goto err2;}gpio_pinmux = ioremap(PINMUX_AUX_DAP4_SCLK_0, 8);if (gpio_pinmux == NULL) {printk("ioremap gpio_pinmux error\n");goto err3;}err3: //跳过来后就顺序执行下面的顺序释放iounmap(gpio_base);
err2:cdev_del(&cdev);
err1: //报错就释放上一步做完的unregister_chrdev_region(devno, LED_NUM); return ret;
}

应用部分

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>#define LED_MAGIC 'L'  //幻数,一般一个驱动一个幻数,和驱动部分幻数一致//用幻数加密后避免程序误操作,有写错的时候有安全问题#define LED_ON	_IOW(LED_MAGIC, 0, int)   //用幻数加密控制命令
#define LED_OFF	_IOW(LED_MAGIC, 1, int)int main(int argc, char **argv)
{int fd;fd = open("/dev/led", O_RDWR);  //打开设备文件if (fd < 0) {perror("open");exit(1);}while(1){ioctl(fd, LED_ON);  //发送控制命令 LED_ONusleep(100000);ioctl(fd, LED_OFF); //发送控制命令 LED_OFFusleep(100000);}return 0;
}struct file_operations  led_fops = { //文件操作(一切皆文件).owner = THIS_MODULE,.open =  led_open,.release =  led_release,.unlocked_ioctl =  led_ioctl,
};

总程序

#include <linux/kernel.h>
#include <linux/module.h>  //模块的头文件 (for module_init MODULE_LICENSE)
#include <linux/fs.h>	  //for MKDEV register_chrdev_region
#include <linux/cdev.h>  //字符设备头文件#include <asm/io.h>   //io操作的头文件(for  ioremap readl)#define LED_MA 500  //主设备号 用于区分不同种类的设备  //某些主设备号已经静态地分配给了大部分公用设备。见Documentation/devices.txt 。
#define LED_MI 0   //次设备号 用于区分同一类型的多个设备
#define LED_NUM 1  //有多少个设备struct cdev cdev; //定义字符设备#define LED_MAGIC 'L'   //幻数:0~0xff的数。用于区分不同的驱动, 见Documentation/ioctl/ioctl-number.txt
#define LED_ON	_IOW(LED_MAGIC, 0, int)   //加幻数方式来定义命令,防止不同驱动间命令错乱
#define LED_OFF	_IOW(LED_MAGIC, 1, int)#define GPIO3  	0x6000D200 //第3个Bank GPIO 的基地址 (GPIO3_PJ.07)
#define CNF	0x04  //配置寄存器 (0:GPIO  1:SFIO)  偏移量
#define OE   	0x14  //输出使能寄存器 (1:使能 0:关闭)
#define OUT  	0x24  //输出寄存器(1:高电平 0:低电平)
#define MSK_CNF 0x84  //配置屏蔽寄存器(高位1:屏蔽 高位0:不屏蔽   低位1:GPIO模式 低位0:SFIO模式)
#define MSK_OE   0x94  //输出使能屏蔽寄存器(高位1:禁止写   低位1:使能)
#define MSK_OUT 0xA4  //输出屏蔽寄存器(高位1:禁止写   低位1:高电平)
#define  PINMUX_AUX_DAP4_SCLK_0  0x70003150   //管脚复用设置unsigned char *gpio_base;
unsigned char *gpio_pinmux;//查看相关寄存器的内容->方便查BUG
void show_reg(void)
{printk(" cnf =%x\n",readl(gpio_base+CNF)); //通过基地址加偏移量,来访问对应的配置寄存器printk(" oe =%x\n",readl(gpio_base+OE));printk(" out =%x\n",readl(gpio_base+OUT));printk("mask cnf =%x\n",readl(gpio_base+MSK_CNF));printk("mask oe =%x\n",readl(gpio_base+MSK_OE));printk("mask out =%x\n",readl(gpio_base+MSK_OUT));printk("gpio_pinmux  =%x\n",readl(gpio_pinmux));
}static int  led_open(struct inode *inode, struct file *file)
{printk("driver led  open ok\n");show_reg();return 0;
}static int  led_release(struct inode *inode, struct file *file)
{printk("driver led  close ok\n");show_reg();return 0;
}//开灯
void led_on(void)
{writel(readl(gpio_base+OUT) | 1 << 7, gpio_base+OUT); //引脚输出高电平,点亮灯printk("out put high ,led on 输出高电平,点亮灯\n");
}//关灯
void led_off(void)
{writel(readl(gpio_base+OUT) & ~(1 << 7), gpio_base+OUT);  //引脚输出低电平,灭灯printk("out put low, led off 输出低电平,灭灯\n");
}//ioctl 用于定制操作				
static long  led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{switch (cmd) {case LED_ON:led_on();break;case LED_OFF:led_off();break;default: //异常处理printk("no found this cmd =%d",cmd);return -1;}return 0;
}//3.实现需要的文件操作
// file_operations 中 定义了针对文件的一系列操作方法   不是每个都需实现	
struct file_operations  led_fops = { //文件操作(一切皆文件).owner = THIS_MODULE,.open =  led_open,.release =  led_release,.unlocked_ioctl =  led_ioctl,
};static int led_init(void)
{dev_t devno = MKDEV(LED_MA, LED_MI); int ret;//1.注册设备号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;}//2.初始化字符设备cdev_init(&cdev, & led_fops);//字符设备初始化ret = cdev_add(&cdev, devno, LED_NUM); //添加字符设备到系统中if (ret < 0) {printk("cdev_add\n");goto err1;}//硬件初始化(成功可看到灯亮)//a.管脚复用的设置,设置做GPIO功能gpio_pinmux = ioremap(PINMUX_AUX_DAP4_SCLK_0, 8);  /*从物理地址PINMUX_AUX_DAP4_SCLK_0开始,映射 8字节长度的空间到内核空间动态映射 物理地址 到内核虚拟地址phys_addr  起始物理地址size       映射范围大小,单位字节返回值     映射后的内核虚拟地址 */	if (gpio_pinmux == NULL) {printk("ioremap gpio_pinmux error\n");goto err3;}writel((readl(gpio_pinmux) & ~(1 << 4))|1, gpio_pinmux); /*管脚复用配置用于 GPIO1:0 I2S4B    PM: 0 = I2S4B 1 = RSVD1 2 = RSVD2  3 = RSVD3 设为非0,表示不用作I2S功能,则默认用做GPIO功能4 TRISTATE TRISTATE:   0 = PASSTHROUGH  1 = TRISTATE设为0,设为直通状态才能驱动外面的设备见 9.5.1 Per Pad OptionsTristate     高阻态 -> 与外界是断开的,默认启动设为高阻太,避免驱动影响外面的设备             passthrough  直通态 -> 才能驱动外面设备   *///b. 做GPIO功能时的内部配置gpio_base = ioremap(GPIO3, 0xFF); if (gpio_base == NULL) {printk("ioremap gpio_base error\n");goto err2;}writel(readl(gpio_base+CNF) | 1 << 7, gpio_base+CNF);   //配置引脚GPIO3_PJ.07 为 GPIO模式writel(readl(gpio_base+OE) | 1 << 7, gpio_base+OE);	//使能引脚(7号)writel(readl(gpio_base+OUT) | 1 << 7, gpio_base+OUT); //输出高电平,点亮灯writel(readl(gpio_base+MSK_CNF) | 1 << 7, gpio_base+MSK_CNF); //取消对GPIO模下引脚的屏蔽writel(readl(gpio_base+MSK_OE) | 1 << 7, gpio_base+MSK_OE);  //取消引脚 使能屏蔽printk("led init ok\n");return 0; //返回值  0:成功   负值:失败//goto 出错处理, 顺序申请,逆序释放,避免资源回收不完全(如内存泄露)
err3:iounmap(gpio_base);
err2:cdev_del(&cdev);
err1:unregister_chrdev_region(devno, LED_NUM);return ret;
}static void led_exit(void)
{//要配对释放资源,逆序释放资源dev_t devno = MKDEV(LED_MA, LED_MI);iounmap(gpio_base); //取消映射 iounmap(gpio_pinmux); cdev_del(&cdev);  //从系统中移除该设备unregister_chrdev_region(devno, LED_NUM);  //取消注册printk("led exit ok\n");
}module_init(led_init); //模块加载入口声明
module_exit(led_exit); //模块卸载入口声明
MODULE_LICENSE("GPL"); //模块免费开源声明

验证测试

$ make
$ aarch64-linux-gnu-gcc app.c  //编译应用程序,生成a.out
$ cp led.ko a.out /nfs/rootfs
# insmod led.ko
# mknod /dev/led c 500 0  //创建设备文件
#./a.out  //运行 成功可看到  灯闪烁

四、实现读写文件操作

  • 应用空间的buf不能直接拷贝到内核空间,采用copy_from_user
  • 错误码,数据长度等问题

驱动部分

//led.c
#include <asm/uaccess.h>  //for read write#define C_BUF_LEN 64
char c_buf[C_BUF_LEN];//返回值  正数:成功写入的字节数  负值:错误码  0:无数据成功写入
static ssize_t led_write (struct file *file, const char __user *buf, //file: 文件指针  buf:用户空间的缓冲区size_t count, loff_t *  f_pos)  //count: 数据长度 f_pos: 文件位置
{ssize_t ret = 0;  printk ("Writing %ld bytes\n", count); if (count > C_BUF_LEN -1) return -ENOMEM; if (count<0) return -EINVAL; /*应用空间的buf不能直接拷贝到内核空间while(count--){*c_buf++ = buf++}*/  if (copy_from_user (c_buf, buf, count)) {	 /*从用户空间拷贝数据到内核空间unsigned long copy_from_user(void * to, const void __user * from, unsigned long n) to:内核空间的目标缓冲区from: 应用空间源缓冲区n:  拷贝的长度   返回值  0: 成功   正数:没有拷贝成功的字节数*/ret = -EFAULT;  } else {  c_buf[63]='\0';    printk ("Received: %s\n", c_buf);   ret = count;   }  return ret;
}static ssize_t  led_read(struct file *file, char *buff, size_t count, loff_t *offp)
{ssize_t result = 0; if(count > C_BUF_LEN -1 )  count = C_BUF_LEN -1; if(count < 0) 	  return -EINVAL;  if (copy_to_user(buff,c_buf, count))       result = -EFAULT; else    printk ("read %ld bytes\n", count);  result = count;  return result;
}struct file_operations  led_fops  ={.write = led_write,.read = led_read,
};

应用部分

//app.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <string.h>int main(int argc, char **argv)
{int fd;char buff[]=" let's go ";fd = open("/dev/led", O_RDWR);if (fd < 0) {perror("open");exit(1);}write (fd, buff, sizeof(buff));memset(buff,'\0',sizeof(buff));read (fd, buff, sizeof(buff) - 1);printf("read buf is %s\n",buff);return 0;
}

验证测试

$ make
$ aarch64-linux-gnu-gcc app.c
$ cp a.out led.ko /nfs/rootfs	
# setenv bootargs  root=/dev/nfs rw nfsroot=192.168.9.119:/nfs/rootfs,v3 console=ttyS0,115200 init=/linuxrc ip=192.168.9.9
# setenv nfsboot ext4load mmc 1:1 0x84000000 /boot/Image \; ext4load mmc 1:1 83100000 /boot/tegra210-p3448-0002-p3449-0000-b00.dtb \; booti 0x84000000 - 83100000
# run nfsboot //成功 可看到 read buf is  let's go ,即读出的数据和写入的一致

相关文章:

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

字符设备驱动 〇、基本知识 设备驱动分类 &#xff08;按共性分类方便管理&#xff09; 1.字符设备驱动 字符设备指那些必须按字节流传输&#xff0c;以串行顺序依次进行访问的设备。它们是我们日常最常见的驱动了&#xff0c;像鼠标、键盘、打印机、触摸屏&#xff0c;还有…...

【零散整理】

1-1 git查看代码的项目总行数 git log --prettytformat: --numstat | awk ‘{ add $1; subs $2; loc $1 - $2 } END { printf “added lines: %s, removed lines: %s, total lines: %s\n”, add, subs, loc }’ - 1-2 cookie const cookies document.cookie.split(; )for…...

RocketMQ重复消费的症状以及解决方案

RocketMQ重复消费的症状以及解决方案 生产消息时重复 症状 当一条消息已被成功发送到 消费者 并完成持久化&#xff0c;此时出现了网络闪断或者客户端宕机&#xff0c;导致服务端对客户端应答失败。 如果此时 生产者 意识到消息发送失败并尝试再次发送消息&#xff0c;消费者…...

数字化时代,企业的商业模式建设

随着新一代信息化、数字化技术的应用&#xff0c;众多领域通过科技革命和产业革命实现了深度化的数字改造&#xff0c;进入到以数据为核心驱动力的&#xff0c;全新的数据处理时代&#xff0c;并通过业务系统、商业智能BI等数字化技术和应用实现了数据价值&#xff0c;从数字经…...

项目实战典型案例23——-注册上nacos上的部分服务总是出现频繁掉线的情况

注册上nacos上的部分服务总是出现频繁掉线的情况一&#xff1a;背景介绍二&#xff1a;思路&方案解决问题过程涉及到的知识nacos服务注册和服务发现一&#xff1a;背景介绍 spring cloud项目通过nacos作为服务中心和配置中心&#xff0c;出现的问题是其中几个服务总是出现…...

玩转金山文档 3分钟让你的文档智能化

在上个月底&#xff0c;我们给大家推荐了金山轻维表的几个使用场景&#xff0c;社群中不少用户反响很好&#xff0c;对其中一些场景的解决方案十分感兴趣。但也有一些人表示&#xff0c;有些场景不知道如何实现&#xff0c;希望我们能提供模版/教程。这次我们将做一期热门模板盘…...

安装了nodejs怎么安装nvm

第一步&#xff0c;从控制面板卸载已经安装的node 第二步&#xff0c;删除C盘program开头文件夹下的node文件 第三步&#xff0c;去C/user/用户名 文件夹下&#xff0c;删除.npmrc文件 第四步&#xff0c;打开隐藏文件&#xff0c;第三步文件夹下有一个Appdata文件&#xff…...

java安全编码规范考试

java安全编码规范考试 整理不易&#xff0c;收点币&#xff01;&#xff01; 安全编码规范考试.md 下面对zip文件的安全解压缩描述&#xff0c;错误的是 A.zip文件解压时&#xff0c;可以使用entry.getSize(&#xff09;对解压缩文件进行文件大小判断 B.zip文件解压时&…...

表格检测识别技术的发展历程

近年来&#xff0c;随着计算机技术的飞速发展&#xff0c;越来越多的研究者开始关注表格检测识别技术。表格检测识别技术是一种利用计算机自动处理表格的技术&#xff0c;它可以实现从文本中检测出表格&#xff0c;并进行识别和提取。这种技术有助于提高文本处理的效率&#xf…...

设计UI - Adobe xd对象介绍

矩形工具 新建矩形 操作步骤&#xff1a;选择矩形工具&#xff0c;快捷键R&#xff0c;鼠标在画板上拖出矩形即可。 拖动定界框周围圆形手柄&#xff0c;可快速调整矩形大小&#xff0c;也可以输入宽和高的参数对矩形大小进行改变。 移动矩形 操作步骤&#xff1a;选择选择工具…...

优思学院|精益生产中的“单件流”真的能够做到吗?

精益生产中提到的“一个流”&#xff08;One Piece Flow&#xff09;是一种生产方式&#xff0c;它的核心理念是通过合理配置作业场地、人员和设备&#xff0c;使产品从投入到成品产出的整个制造加工过程中始终处于不停滞、不堆积、不超越&#xff0c;按节拍一个一个地流动。 …...

移除元素问题解决方法------LeetCode-OJ题

问题&#xff1a; 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 要求&#xff1a; 不要使用额外的数组空间&#xff0c;你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 元素的顺序可以改…...

JavaScript学习笔记(1.0)

push() 语法&#xff1a;数组.push(数据) 作用&#xff1a;将数据追加到数组的末尾 返回值&#xff1a;追加数据后数组最新的长度 pop() 语法&#xff1a;数组.pop() 作用&#xff1a;删除数组最后一个数据 返回值&#xff1a;被删除的数据 unshift() 语法&#xff1a;数…...

FCN网络介绍

目录前言一.FCN网络二.网络创新点前言 在图像分割领域&#xff0c;有很多经典的网络&#xff0c;如MASK R-CNN&#xff0c;U-Net&#xff0c;SegNet&#xff0c;DeepLab等网络都是以FCN为基础进行设计的。我们这里简单介绍一下这个网络。 一.FCN网络 FCN网络介绍   FCN 即全…...

Idea+maven+spring-cloud项目搭建系列--11 整合dubbo

前言&#xff1a; 微服务之间通信框架dubbo&#xff0c;使用netty &#xff08;NIO 模型&#xff09;完成RPC 接口调用&#xff1b; 1 dubbo 介绍&#xff1a; Apache Dubbo 是一款 RPC 服务开发框架&#xff0c;用于解决微服务架构下的服务治理与通信问题&#xff0c;官方提…...

2023年上半年北京杭州/广州深圳软考中/高级报名入口

软考是全国计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试&#xff08;简称软考&#xff09;项目&#xff0c;是由国家人力资源和社会保障部、工业和信息化部共同组织的国家级考试&#xff0c;既属于国家职业资格考试&#xff0c;又是职称资格考试。 系统集成…...

jupyter notebook配置和使用

简介 Jupyter Notebook是基于网页的用于交互计算的应用程序。其可被应用于全过程计算&#xff1a;开发、文档编写、运行代码和展示结果。 参考博客&#xff1a;https://zhuanlan.zhihu.com/p/33105153 特点 ①编程时具有语法高亮、缩进、tab补全的功能。 ② 可直接通过浏览器…...

【C++】通过stack、queue、deque理解适配器模式

破镜不能重圆&#xff0c;枯木可以逢春。 文章目录一、stack1.stack的介绍2.stack相关OJ题&#xff08;巧妙利用stack数据结构的特征&#xff09;3.stack的模拟实现二、queue1.queue的介绍2.queue的相关OJ题&#xff08;巧妙利用queue数据结构的特征&#xff09;3.queue的模拟实…...

JavaScript 高级实例集合

文章目录JavaScript 高级实例集合创建一个欢迎 cookie简单的计时另一个简单的计时在一个无穷循环中的计时事件带有停止按钮的无穷循环中的计时事件使用计时事件制作的钟表创建对象的实例创建用于对象的模板JavaScript 高级实例集合 创建一个欢迎 cookie 源码 <!DOCTYPE ht…...

Flutter(五)容器类组件

布局类组件包含多个子组件&#xff0c;而容器类组件只包含一个子组件 目录填充&#xff08;Padding&#xff09;装饰容器&#xff08;DecoratedBox&#xff09;变换&#xff08;Transform&#xff09;Transform.translate 平移Transform.rotate 旋转Transform.scale 缩放Rotate…...

【Linux】shell脚本忽略错误继续执行

在 shell 脚本中&#xff0c;可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行&#xff0c;可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令&#xff0c;并忽略错误 rm somefile…...

从WWDC看苹果产品发展的规律

WWDC 是苹果公司一年一度面向全球开发者的盛会&#xff0c;其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具&#xff0c;对过去十年 WWDC 主题演讲内容进行了系统化分析&#xff0c;形成了这份…...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解

【关注我&#xff0c;后续持续新增专题博文&#xff0c;谢谢&#xff01;&#xff01;&#xff01;】 上一篇我们讲了&#xff1a; 这一篇我们开始讲&#xff1a; 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下&#xff1a; 一、场景操作步骤 操作步…...

(二)TensorRT-LLM | 模型导出(v0.20.0rc3)

0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述&#xff0c;后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作&#xff0c;其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...

android13 app的触摸问题定位分析流程

一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...

Monorepo架构: Nx Cloud 扩展能力与缓存加速

借助 Nx Cloud 实现项目协同与加速构建 1 &#xff09; 缓存工作原理分析 在了解了本地缓存和远程缓存之后&#xff0c;我们来探究缓存是如何工作的。以计算文件的哈希串为例&#xff0c;若后续运行任务时文件哈希串未变&#xff0c;系统会直接使用对应的输出和制品文件。 2 …...

【HarmonyOS 5】鸿蒙中Stage模型与FA模型详解

一、前言 在HarmonyOS 5的应用开发模型中&#xff0c;featureAbility是旧版FA模型&#xff08;Feature Ability&#xff09;的用法&#xff0c;Stage模型已采用全新的应用架构&#xff0c;推荐使用组件化的上下文获取方式&#xff0c;而非依赖featureAbility。 FA大概是API7之…...

SQL进阶之旅 Day 22:批处理与游标优化

【SQL进阶之旅 Day 22】批处理与游标优化 文章简述&#xff08;300字左右&#xff09; 在数据库开发中&#xff0c;面对大量数据的处理任务时&#xff0c;单条SQL语句往往无法满足性能需求。本篇文章聚焦“批处理与游标优化”&#xff0c;深入探讨如何通过批量操作和游标技术提…...

C++11 constexpr和字面类型:从入门到精通

文章目录 引言一、constexpr的基本概念与使用1.1 constexpr的定义与作用1.2 constexpr变量1.3 constexpr函数1.4 constexpr在类构造函数中的应用1.5 constexpr的优势 二、字面类型的基本概念与使用2.1 字面类型的定义与作用2.2 字面类型的应用场景2.2.1 常量定义2.2.2 模板参数…...

HTML中各种标签的作用

一、HTML文件主要标签结构及说明 1. <&#xff01;DOCTYPE html> 作用&#xff1a;声明文档类型&#xff0c;告知浏览器这是 HTML5 文档。 必须&#xff1a;是。 2. <html lang“zh”>. </html> 作用&#xff1a;包裹整个网页内容&#xff0c;lang"z…...