linux驱动部分内容整理

文章目录

  • Linux驱动
    • 概念
    • 应用程序调用驱动程序流程
    • 驱动模块的加载
    • linux设备号
    • 加载和卸载
    • 注册
    • 新字符设备注册
    • 设备节点
    • 自动创建设备节点
    • 编译
      • 编译驱动程序
      • 编译应用程序
    • 地址映射
    • ioctrl
      • 命令码的解析
    • 并发与竞争
      • 原子操作
      • 自旋锁
      • 信号量
      • 互斥体
    • linux中断
    • DMA映射
    • 其它
      • printk
      • memcpy
      • volatile关键字
      • 用户访问内核

Linux驱动

概念

驱动充当着硬件与应用软件之间的桥梁(上面是系统调用,下面是硬件)。

Linux 驱动属于内核的一部分,因此驱动运行于内核空间。

驱动的具体任务:

  1. 读写设备寄存器(实现控制的方式);
  2. 完成设备的轮询、中断处理、DMA通信(CPU与外设通信的方式);
  3. 进行物理内存向虚拟内存的映射(在开启硬件MMU的情况下);

驱动的两个方向:

  1. 操作硬件(向下);
  2. 将驱动程序通入内核,实现面向操作系统内核的接口内容,接口由操作系统实现(向上)
img

用户空间不能直接对内核进行操作,必须使用**“系统调用”**的方法来实现从用户空间’‘陷入’'到内核空间,这样才能实现对底层驱动的操作。

应用程序调用驱动程序流程

在这里插入图片描述

驱动加载成功以后会在“/dev”目录下生成一个相应的文件, 应用程序通过对这个名为“/dev/xxx”(xxx 是具体的驱动文件名字)的文件进行相应的操作即可实现对硬件的操作。

open(close)函数:打开(关闭)/dev/xxxx

write(read)函数:向驱动写入数据(从驱动读取数据)

mmap函数:用于将设备的内存映射到进程空间中

在这里插入图片描述

应用程序使用到的函数在具体驱动程序中都有与之对应的函数,每一个系统调用,在驱动中都有与之对应的一个驱动函数。

驱动模块的加载

在 Linux 内核文件 include/linux/fs.h 中有个叫做file_operations的结构体,此结构体就是 Linux 内核驱动具体操作函数的集合。可以通过重新定义这些函数,来实现自己想要的功能。

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 (*read_iter) (struct kiocb *, struct iov_iter *); ssize_t (*write_iter) (struct kiocb *, struct iov_iter *); int (*iterate) (struct file *, struct dir_context *); int (*iterate_shared) (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 (*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, gned 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 **, void **); long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len);  void (*show_fdinfo)(struct seq_file *m, struct file *f); #ifndef CONFIG_MMU unsigned (*mmap_capabilities)(struct file *); #endif ssize_t (*copy_file_range)(struct file *, loff_t, struct file *, loff_t, size_t, unsigned int); int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t, u64); ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *, u64); } __randomize_layout;

linux设备号

Linux 中每个设备都有一个设备号(32位的dev_t类型),由主设备号(高12位)和次设备号(低20位)两部分组成,主设备号范围为0~4096,不可超出范围。主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备。

一些设备号的操作函数:

从dev_t中获取主设备号:MAJOR(dev)

从dev_t中获取从设备号:MANOR(dev)

将给定的主设备号和次设备号的值组合成 dev_t 类型的设备号:MKDEV(ma,mi)

加载和卸载

将驱动编译成模块(.ko),linux启动以后,使用相应命令加载驱动:

加载驱动模块:insmod xxx.ko

卸载模块:rmmod xxx.ko

查看系统中加载的所有模块及模块间的依赖关系:ismod

注册

编写驱动程序时,需要向内核注册模块加载函数(加载驱动时,module_init会被调用(入口);卸载驱动时,module_exit会被调用(出口)):

static struct file_operations chrdevbase_fops = {
/*传输的函数名称*/
};
/* 驱动入口函数 */ 
static int __init xxx_init(void) 
{ 
/* 入口函数具体内容 */ 
return 0; 
} /* 驱动出口函数 */ 
static void __exit xxx_exit(void) 
{ 
/* 出口函数具体内容 */ 
} /* 将上面两个函数指定为驱动的入口和出口函数 */ 
module_init(xxx_init); 
module_exit(xxx_exit); 
/*添加模块license信息*/
MODULE_LICENSE("GPL");

新字符设备注册

cdev结构体:

struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; struct list_head list; dev_t dev; unsigned int count; }; 
  1. 对cdev变量初始化:cdev_init(&cdev, &fops);,cdev就是要初始化的cdev结构体变量,fops是字符设备文件操作函数集合。

  2. 向linux系统添加字符设备:cdev_add(&cdev, devid, 1); ,cdev是要添加的字符设备,devid是该设备的设备号,count是要添加的设备数量。

  3. 卸载驱动时要删除字符设备:cdev_del(&cdev);

内核动态分配设备号:alloc_chrdev_region(dev, basemibor, count, name),dev用来获取设备号,baseminor是次设备号起始值,count是次设备号个数,name是设备名称,返回值为0代表错误。

释放设备号:unregister_chrdev_region(from, count),from是要释放的设备号,count是从from开始要释放的设备号数量。

设备节点

驱动加载成功后需要在/dev目录下创建一个与之对应的设备节点文件,应用程序就是通过操作这个设备节点文件来完成对具体设备的操作mknod /dev/chrdevbase c 200 0

“mknod”是创建节点命令,“/dev/chrdevbase”是要创建的节点文件,“c”表示这 是个字符设备,“200”是设备的主设备号,“0”是设备的次设备号。创建完成以后就会存在 /dev/chrdevbase 这个文件

自动创建设备节点

在驱动入口函数创建类和设备。

  1. 创建类:class_create(owner, name),owner一般为固定的THIS_MODULE,name是类名字。

    删除类:class_destroy(cls);,cls是要删除的类。

  2. 在类下创建设备:device_create(class,parent,devt,drvdata,fmt),其中class就是设备要创建于哪个类下面,parent和drvdata一般为0,devt是设备号,fmt是设备名字(如果设置fmt=xxx,就会生成/dev/xxx这个设备文件)。

    删除设备:device_destroy(class,devt) ,class是要删除的类,devt是要删除的设备号。

  3. 将设备的属性信息写成结构体:

struct test_dev{ dev_t devid; /* 设备号 */ struct cdev cdev; /* cdev */ struct class *class; /* 类 */ struct device *device; /* 设备 */ int major; /* 主设备号 */ int minor; /* 次设备号 */ };

编译

编译驱动程序

obj-m表示将.c文件编译为模块,-C表示将当前目录切换到指定目录,加入M=dir以后程序会自动到指定的 dir 目录中读取模块的源码并将其编译为.ko 文件,modules表示编译模块。

KERN_DIR := $linux_pathobj-m := xxx.oall:make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERN_DIR) M=`pwd` modulesclean:make -C $(KERN_DIR) M=`pwd` clean

编译应用程序

测试 APP 是要在 ARM 开发板上运行的,所以需要使用 arm-linux-gnueabihf-gcc 来编译:arm-linux-gnueabihf-gcc xxx.c -o xxx

地址映射

MMU(内存管理单元)功能:

  1. 完成虚拟空间到物理空间的映射;
  2. 内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性;

32位处理器虚拟地址范围是2^32=4GB

CPU只能访问虚拟地址,不能直接向寄存器地址写入数据,必须通过寄存器物理地址在Linux系统中对应的虚拟地址

在这里插入图片描述

地址映射函数:ioremap(phys_addr,size),phys_addr是要映射给的物理起始地址,size是要映射的内存空间大小。

#define APER_CLK_CTRL 0xF800012C 
static void __iomem *aper_clk_ctrl_addr; 
aper_clk_ctrl_addr = ioremap(APER_CLK_CTRL, 4); 

释放映射函数:iounmap(addr),其中addr是要取消映射的虚拟地址空间首地址。

iounmap(aper_clk_ctrl_addr);

IO内存写入函数:iowrite32(v,p),p为写入的虚拟地址,v为写入数据的地址。

ioctrl

ioctrl是设备接口控制函数,一些无法归类于file_operations所列功能的函数可以统一放在ioctrl这个函数操作中,对应于file_operations结构体的unlocked_ioctl成员。ioctrl函数里实现了多个对硬件的操作,应用层通过传入命令来调用相应的操作。

原型:int (*ioctl) (struct inode * node, struct file *filp, unsigned int cmd, unsigned long arg);

使用:

ioctrl(fd,cmd,arg);

fd:文件标识符,一般对应设备文件/dev/xxx

cmd:命令码

arg:用户传递的数据的地址

ioctrl本质上就是用户空间向内核空间提交一段具有特定含义的命令码,内核空间根据内核规定好的方式,对命令码进行解析,执行对应的底层操作。即:命令码和底层操作应该是一一对应的,一个具体的命令码就代表了一次底层操作的全部信息

命令码的解析

每个命令码由32bit组成:

bit 位数31 : 3029 : 1615 : 87 : 0
代表含义数据传输方向数据传递大小设备类型码(魔数)功能码
占用bit数21488

可以使用内核定义好的宏定义简化命令码的封装:如_IOR(设备码,功能码,变量类型)

数据传输方向:

[00] 表示不传递数据,内核宏定义为: _IO
[10] 表示只读,内核定义为: _IOR
[01] 表示只写,内核宏定义为: _IOW
[11] 表示可读可写,内核宏定义为: _IOWR

数据传递大小:使用宏定义时,需填写数据类型,如无符号32位就要填int。

设备类型码:每个驱动通过一个唯一的字符来代表,只是为了区分设备,可以为任意char型字符,如‘a’,‘b’。

功能码:区分不同的功能,可以为任意无符号整型数据,范围为0~255,如果定义了多个 ioctl 命令,通常从 0 开始编号递增

举例:

#include <sys/ioctl.h>/* 使用宏定义封装命令码 表示只写 设备类型码为 'L' 点灯功能码为 10 or 11 传输数据大小为 int 的大小*/
#define 	LED_ON_FUNC 	_IOW('L', 10, int)
#define 	LED_OFF_FUNC 	_IOW('L', 11, int)
.........
int main(int argc, const char *argv[])
{int fd;									// 文件描述符int ledSwitch = 0;						 // 用来表示操作哪盏 LEDif((fd = open("/dev/ledDev", O_RDWR)) == -1){perror("Open dev failed");return -1;}ledSwitch = 2;							// 表示要操作 LED2/* 使用 ioctl 函数 注意传入的是 ledSwitch 的地址*/if((ioctl(fd, LED_ON_FUNC, &ledSwitch)) == -1){perror("ioctl failed");return -1;}
}

并发与竞争

在驱动开发中要注意处理对共享资源的并发访问,保护多个线程都会访问的共享数据。

临界区就是共享数据段,对于临界区必须保证一次只有一个线程访问。

原子操作

为避免竞争,将一些指令作为一个整体运行,即作为一个原子存在,只能对整形变量或者位进行保护。

定义原子变量:atomic_t a;

示例:

atomic_t v = ATOMIC_INIT(0); /* 定义并初始化原子变零 v=0 */ atomic_set(10); /* 设置 v=10 */ 
atomic_read(&v); /* 读取 v 的值,肯定是 10 */ 
atomic_inc(&v); /* v 的值加 1,v=11 */ 

自旋锁

当一个线程要访问某个共享资源的时候首先要先获取相应的锁,锁只能被一个线程持有, 只要此线程不释放持有的锁,那么其他的线程就不能获取此锁。对于自旋锁而言,如果自旋锁 正在被线程 A 持有,线程 B 想要获取自旋锁,那么线程 B 就会处于忙循环-旋转-等待状态。

一 般 在 线 程 中 使 用 spin_lock_irqsave/ spin_unlock_irqrestore , 在 中 断 服 务 函 数 中 使 用 spin_lock/spin_unlock。

注意:

①因为在等待自旋锁的时候处于“自旋”状态,因此锁的持有时间不能太长,一定要短, 否则的话会降低系统性能。如果临界区比较大,运行时间比较长的话要选择其他的并发处理方 式,比如信号量和互斥体。

②自旋锁保护的临界区内不能调用任何可能导致线程休眠的 API 函数,否则的话可能导致死锁。

③不能递归申请自旋锁,因为一旦通过递归的方式申请一个你正在持有的锁,那么你就 必须“自旋”,等待锁被释放,然而你正处于“自旋”状态,根本没法释放锁。结果就是自己 把自己锁死了!

示例:

 /* 定义并初始化一个自旋锁 */ static spinlock_t lock;spin_lock_init(&lock);/* 线程 A */ void functionA (){ unsigned long flags; /* 中断状态 */ spin_lock_irqsave(&lock, flags); /* 获取锁 */ /* 临界区 */ spin_unlock_irqrestore(&lock, flags); /* 释放锁 */ } /* 中断服务函数 */ void irq() { spin_lock(&lock); /* 获取锁 */ /* 临界区 */ spin_unlock(&lock); /* 释放锁 */ }

信号量

特点:

①因为信号量可以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场 合。

②因此信号量不能用于中断,因为信号量会引起休眠,中断不能休眠。

③如果共享资源的持有时间比较短,那就不适合使用信号量了,因为频繁的休眠、切换 线程引起的开销要远大于信号量带来的那点优势。

示例:

struct semaphore sem; /* 定义信号量 */ sema_init(&sem, 1); /* 初始化信号量 */ down(&sem); /* 申请信号量 */ 
/* 临界区 */
up(&sem); /* 释放信号量 */ 

sem_t 是 信号量(semaphore)的类型定义,通常用于多线程或多进程之间的同步和互斥。信号量是一个非负整数,用于控制对共享资源的访问。

信号量通常通过以下几个函数来操作:

  • sem_init():初始化一个信号量。
  • sem_post():增加信号量的值(释放一个资源)。
  • sem_wait():减少信号量的值(请求一个资源)。如果信号量的值为零,则调用线程将被阻塞,直到信号量的值大于零。
  • sem_trywait():尝试减少信号量的值。如果信号量的值大于零,则减少它并立即返回;如果信号量的值为零,则立即返回错误。
  • sem_destroy():销毁一个信号量。

互斥体

互斥访问表示一次只有一个线程可以访 问共享资源,不能递归申请互斥体。

特点:

①mutex 可以导致休眠,因此不能在中断中使用 mutex,中断中只能使用自旋锁(因为中 断不参与进程调度,如果一旦在中断服务函数执行过程中休眠了,休眠了则意味着交出了 CPU 的使用权,CPU 使用权则跑到了其它线程了,那么就不能再回到中断断点处了)。

②和信号量一样,mutex 保护的临界区可以调用引起阻塞的 API 函数。

③因为一次只有一个线程可以持有 mutex,因此,必须由 mutex 的持有者释放 mutex。 并且 mutex 不能递归上锁和解锁。

示例:

struct mutex lock; /* 定义一个互斥体 */ mutex_init(&lock); /* 初始化互斥体 */ mutex_lock(&lock); /* 上锁 */ /* 临界区 */ mutex_unlock(&lock); /* 解锁 */ 

linux中断

申请中断:request_irq(irq,handler,flags,name,dev),irq是要申请的中断号;handler是对应的中断处理函数;flags是中断标志;name是中断名字(设置后可以在/proc/interrupts文件中看到);dev用于区分,可设为NULL;返回0中断申请成功,其它负值则中断申请失败。

中断标志:

IRQF_TRIGGER_RISI NG 上升沿触发

IRQF_TRIGGER_FALL ING下降沿触发

IRQF_TRIGGER_HIGH高电平触发

IRQF_TRIGGER_LOW低电平触发

IRQF_ONESHOT单次中断,中断执行一次就结束

IRQF_TRIGGER_NONE无触发

释放中断:free_irq(irq,dev),irq是要释放的中断,dev可设为NULL。

中断处理函数定义:irqreturn_t xxx (*irq_handler_t,int, void *),handler_t是要处理的中断号;void要与request_irq 函数的 dev 参数保持一致;返回值使用如下形式:return IRQ_RETVAL(IRQ_HANDLED)

获取设备节点:of_find_node_by_path(“node”),node是定义在设备树中的节点名,返回值是设备节点的结构体。

从interupts属性中提取设备号:irq_of_parse_and_map(struct device_node *dev, int index),dev是设备节点,index是索引号,返回中断号。

DMA映射

DMA传输需要连续的物理地址,而内核中使用的都是虚拟地址,需要建立物理地址和虚拟地址的映射。

一致性DMA映射:A=dma_alloc_coherent(dev,size,handle,flag)

dev:struct device *类型,可设为NULL

size:要分配的内存大小

handle:返回的内存物理(起始)地址,DMA可用

flag:用于指定内存分配的类型和行为的标志位,可设为GFP_KERNEL

返回值A:内存的虚拟起始地址

调用此函数将会分配一段内存,handle将返回这段内存的实际物理地址供DMA使用,A是handle对应的虚拟地址供内核使用,对A和handle任意一个操作,都会改变这段内存缓冲区的内容。

dma_addr_t是一个数据类型,通常用于表示DMA传输中数据所在的物理地址。

其它

printk

printk运行在内核态,根据级别对消息分类。

#define KERN_SOH "\001" /* ASCII Start Of Header */ 
#define KERN_SOH_ASCII '\001' #define KERN_EMERG KERN_SOH "0" /* system is unusable */ 
#define KERN_ALERT KERN_SOH "1" /* action must be taken immediately */
#define KERN_CRIT KERN_SOH "2" /* critical conditions */ 
#define KERN_ERR KERN_SOH "3" /* error conditions */ 
#define KERN_WARNING KERN_SOH "4" /* warning conditions */ 
#define KERN_NOTICE KERN_SOH "5" /* normal but significant condition */ 
#define KERN_INFO KERN_SOH "6" /* informational */ 
#define KERN_DEBUG KERN_SOH "7" /* debug-level messages */ 

0优先级最高,默认消息级别为4,只有比7级别高的消息才能显示在控制台上。

memcpy

memcpy函数的功能是从源头指向的内存块拷贝固定字节数的数据到目标指向的内存块。

memcpy用法:memcpy(destination,source,num),destination是要拷贝的目的地址内存起始地址,source是源头内存块起始地址,num是要拷贝的字节数。

volatile关键字

编译器优化常用的方法有:将内存变量缓存到寄存器;调整指令顺序充分利用CPU指令流水线。

volatile表示该变量随时可能发生变化,使用volatile声明变量时,总是从它所在的内存读取数据,遇到这个关键字声明的变量,编译器对访问该变量的代码不再进行优化,从而可以提供对特殊地址的稳定访问。

用户访问内核

内核空间到用户空间的复制:copy_from_user(to,from,n),to为目标用户空间的地址,from是要拷贝的内容的源内核空间地址,n是要拷贝的字节数,成功返回0。

用户空间到内核空间的复制:copy_to_usr(to,from,n)

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

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

相关文章

RocketMQ常用基本操作

文章中的rabbitmq使用的是rocketmq-all-5.1.3-bin-release版本&#xff0c;需要安装包的可自行下载 RockerMQ启动停止命令 启动命令 nohup sh bin/mqnamesrv & nohup sh bin/mqbroker -n localhost:9876 --enable-proxy & 查看日志 tail -f ~/logs/rocketmqlogs/…

明星中药企业系列洞察(九)一手好牌打的稀烂!近500年老字号锁定退市,太安堂为何“塌房”了?

近日&#xff0c;太安堂发布公告称&#xff0c;公司已收到深交所下发的《关于广东太安堂药业股份有限公司股票终止上市的决定》&#xff0c;深交所决定终止公司股票上市&#xff0c;预计其最后交易日期为7月4日。太安堂曾作为国内知名的中成药上市公司之一&#xff0c;是国家级…

matlab仿真 通信信号和系统分析(上)

&#xff08;内容源自详解MATLAB&#xff0f;SIMULINK 通信系统建模与仿真 刘学勇编著第三章内容&#xff0c;有兴趣的读者请阅读原书&#xff09; 一、求离散信号卷积和 主要还是使用卷积函数conv&#xff0c;值得注意的是&#xff0c;得到的卷积和长度结果为81&#xff0…

node.js+uniapp(vue),阿里云短信验证码

reg.vue: 思路是&#xff1a;前端调用获取验证码的接口 > 后端生成验证码返回给前端 > 前端渲染验证码 <template> <div> <input class"sl-input" v-model"phone" type"tel" maxlength"11" placeholder"手…

微信小程序毕业设计-微信食堂线上订餐系统项目开发实战(附源码+论文)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;微信小程序毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计…

【在线评论】不同视角下在线评论对客户满意度和推荐度的影响—推文分析—2024-07-01

今天的推文主题是【在线评论】&#xff0c;重点关注可以关注第四篇&#xff0c;很全面地分析了在线评论的信息多维性。 第一篇从客户的在线评论入手&#xff0c;将客户消费的动机为功利、享受、社会满足&#xff1b;第二篇是关于在线评论对消费者再次选择同一家酒店的机制探索…

MySQL之主从同步、分库分表

1、主从同步的原理 MySQL主从复制的核心是二进制日志 二进制日志&#xff08;binlog&#xff09;记录了所有DDL语句和DML语句&#xff0c;但不包括数据查询&#xff08;select、show&#xff09;语句。 1.1、复制分三步 master主库在事务提交时&#xff0c;会把数据变更记录…

电子战学习笔记01:电子战概论

0、写在文前 本人在学习电子战相关理论知识时&#xff0c;一直感觉无从下手&#xff0c;之后在老师的推荐下购买了《EW101&#xff1a;电子战基础》纸质书籍学习&#xff0c;所以将自己的学习笔记在CSDN上记录一下&#xff0c;也供有需要的同学参考。 1、电子战定义 电子战&…

全网最详细的 gin框架请求数据绑定Bind 源码解析 -- 帮助你全面了解gin框架的请求数据绑定原理和方法

在gin框架中&#xff0c;我们可以将多种请求数据&#xff08;json, form,uri&#xff0c;header等&#xff09;直接绑定到我们定义的结构体&#xff0c;底层是通过反射方式获取我们定义在结构体上面的tag来实现请求数据到我们的结构体数据的绑定的。 在gin的底层有2大体系的数据…

Python pip install模块时C++编译环境问题

pip install模块时C编译环境问题 在接触和使用python后&#xff0c;常常会通过pip install命令安装第三方模块&#xff0c;大多数模块可以直接安装&#xff0c;但许多新同学仍会遇见某些模块需要实时编译后才能安装&#xff0c;如报错信息大概是缺乏C编译环境&#xff0c;本文则…

【Elasticsearch】Elasticsearch索引创建与管理详解

文章目录 &#x1f4d1;引言一、Elasticsearch 索引的基础概念二、创建索引2.1 使用默认设置创建索引2.2 自定义设置创建索引2.3 创建索引并设置映射 三、索引模板3.1 创建索引模板3.2 使用索引模板创建索引 四、管理索引4.1 查看索引4.2 更新索引设置4.3 删除索引 五、索引别名…

Go-知识测试-性能测试

Go-知识测试-性能测试 1. 定义2. 例子3. testing.common 测试基础数据4. testing.TB 接口5. 关键函数5.1 testing.runBenchmarks5.2 testing.B.runN5.3 testing.B.StartTimer5.4 testing.B.StopTimer5.5 testing.B.ResetTimer5.6 testing.B.Run5.7 testing.B.run15.8 testing.B…

监听蓝牙对话的BlueSpy技术复现

本文是之前文章的BlueSpy技术的复现过程&#xff1a;https://mp.weixin.qq.com/s/iCeImLLPAwwKH1avLmqEpA 2个月前&#xff0c;网络安全和情报公司Tarlogic在西班牙安全大会RootedCon 2024上提出了一项利用蓝牙漏洞的BlueSpy技术&#xff0c;并在之后发布了一个名为BlueSpy的概…

git 提交代码忽略eslint代码检测

在暂存代码的时候会出现以上情况因为在提交代码的时候会默认运行代码进行检测&#xff0c;如果不符合代码规范就会进行报错 解决&#xff1a; 使用 git commit --no-verify -m xxx 忽略eslint的检测

Laravel 谨慎使用Storage::append()

在 driver 为 local 时&#xff0c;Storage::append()在高并发下&#xff0c;会存在丢失数据问题&#xff0c;文件被覆写&#xff0c;而非尾部添加&#xff0c;如果明确是本地文件操作&#xff0c;像日志写入&#xff0c;建议使用 Illuminate\Filesystem\Filesystem或者php原生…

邀请函 | 极限科技全新搜索引擎 INFINI Pizza 亮相 2024 可信数据库发展大会!

过去一年&#xff0c;在全球 AI 浪潮和国家数据局成立的推动下&#xff0c;数据库产业变革不断、热闹非凡。2024 年&#xff0c;站在中国数字经济产业升级和数据要素市场化建设的时代交汇点上&#xff0c;“2024 可信数据库发展大会” 将于 2024 年 7 月 16-17 日在北京悠唐皇冠…

肆拾玖坊的商业模式,49坊新零售奖金制度体系,众筹众创+会员制

肆拾玖坊之所以能够在短时间内成为白酒行业的“现象级”企业,,不仅是依靠独特商业模式,同时也依靠的是坚持用户为核心,围绕用户需求,让用户与产品直接产生连接理念。 坐标&#xff1a;厦门&#xff0c;我是易创客肖琳 深耕社交新零售行业10年&#xff0c;主要提供新零售系统工…

前端技术(二)——javasctipt 介绍

一、javascript基础 1. javascript简介 ⑴ javascript的起源 ⑵ javascript 简史 ⑶ javascript发展的时间线 ⑷ javascript的实现 ⑸ js第一个代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>…

Vue中的axios深度探索:从基础安装到高级功能应用的全面指南

文章目录 前言一、axios 请求1. axios的概念2. axios的安装3. axiso请求方式介绍4. axios请求本地数据5. axios跨域6. axios全局注册7. axios支持的请求类型1&#xff09;get请求2&#xff09;post请求3&#xff09;put请求4&#xff09;patch请求5&#xff09;delete请求 二、…

MyBatis操作数据库(入门)

本节目标 使用MyBatis完成简单的增删改查操作&#xff0c;参数传递掌握MyBatis的两种写法&#xff1a;注解和XML方式掌握MyBatis相关的日志配置 前言 在应用分层学习中&#xff0c;我们了解web应用程序一般分为三层&#xff0c;即Controller、Service、Dao。在之前的案例中&a…