当前位置: 首页 > 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…...

实现满屏品字布局

html, body {width: 100%;height: 100%;}.first {width: 50%;height: 50%;margin: auto;background-color: pink;}.second {width: 50%;height: 50%;float: left;background-color: greenyellow;}.third {width: 50%;height: 50%;float: left;background-color: yellow;}...

软件测试-性能测试-基础知识

文章目录 1.性能测试理论1.1 相关概念1.2 性能测试指标2.性能测试策略2.1 基准测试2.2 负载测试2.3 稳定性测试2.4 其他测试策略3.性能测试的流程3.1 需求分析3.2 编写性能测试计划和方案3.3 编写性能测试用例3.4 性能测试执行3.5 性能测试报告4.性能测试工具4.1 Loadrunner4.2…...

java多线程与线程池-02线程池与锁

线程池与锁 第4章 线程池入门 4.1 ThreadPoolExecutor ThreadPoolExecutor是应用最广的底层线程池类,它实现了Executor和ExecutorService接口。 4.1.1 创建线程池 下面创建一个线程池,通过调整线程池构造函数的参数来了解线程池的运行特性。把核心线程数设置为3,最大…...

AB测试——流程介绍(设计实验)

前言&#xff1a; 作为AB测试的学习记录&#xff0c;接上文内容&#xff0c; 本文继续介绍假设建立和实验设计部分&#xff0c;包括实验对象、样本量计算&#xff08;显著性水平、统计功效及最小可检测效应&#xff09;、实验周期。 相关文章&#xff1a; AB测试——原理介绍 A…...

C++中的智能指针有哪些?分别解决的问题以及区别?

1.C中的智能指针有4种&#xff0c;分别为&#xff1a;shared_ptr、unique_ptr、weak_ptr、auto_ptr&#xff0c;其中auto_ptr被C11弃用。 2.使用智能指针的原因 申请的空间&#xff08;即new出来的空间&#xff09;&#xff0c;在使用结束时&#xff0c;需要delete掉&#xff0…...

通达信捉妖改良CCI指标公式,简洁巧妙

高端的食材&#xff0c;往往只需要简单的烹饪方式。好的指标也是一样&#xff0c;只需要简单处理&#xff0c;就可以实现不错的效果。捉妖改良CCI指标公式属于意外之喜&#xff0c;编写指标时写错了&#xff0c;研究后发现结果比原想法更好。 捉妖改良CCI指标公式利用了CCI&am…...

「Python 基础」面向对象编程

文章目录1. 面向对象编程类和实例访问限制继承和多态type()isinstance()dir()实例属性和类属性2. 面向对象高级编程\_\_slots\_\_property多重继承定制类枚举类元类1. 面向对象编程 Object Oriented Programming 简称 OOP&#xff0c;一种程序设计思想&#xff0c;以对象为程…...

【K3s】第23篇 一篇文章带你学习k3s私有镜像仓库配置

目录 1、私有镜像仓库配置 2、registries.yaml Mirrors Configs 1、私有镜像仓库配置 可以配置 Containerd 连接到私有镜像仓库,并使用它们在节点上拉取私有镜像。 启动时,K3s 会检查/etc/rancher/k3s/中是否存在registries.yaml文件,并指示 containerd 使...

Redis学习【12】之Redis 缓存

文章目录前言一 Jedis 简介二 使用 Jedis2.1 测试代码2.2 使用 JedisPool2.3 使用 JedisPooled2.4 连接 Sentinel 高可用集群2.5 连接分布式系统2.6 操作事务三 Spring Boot整合Redis3.1 创建工程3.2 定义 pom 文件3.3 完整代码3.4 总结四 高并发问题4.1 缓存穿透4.2 缓存击穿4…...

Bootargs 参数

bootargs 的参数有很多&#xff0c;而且随着 kernel 的发展会出现一些新的参数&#xff0c;使得设置会更加灵活多样1。除了我之前介绍的 root、console、earlyprintk 和 loglevel 之外&#xff0c;还有以下一些常用的参数&#xff1a;init: 用来指定内核启动后执行的第一个程序…...

wordpress 打开慢/网站怎么注册

给大家分享几个对于长篇文档排版过程中所能用得上的几个快捷键小技巧&#xff0c;相信这些不起眼的技巧或许在关键时刻能够帮你解决很多重要的问题。快速定位到文档开头和结尾如果在Word文档中内容过多&#xff0c;此时&#xff0c;你想直接翻到文档结尾看看&#xff0c;那么我…...

做一手房用什么网站好/交换友情链接的途径有哪些

php mysql获取表字段名称和字段信息的三种方法先给出本实例中使用的表的信息&#xff1a;使用desc获取表字段信息php代码如下&#xff1a;mysql_connect("localhost","root","");mysql_select_db("test");$query "desc student&…...

超市如何建立网站/在哪里做推广效果好

Kube-proxy需要在每一个minion结点上运行。他的作用是service的代理&#xff0c;负责将业务连接到service后面具体执行结点&#xff08;endpoints&#xff09;。 我们列一下体现kube-proxy主要设计的代码部分。 总的来说kubernetes的代码可以从cmd看进去&#xff0c;看每个组件…...

用vs2013做网站登录/数据分析师就业前景

数据绘图要点11-雷达图的注意事项 准确通过雷达图表现数据有许多值得需要思考的地方&#xff0c;本文主要介绍雷达图的一些注意事项。 文章目录数据绘图要点11-雷达图的注意事项雷达图绘制基础雷达图多类别雷达图雷达图存在的问题与解决方案雷达图存在的问题解决方案参考雷达图…...

访问网站的过程/需要优化的网站有哪些

31.Elasticsearch批量导入本地Json文件Java实现(ES文件同步)发布时间&#xff1a;2018-04-30 14:08,浏览次数&#xff1a;769, 标签&#xff1a;ElasticsearchJsonJavaES题记产品开发需要&#xff0c;我们需要将互联网采集的数据存储到ES中&#xff0c;以实现数据的全文检索。互…...

江苏省建设厅新疆办网站/搜索引擎优化技巧

http://oshearesearch.com/...