Linux驱动入门 —— 利用引脚号操作GPIO进行LED点灯

目录

一、字符设备驱动程序框架

编写驱动程序的步骤:

对于 LED 驱动,我们想要什么样的接口?

LED 驱动能支持多个板子的基础:分层思想

二、Linux驱动如何指向一个GPIO

直接通过寄存器来操作GPIO

利用引脚号操作GPIO

IMX6ULL引脚获取

三、Linux的统一接口 — GPIO子系统

为什么需要统一接口

四、GPIO子系统函数介绍

(1)gpio_request()

(2)gpio_free()

(3)gpio_direction_input()

(4)gpio_direction_output()

(5)gpio_get_value()

(6)gpio_set_value()

五、LED驱动代码

驱动层代码:

应用层代码:

Makefile

上机测试:


一、字符设备驱动程序框架

图 6.1 中驱动层访问硬件外设寄存器依靠的是 ioremap 函数去映射到寄存 器地址,然后开始控制寄存器。

编写驱动程序的步骤:

  1. 确定主设备号,也可以让内核分配
  2. 定义自己的 file_operations 结构体
  3. 实现对应的 drv_open/drv read/drv write 等函数,填入 file operations 结构体
  4. 把 file_operations 结构体告诉内核: register_chrdev
  5. 谁来注册驱动程序啊? 得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
  6. 有入口函数就应该有出口函数: 卸载驱动程序时,出口函数调用unregister_chrdev
  7. 其他完善:提供设备信息,自动创建设备节点: class_create,device_create

对于 LED 驱动,我们想要什么样的接口?

LED 驱动能支持多个板子的基础:分层思想

把驱动拆分为通用的框架(leddrv.c)、具体的硬件操作(board_X.c):

以面向对象的思想,改进代码,抽象出一个结构体:

每个单板相关的 board_X.c 实现自己的 led_operations 结构体,供上层 的 leddrv.c 调用:

二、Linux驱动如何指向一个GPIO

在编写驱动程序的时候,如果我们需要进行点灯操作,首先要知道控制的是哪一个引脚才可以真正的操作LED。我们通过在原理图中查找发现,LED是由GPIO5_3控制。

直接通过寄存器来操作GPIO

(1)我们在学习入门视频的时候,常常会看到他们使用ioremap()函数对寄存器进行映射,然后直接操作寄存器。不再要使用这个寄存器的时候,就调用iounmap()函数进行释放。
(2) 这样编写毫无疑问,非常原始,就像是在写51单片机的程序。但是不同的在于,51单片机的寄存器并不多,所以直接操作寄存器并不麻烦。而i.max6ull这个级别的芯片,寄存器一大堆,再直接进行寄存器操作,无疑非常麻烦。
(3)我上一篇写的博客就是直接利用寄存器来操作GPIO,链接如下:Linux驱动入门 —— LED点灯驱动程序-CSDN博客

想要深入学习的,可以看看正点原子,或者韦东山老师的驱动教学视频。

利用引脚号操作GPIO

(1)从上面的原理图,我们知道了LED是由GPIO5_3控制之后,就可以直接开始操作了吗?
(2)不对,在 Linux 中,GPIO 的标识和控制通常是通过引脚号来进行的,引脚号是用于唯一标识特定的 GPIO 引脚。
(3)如果我们有stm32,msp430这种裸机开发经验,就会发现,不同的芯片对于GPIO的名字定义是不同的。比如STM32将引脚定义成PA0,PB4这种。但是如果是MSP430单片机,他对引脚的定义是P2.3,P1.0这样的。不同厂家对自己的芯片GPIO名字不同。
(4)这种GPIO名字不同,会导致什么结果呢?这样会让驱动开发人员总是要记,不同的芯片的命名规则,显然是非常麻烦的。于是,Linux规定了,我不管你是什么芯片厂家,不管你怎么命名,你如果要跑Linux,就必须将引脚变成一个数字,这个数字叫做引脚号。驱动开发人员,只需要知道这个引脚对应的引脚号,就可以进行操作了。而把这个引脚变成引脚号的过程,就是由芯片原厂的工程师来做了。
(5)关于引脚号的获取,最简单的办法就是,直接联系厂家询问。比如如下是飞腾的芯片,他们的引脚映射表。

IMX6ULL引脚获取

现在我的这个IMX6ULL开发板要控制GPIO5_3,而且我找不到他们的引脚映射表,那么他这个引脚号是多少呢?
如果找不到映射表,我们连接上开发板

输入指令:cat /sys/kernel/debug/gpio

即可获得GPIO的映射表,以及他的起始地址。

(1)这个时候有人可能就会认为,这里搜索到的gpiochip5就是GPIO5了。答案是否定的。
(2)为什么这么说呢?因为我上面说了,不同厂家对GPIO的命名是不同的,他们厂家的工程师最终会将这些GPIO抽象成一个引脚号。在这个抽象的过程中驱动中的命名可能会和原理图上的命名有些许出入,比如imax6ull开发板的GPIO5就是gpiochip4,因为imax6ull开发板是从GPIO1开始进行计算,而驱动程序中,是从gipo0开始的。
(3)如何确定是这样的呢?首先我们看上图,指导gpio0的地址为209c000,那么直接打开芯片手册,可以看到GPIO1的起始地址为209c000,正好对应

(4)现在知道GPIO5对应gpio4了,然后从终端中可以指导,gpio4的起始引脚号为128,那么GPIO5_3的引脚号就是128+3=131。

三、Linux的统一接口 — GPIO子系统

为什么需要统一接口

(1)讲解Linux的GPIO子系统之前,我先拿单片机开发做引子。
(2)对于绝大多数人而言,学习嵌入式开发,都是从51单片机开始的。STC89C52作为51单片机的一款经典单片机,大家多多少少都有些许了解。
(3)在编写STC89C52单片机的程序时候,我们都是直接对寄存器进行操作的,比如下面这个串口初始化程序。

void UartInit(void)		//9600bps@11.0592MHz
{SCON = 0x50;		//8位数据,可变波特率AUXR &= 0xBF;		//定时器1时钟为Fosc/12,即12TAUXR &= 0xFE;		//串口1选择定时器1为波特率发生器TMOD &= 0x0F;		//设定定时器1为16位自动重装方式TL1 = 0xE8;		//设定定时初值TH1 = 0xFF;		//设定定时初值ET1 = 0;		//禁止定时器1中断TR1 = 1;		//启动定时器1
}

(4)学习完51单片机之后,大多数人开始进阶STM32F103这款芯片。因为STM32F103这款芯片的寄存器很多,直接使用寄存器开发,查手册会相当的麻烦。于是ST公司就封装了一些库,如下为GPIO操作部分的库函数。

(5)我们会发现,不同的芯片,他的库函数大概率是不一样的。假如我们编写了一个业务程序,在STM32上跑的好好的。如果因为某些事情,我要换一款芯片,而这款芯片的库函数和STM32的不一样。最终会导致什么结果?很明显,所有业务程序需要重写编写!这是非常麻烦的事情!
(6)为了防止出现这种情况,Linux规定了,不管你是啥芯片,你只要想跑Linux,就必须给我统一接口!管你什么厂家,你的芯片让GPIO设置为输出的函数,名字必须叫做int gpio_direction_output()!
(7)这样做,存在什么好处呢?显而易见,我们业务代码不需要更改了,如果我们想换一款芯片,只需要底层稍微的改动一下即可。这也是为什么有些人说的,没跑Linux,阶级分明,跑了Linux,众生平等。

四、GPIO子系统函数介绍

Linux的GPIO子系统中可以通过如下函数配置GPIO。

int gpio_request(unsigned gpio, const char *label);
void gpio_free(unsigned gpio);
int gpio_direction_input(unsigned gpio);
int gpio_direction_output(unsigned gpio, int value);
int gpio_get_value(unsigned gpio);
void  gpio_set_value(unsigned gpio, int value);

(1)gpio_request()

/****** 函数介绍 ******/
/* 作用 :  向Linux 内核中用于请求申请一个 GPIO 引脚* 传入参数 : * gpio : 要请求的 GPIO 引脚号* label : 给GPIO起一个名字* 返回参数 :  如何返回0,表示申请GPIO成功。如果返回负数,表示申请GPIO出现错误
*/
int gpio_request(unsigned gpio, const char *label);
  • 作用: 向Linux 内核中用于请求申请一个 GPIO 引脚的函数。如果我们想对一个引脚进行操作,需要最先调用 gpio_request()这个函数。
  • gpio : 要请求的 GPIO 引脚号。这个引脚号可以自己直接给出,还可以通过 of_get_named_gpio 函数从设备树获取指定 GPIO 属性信息(设备树的内容)
  • label : 给GPIO起一个名字,因为直接一个引脚号不方便人阅读,所以可以给这个引脚号起一个名字。随便起名字,只要你自己喜欢,不影响。
  • 返回值 : 如何返回0,表示申请GPIO成功。如果返回负数,表示申请GPIO出现错误。

(2)gpio_free()

/****** 函数介绍 ******/
/* 作用 : 如果不使用某个GPIO了,那么就需要调用 gpio_free 函数进行释放* 传入参数 : * gpio : 要释放的GPIO引脚号* 返回参数 :  无
*/
void gpio_free(unsigned gpio);
  • 作用 : 如果不使用某个 GPIO 了, 那么就需要调用 gpio_free 函数进行释放。
  • gpio : 要释放的GPIO引脚号。与gpio_request的GPIO引脚号是同一个东西。
  • 返回参数 : 无

(3)gpio_direction_input()

/****** 函数介绍 ******/
/* 作用 : 设置某个 GPIO 为输入* 传入参数 : * gpio : 要设置为输入的GPIO 引脚号* 返回参数 : 设置成功返回 0; 设置失败返回负值
*/
int gpio_direction_input(unsigned gpio);
  • 作用 : 将GPIO配置为输入方向。申请完GPIO之后,需要根据需求配置为输入或者输出,这个函数可以将GPIO设置为输入
  • gpio : 要设置为输入的GPIO 引脚号
  • 返回参数 : 返回 0,表示成功将 GPIO 引脚设置为输入模式。返回负数,表示出错或无法设置 GPIO 引脚。

(4)gpio_direction_output()

/****** 函数介绍 ******/
/* 作用 : 设置某个 GPIO 为输出,并且设置默认输出值* 传入参数 : * gpio : 要设置为输出的GPIO 引脚号* value : GPIO 默认输出值* 返回参数 : 设置成功返回 0; 设置失败返回负值
*/
int gpio_direction_output(unsigned gpio, int value);
  • 作用 : 将GPIO配置为输出方向,并且设置默认输出值。申请完GPIO之后,需要根据需求配置为输入或者输出,这个函数可以将GPIO设置为输出
  • gpio : 设置为输出的GPIO 引脚号
  • value : GPIO 默认输出值。如果GPIO初始化成功之后,默认输出的电压。
  • 返回参数 : 返回 0,表示成功将 GPIO 引脚设置为输出模式。返回负数,表示出错或无法设置 GPIO 引脚。

(5)gpio_get_value()

/****** 函数介绍 ******/
/* 作用 : 获取指定GPIO的电平值* 传入参数 : * gpio : 要获取电平值的GPIO标号* 返回参数 : 获取电平信息成功,高电平返回1,低电平返回0。GPIO电平获取失败返回负值
*/
int gpio_get_value(unsigned gpio);
  • 作用 : 获取指定GPIO的电平信息
  • gpio : 要获取电平值的GPIO标号
  • 返回参数 : 获取电平信息成功,高电平返回1,低电平返回0。GPIO电平获取失败返回负值。

(6)gpio_set_value()

  • 作用 : 设置指定GPIO的电平值
  • gpio : 要设置指定GPIO的电平值
  • value : 要设置的电平值,如果传入0,则表示将GPIO设置为低电平。传入一个非0值,表示将GPIO设置为高电平
  • 返回参数 : 无
/****** 函数介绍 ******/
/* 作用 : 获取指定GPIO的电平值* 传入参数 : * gpio : 要设置指定GPIO的电平值* value : 要获取电平值的GPIO标号* 返回参数 : 无
*/
void  gpio_set_value(unsigned gpio, int value);

五、LED驱动代码

驱动层代码:

这里只展现代码,详细介绍参考大佬博客 Linux驱动入门 —— LED点灯驱动程序-CSDN博客

led_drv.c

#include "asm-generic/errno-base.h"
#include "asm-generic/gpio.h"
#include "asm/uaccess.h"
#include <linux/module.h>
#include <linux/poll.h>#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>// 描述一个引脚
struct gpio_desc{int gpio;   // 引脚编号char *name; // 名字
};static struct gpio_desc gpios[] = {{131, "led0", },  // 引脚编号,名字
};/* 1. 确定主设备号                                                                 */
static int major = 0;
static struct class *gpio_class;  // 一个类,用于创建设备节点/* 3. 实现对应的open/read/write等函数,填入file_operations结构体                     */
static ssize_t gpio_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{char tmp_buf[2];  // 存放驱动层和应用层交互的信息int err;   // 没有使用,用于存放copy_from_user和copy_to_user的返回值,消除报错int count = sizeof(gpios)/sizeof(gpios[0]); // 记录定义的最大引脚数量// 应用程序读的时候,传入的值如果不是两个,那么返回一个错误if (size != 2)return -EINVAL;/* 作用 : 驱动层得到应用层数据* tmp_buf : 驱动层数据* buf : 应用层数据* 1  :数据长度为1个字节(因为我只需要知道他控制的是那一盏灯,所以只需要传入一个字节数据)*/err = copy_from_user(tmp_buf, buf, 1);//第0项表示要操作哪一个LED,如果操作的LED超出,表示失败if (tmp_buf[0] >= count)return -EINVAL;//将引脚电平读取出来tmp_buf[1] = gpio_get_value(gpios[(int)tmp_buf[0]].gpio);/* 作用 : 驱动层发数据给应用层* buf : 应用层数据* tmp_buf : 驱动层数据* 2  :数据长度为2个字节*/err = copy_to_user(buf, tmp_buf, 2);return 2;
}static ssize_t gpio_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{unsigned char ker_buf[2];int err;// 应用程序读的时候,传入的值如果不是两个,那么返回一个错误if (size != 2)return -EINVAL;/* 作用 : 驱动层得到应用层数据* tmp_buf : 驱动层数据* buf : 应用层数据* size  :数据长度为size个字节*/err = copy_from_user(ker_buf, buf, size);// 如果要操作的GPIO不在规定范围内,返回错误if (ker_buf[0] >= sizeof(gpios)/sizeof(gpios[0]))return -EINVAL;// 设置指定引脚电平gpio_set_value(gpios[ker_buf[0]].gpio, ker_buf[1]);return 2;    
}/* 2. 定义自己的file_operations结构体                                            */
static struct file_operations gpio_led_drv= {.owner	 = THIS_MODULE,.read    = gpio_drv_read,.write   = gpio_drv_write,
};/* 4. 把file_operations结构体告诉内核:注册驱动程序                           */
/* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
static int __init gpio_drv_init(void)
{int err;  //用于保存函数返回值,用于判断函数是否执行成功int i;    //因为存在多个GPIO可能要申请,所以建立一个i进行for循环int count = sizeof(gpios)/sizeof(gpios[0]);  //统计有多少个GPIO/*__FILE__ :表示文件*__FUNCTION__ :当前函数名*__LINE__ :在文件的哪一行*/printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);for (i = 0; i < count; i++){		/* 设置为输出引脚 *///申请指定GPIO引脚,申请的时候需要用到名字err = gpio_request(gpios[i].gpio, gpios[i].name);// 如果返回值小于0,表示申请失败if (err < 0) {//如果GPIO申请失败,打印出是哪个GPIO申请出现问题printk("can not request gpio %s %d\n", gpios[i].name, gpios[i].gpio);return -ENODEV;}// 如果GPIO申请成功,设置输出高电平gpio_direction_output(gpios[i].gpio, 1);}/* 注册file_operations 	*/// 注册字符驱动程序major = register_chrdev(0, "100ask_led", &gpio_led_drv);  /* /dev/gpio_desc *//******这里相当于命令行输入 mknod  /dev/100ask_gpio c 240 0 创建设备节点*****/// 创建类,为THIS_MODULE模块创建一个类,这个类叫做gpio_classgpio_class = class_create(THIS_MODULE, "100ask_led_class");if (IS_ERR(gpio_class))   //如果返回错误{/*__FILE__ :表示文件*__FUNCTION__ :当前函数名*__LINE__ :在文件的哪一行*/printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);// 注销字符驱动程序unregister_chrdev(major, "100ask_led_class");// 返回错误return PTR_ERR(gpio_class);}/*输入参数是逻辑设备的设备名,即在目录/dev目录下创建的设备名*参数一 : 在gpio_class类下面创建设备*参数二 : 无父设备的指针*参数三 : 主设备号+次设备号*参数四 : 没有私有数据*/device_create(gpio_class, NULL, MKDEV(major, 0), NULL, "100ask_led"); /* /dev/100ask_gpio */// 如果执行到这里了,说明LED驱动装载完成printk("LED driver loading is complete\n");return err;
}/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数 */
static void __exit gpio_drv_exit(void)
{int i;int count = sizeof(gpios)/sizeof(gpios[0]);/*__FILE__ :表示文件*__FUNCTION__ :当前函数名*__LINE__ :在文件的哪一行*/printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);// 销毁gpio_class类下面的设备节点device_destroy(gpio_class, MKDEV(major, 0));// 销毁gpio_class类class_destroy(gpio_class);// 注销驱动unregister_chrdev(major, "100ask_led");for (i = 0; i < count; i++){// 将GPIO释放gpio_free(gpios[i].gpio);		}// 如果执行到这里了,说明LED驱动卸载完成printk("The LED driver is uninstalled\n");
}/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */module_init(gpio_drv_init);     // 确认入口函数
module_exit(gpio_drv_exit);     // 确认出口函数/*最后我们需要在驱动中加入 LICENSE 信息和作者信息,其中 LICENSE 是必须添加的,否则的话编译的时候会报错,作者信息可以添加也可以不添加*这个协议要求我们代码必须免费开源,Linux遵循GPL协议,他的源代码可以开放使用,那么你写的内核驱动程序也要遵循GPL协议才能使用内核函数*因为指定了这个协议,你的代码也需要开放给别人免费使用,同时可以根据这个协议要求很多厂商提供源代码*但是很多厂商为了规避这个协议,驱动源代码很简单,复杂的东西放在应用层
*/
MODULE_LICENSE("GPL"); // 指定模块为GPL协议
MODULE_AUTHOR("CSDN:qq_919426896");  // 表明作者,可以不写

应用层代码:

strtol()函数是将字符转换为数字。

因为我们在命令行中输入的1,其实是字符1,而不是数字1。为了和驱动层统一数据类型,所以这里需要调用这个函数。

ledtest.c

#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <poll.h>
#include <signal.h>static int fd;// int led_on(int which);
// int led_off(int which);
// int led_status(int which);/* 可执行文件名   | 表示要操作哪一盏灯  | 灯状态  |    效果* ./led_test    |   <0|1|2|..>        | on     |硬件上开灯* ./led_test    |   <0|1|2|..>        | off    |硬件上关灯* ./led_test    |   <0|1|2|..>        |        |读取led状态,并且显示在终端*/
int main(int argc, char **argv)
{int ret;     // 存放函数返回值,用于判断函数是否正常执行char buf[2]; // 存放命令行的后两个字符(<0|1|2|...> [on | off])// 如果传入参数少于两个,打印文件用法if (argc < 2) {printf("Usage: %s <0|1|2|...> [on | off]\n", argv[0]);return -1;}// 打开文件,因为在驱动层中,device_create()函数创建的设备节点名字叫做100ask_led,而设备节点都存放在/dev目录下,所以这里是/dev/100ask_ledfd = open("/dev/100ask_led", O_RDWR);// 如果无法打开,返回错误if (fd == -1){printf("can not open file /dev/100ask_led\n");return -1;}// 如果传入了三个参数,表示写入if (argc == 3){/* write *//* 作用 : 将字符串转化为一个整数* argv[1] :  要转换为长整数的字符串* NULL :如果提供了 endptr 参数,则将指向解析结束位置的指针存储在 endptr 中。endptr 可以用于进一步处理字符串中的其他内容* 0 : 设置为 0,则会根据字符串的前缀(如 "0x" 表示十六进制,"0" 表示八进制,没有前缀表示十进制)来自动判断进制*/buf[0] = strtol(argv[1], NULL, 0);// 判断是否为打开if (strcmp(argv[2], "on") == 0)buf[1] = 0;  //因为LED外接3.3V,所以输出低电平才是开灯elsebuf[1] = 1;  //因为LED外接3.3V,所以输出高电平才是关灯// 向字符驱动程序中写入ret = write(fd, buf, 2);}// 否则表示读取电平信息else{/* read *//* 作用 : 将字符串转化为一个整数* argv[1] :  要转换为长整数的字符串* NULL :指向第一个不可转换的字符位置的指针* 0 : 表示默认采用 10 进制转换*/buf[0] = strtol(argv[1], NULL, 0);// 读取电平,从驱动层读取两个数据ret = read(fd, buf, 2);// 如果返回值为2,表示正常读取到了电平。(为什么是2,看驱动程序的gpio_drv_read)if (ret == 2){//打印引脚信息printf("led %d status is %s\n", buf[0], buf[1] == 0 ? "on" : "off");}}close(fd);return 0;
}

Makefile

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88all:make -C $(KERN_DIR) M=`pwd` modules $(CROSS_COMPILE)gcc -o ledtest ledtest.c clean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderrm -f ledtestobj-m	+= led_drv.o

上机测试:

可参考上一篇博客 Linux驱动入门 —— LED点灯驱动程序-CSDN博客

在ubuntu上执行make

挂载 NFS 目录,参考 开发板挂载 Ubuntu 的 NFS 目录-CSDN博客

打开开发板,这里使用的是imx6ull  

执行 insmod led_drv.ko 

开始测试

执行 ./ledtest 0 on  使开发板亮灯

测试结束执行 rmmod led_drv.ko 卸载驱动

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/220073.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

算法通关村第十八关-黄金挑战回溯困难问题

大家好我是苏麟 , 今天带来几道回溯比较困难的题 . 回溯有很多比较难的问题&#xff0c;这里我们看两个&#xff0c;整体来说这两个只是处理略复杂&#xff0c;还不是最难的问题 . 大纲 IP问题 IP问题 描述 : 有效 IP 地址 正好由四个整数&#xff08;每个整数位于 0 到 255 …

redis:一、面试题常见分类+缓存穿透的定义、解决方案、布隆过滤器的原理和误判现象、面试回答模板

redis面试题常见分类 缓存穿透 定义 缓存穿透是一种现象&#xff0c;引发这种现象的原因大概率是遭到了恶意攻击。具体就是查询一个一定不存在的数据&#xff0c;mysql查询不到数据也不会直接写入缓存&#xff0c;就会导致这个数据的每次请求都需要查DB&#xff0c;数据库压力…

# 和 $ 的区别①

# 和 $ 都是为了获取变量的值 # 和 $ 区别 : 使用 # 查询 id 为 1 的内容 如果看不懂代码,就去看<<Mybatis 的操作(结合上文)续集>>,我这里为了简练一点就不多解释了 Select("select * from userInfo where id #{id}")UserInfo selectOne(Integer id…

Hive命令操作

1.命令行模式 1. 获取帮助 --> hive -H 或-help 2. 运行hive语句 --> hive -e "执行语句" 3. 运行hive文件 --> hive –f "执行文件" 4. 定义变量 --> hive –hivevar keyvalue 5. 引用变量 --> ${varname} 2. 交互模式 1. 进入客户端 -…

【UE 材质】切换颜色、纹理时的过渡效果

效果 步骤 1. 新建一个工程&#xff0c;创建Basic关卡 2. 创建一个材质&#xff0c;这里命名为“M_Plane”&#xff0c;打开这个材质&#xff0c;在材质图表中添加如下节点 注意“Noise”节点中的函数选择“Voronoi” 3. 对材质“M_Plane”创建材质实例 4. 在场景中放置一个平…

【SpringBoot】FreeMarker视图渲染

目录 一、FreeMarker 简介 1.1 什么是FreeMarker&#xff1f; 1.2 Freemarker模板组成部分 1.3 为什么要使用FreeMarker 二、Springboot集成FreeMarker 2.1 配置 2.2 数据类型 2.2.1 字符串 2.2.2 数值 2.2.3 布尔值 2.2.4 日期 2.3 常见指令 2.3.2 assign 2.3…

docker compose部署wordpress

准备机器&#xff1a; 192.168.58.151 &#xff08;关闭防火墙和selinux&#xff09; 安装好docker服务 &#xff08;详细参照&#xff1a;http://t.csdnimg.cn/usG0s 中的国内源安装docker&#xff09; 部署wordpress: 创建目录&#xff1a; [rootdocker ~]# mkdir…

docker-compose容器编排(单机一键拉起所有容器)

1、安装docker-compose实验 安装完成 2、yaml文件 &#xff08;1&#xff09;定义 一种直观的、以竖列形式展示序列化数据格式的标记语言&#xff0c;可读性高。类似于json格式&#xff0c;但语法简单 yaml通过缩进表示数据结构&#xff0c;连续的项目用-减号表示 &#x…

【教程】查看GPU与PCIe版本和匹配速率

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhang.cn] PCIe理论速度对照表 bash脚本 #!/bin/bash# 查找所有 NVIDIA GPU 设备的设备ID及其类型 device_info$(lspci | grep -i nvidia | egrep "VGA compatible controller|3D controller" | awk {print $1, …

C# WPF上位机开发(动态库dll的开发)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 很多时候&#xff0c;我们并不希望所有的程序都放到一个exe里面。因为这样相当于把所有的风险都放在了一个文件里里面&#xff0c;既不利于程序的升…

完全平方数 C语言xdoj49

问题描述 若一个整数n能表示成某个整数m的平方的形式&#xff0c;则称这个数为完全平方数。写一个程序判断输入的整数是不是完全平方数。 输入说明 输入数据为一个整数n&#xff0c;0<n<10000000。 输出说明 如果n是完全平方数&#xff0c;则输出构成这个完全…

Simple Water Caustic Pattern In Unity ShaderGpaph

shadertoy上有各种神奇的效果&#xff0c;以我的见识根本想象不到这些是怎么弄出来的。 不过不会做至少可以先会用。 这篇文章抓取一个shadertoy的示例以制作一个测试效果。 参考这篇shadertoy&#xff0c;使用自定义节点装填hlsl的noise代码 Shader - Shadertoy BETA 首先使…

洛谷P1722 矩阵Ⅱ——卡特兰数

传送门&#xff1a; P1722 矩阵 II - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P1722 用不需要除任何数的公式来求。 #define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<cstdio> #include<cmath> #includ…

12.13_黑马数据结构与算法笔记Java

目录 098 堆 heapify 3 099 堆 增删替换 100 堆 e01 堆排序 100 堆e02 求数组第k大元素 100 堆e03 求数据流第k大元素 100 堆e04 求数据流中位数1 100 堆e04 求数据流中位数2 100 堆e04 求数据流中位数3 101 二叉树 概述 102 二叉树 深度优先遍历 103 二叉树 前中后…

loki 如何格式化日志

部署 grafana-loki 首先介绍一下如何部署 官方文档&#xff1a;部署 grafana-loki 部署命令 设置集群的存储类&#xff0c;如果有默认可以不设置设置命名空间 helm install loki oci://registry-1.docker.io/bitnamicharts/grafana-loki --set global.storageClasslocal -n …

AWR2243级联(TI文档)

摘要 本应用报告描述了TI的级联毫米波雷达系统。该解决方案基于TI的AWR2243雷达芯片。使用20 GHz的本振输入和输出路径&#xff0c;这些芯片中的几个级联在一起并同步工作。每个AWR2243芯片最多支持4个接收天线和3个发射天线。级联多个这样的芯片允许雷达系统使用更多的接收和发…

对于双显卡电脑,如何分辨现在用的是独立显卡还是集成显卡?

一、问题描述 台式电脑本身自带了集成显卡&#xff0c;然后又购买了一块NVIDIA的独立显卡。 现在&#xff0c;就有疑问了&#xff0c;如何判断你的显示器连接的是独立显卡还是集成显卡呢&#xff1f; 二、NVIDIA双显卡机型 1、在桌面右下角&#xff0c;选择NVIDIA图标&…

YOLOv8改进 | 2023主干篇 | 替换LSKNet遥感目标检测主干 (附代码+修改教程+结构讲解)

一、本文介绍 本文给大家带来的改进内容是LSKNet&#xff08;Large Kernel Selection, LK Selection&#xff09;&#xff0c;其是一种专为遥感目标检测设计的网络架构&#xff0c;其核心思想是动态调整其大的空间感受野&#xff0c;以更好地捕捉遥感场景中不同对象的范围上下…

数据结构之Map/Set讲解+硬核源码剖析

&#x1f495;"活着是为了活着本身而活着"&#x1f495; 作者&#xff1a;Mylvzi 文章主要内容&#xff1a;数据结构之Map/Set讲解硬核源码剖析 一.搜索树 1.概念 二叉搜索树又叫二叉排序树&#xff0c;他或者是一颗空树&#xff0c;或者是具有以下性质的树 若它…

HTTP协议请求详解

✏️✏️✏️今天给大家分享的是 HTTP 请求部分的基础知识。 清风的CSDN博客 &#x1f6e9;️&#x1f6e9;️&#x1f6e9;️希望我的文章能对你有所帮助&#xff0c;有不足的地方还请各位看官多多指教&#xff0c;大家一起学习交流&#xff01; ✈️✈️✈️动动你们发财的小…