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.pswp.cn/diannao/38565.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/38565.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/38565.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

如何在ubuntu上安裝ros-noetic?

如何在ubuntu上安裝ros-noetic&#xff1f; 1. 源由2. 快速安裝3. ROS學習 1. 源由 圍繞ros-noetic這個系統&#xff0c;前面已經有不少談及&#xff1a; Linux 35.5 JetPack v5.1.3ros-noetic安裝Linux 35.5 JetPack v5.1.3Fast-Planner編譯安裝Linux 35.5 JetPack v5.1.…

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/…

多線程編程的挑戰與解決方案

多線程編程的挑戰與解決方案 大家好&#xff0c;我是免費搭建查券返利機器人省錢賺傭金就用微賺淘客系統3.0的小編&#xff0c;也是冬天不穿秋褲&#xff0c;天冷也要風度的程序猿&#xff01; 1. 多線程編程的挑戰 在現代軟件開發中&#xff0c;多線程編程成為處理并發任務…

PatchTST創新點

這篇論文的創新點主要集中在PatchTST模型的設計和應用中。以下是對其創新點的詳細說明&#xff1a; 創新點 頻道獨立補丁設計&#xff1a;PatchTST模型通過將多變量時間序列分割成不同的頻道&#xff0c;每個頻道作為單變量時間序列處理。每個頻道獨立地通過實例歸一化操作和補…

明星中藥企業系列洞察(九)一手好牌打的稀爛!近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、電子戰定義 電子戰&…

Spring Boot與Apache Kafka集成的深度指南

Spring Boot與Apache Kafka集成的深度指南 大家好&#xff0c;我是免費搭建查券返利機器人省錢賺傭金就用微賺淘客系統3.0的小編&#xff0c;也是冬天不穿秋褲&#xff0c;天冷也要風度的程序猿&#xff01; 在現代分布式系統中&#xff0c;消息隊列的作用愈發重要&#xff0…

【鴻蒙學習筆記】鴻蒙ArkTS學習筆記

應用開發導讀&#xff1a;https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/application-dev-guide-V5 【鴻蒙培訓】第&#xff11;天?環境安裝 【鴻蒙培訓】第&#xff12;天?裝飾器?組件和頁面生命周期 【鴻蒙學習筆記】數據類型 【鴻蒙學習筆記】運算…

Spring Cloud中的服務發現與注冊

Spring Cloud中的服務發現與注冊 大家好&#xff0c;我是免費搭建查券返利機器人省錢賺傭金就用微賺淘客系統3.0的小編&#xff0c;也是冬天不穿秋褲&#xff0c;天冷也要風度的程序猿&#xff01;今天我們將探討Spring Cloud中的服務發現與注冊&#xff0c;這是微服務架構中至…

全網最詳細的 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的概…

深度學習之生成對抗網絡StyleGAN3

StyleGAN3 是由 NVIDIA 團隊提出的第三代生成對抗網絡(GAN),在前代 StyleGAN 和 StyleGAN2 的基礎上進行了改進,以實現更高質量的圖像生成。StyleGAN3 的主要改進在于解決了 StyleGAN2 中存在的偽影(artifacts)問題,并且提升了生成圖像的一致性和穩定性。 StyleGAN3 的…