【Linux設備驅動】1.字符設備驅動程序框架及相關結構體

目錄

  • 程序總體框架
    • 模塊加載函數
    • 模塊卸載函數
    • 具體操作函數
  • 相關結構體
    • cdev結構體
    • file_oparations結構體
  • 設備號
    • 分配設備號
    • 注銷設備號
    • 創建設備文件

程序總體框架

/* 包含相關頭文件 */
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>#define GLOBALMEM_SIZE	0x1000
#define MEM_CLEAR 0x1
#define GLOBALMEM_MAJOR 230static int globalmem_major = GLOBALMEM_MAJOR;
module_param(globalmem_major, int, S_IRUGO);struct globalmem_dev {struct cdev cdev;unsigned char mem[GLOBALMEM_SIZE];
};struct globalmem_dev *globalmem_devp;/* 用于 填充file_operations的函數 */
static int globalmem_open(struct inode *inode, struct file *filp)
{filp->private_data = globalmem_devp;return 0;
}static int globalmem_release(struct inode *inode, struct file *filp)
{return 0;
}static long globalmem_ioctl(struct file *filp, unsigned int cmd,unsigned long arg)
{return 0;
}static ssize_t globalmem_read(struct file *filp, char __user * buf, size_t size,loff_t * ppos)
{unsigned long p = *ppos;unsigned int count = size;int ret = 0;struct globalmem_dev *dev = filp->private_data;if (p >= GLOBALMEM_SIZE)return 0;if (count > GLOBALMEM_SIZE - p)count = GLOBALMEM_SIZE - p;if (copy_to_user(buf, dev->mem + p, count)) {ret = -EFAULT;} else {*ppos += count;ret = count;printk(KERN_INFO "read %u bytes(s) from %lu\n", count, p);}return ret;
}static ssize_t globalmem_write(struct file *filp, const char __user * buf,size_t size, loff_t * ppos)
{unsigned long p = *ppos;unsigned int count = size;int ret = 0;struct globalmem_dev *dev = filp->private_data;if (p >= GLOBALMEM_SIZE)return 0;if (count > GLOBALMEM_SIZE - p)count = GLOBALMEM_SIZE - p;if (copy_from_user(dev->mem + p, buf, count))ret = -EFAULT;else {*ppos += count;ret = count;printk(KERN_INFO "written %u bytes(s) from %lu\n", count, p);}return ret;
}/* 定義一個file_operations結構體 */
static const struct file_operations globalmem_fops = {.owner = THIS_MODULE,.read = globalmem_read,.write = globalmem_write,.unlocked_ioctl = globalmem_ioctl,.open = globalmem_open,.release = globalmem_release,
};/* 定義模塊入口函數 */
static int __init globalmem_init(void)
{/* 分配設備號 */int ret;dev_t devno = MKDEV(globalmem_major, 0);if (globalmem_major)ret = register_chrdev_region(devno, 1, "globalmem");else {ret = alloc_chrdev_region(&devno, 0, 1, "globalmem");globalmem_major = MAJOR(devno);}if (ret < 0)return ret;/* 申請、分配結構體內存 */globalmem_devp = kzalloc(sizeof(struct globalmem_dev), GFP_KERNEL);if (!globalmem_devp) {ret = -ENOMEM;goto fail_malloc;}/* 設置cdev結構體 */int err;cdev_init(&globalmem_devp->cdev, &globalmem_fops);globalmem_devp->cdev.owner = THIS_MODULE;err = cdev_add(&globalmem_devp->cdev, devno, 1);if (err)printk(KERN_NOTICE "Error %d adding globalmem%d", err, 0);return 0;fail_malloc:unregister_chrdev_region(devno, 1);return ret;
}
module_init(globalmem_init);/* 定義模塊出口函數 */
static void __exit globalmem_exit(void)
{cdev_del(&globalmem_devp->cdev);kfree(globalmem_devp);unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);
}
module_exit(globalmem_exit);/* 模塊的相關描述 */
MODULE_AUTHOR("xxx");
MODULE_LICENSE("GPL v2");

總體框架可以分為:

  • 驅動模塊加載函數
  • 驅動模塊卸載函數
  • 操作函數如open、read、write、release函數,填充file_operations結構體

模塊加載函數

/* 定義模塊入口函數 */
static int __init globalmem_init(void)
{/* 分配設備號 */int ret;dev_t devno = MKDEV(globalmem_major, 0);if (globalmem_major)ret = register_chrdev_region(devno, 1, "globalmem");else {ret = alloc_chrdev_region(&devno, 0, 1, "globalmem");globalmem_major = MAJOR(devno);}if (ret < 0)return ret;/* 申請、分配結構體內存 */globalmem_devp = kzalloc(sizeof(struct globalmem_dev), GFP_KERNEL);if (!globalmem_devp) {ret = -ENOMEM;goto fail_malloc;}/* 設置cdev結構體 */int err;cdev_init(&globalmem_devp->cdev, &globalmem_fops);globalmem_devp->cdev.owner = THIS_MODULE;err = cdev_add(&globalmem_devp->cdev, devno, 1);if (err)printk(KERN_NOTICE "Error %d adding globalmem%d", err, 0);return 0;fail_malloc:unregister_chrdev_region(devno, 1);return ret;
}
module_init(globalmem_init);

主要完成幾件事:申請\分配設備號、cdev結構體的初始化和注冊(便于統一管理)。

模塊卸載函數

/* 定義模塊出口函數 */
static void __exit globalmem_exit(void)
{cdev_del(&globalmem_devp->cdev);kfree(globalmem_devp);unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);
}
module_exit(globalmem_exit);

主要完成幾件事:注銷cdev結構體,釋放設備號。

具體操作函數

/* 用于 填充file_operations的函數 */
static int globalmem_open(struct inode *inode, struct file *filp)
{filp->private_data = globalmem_devp;return 0;
}static int globalmem_release(struct inode *inode, struct file *filp)
{return 0;
}static long globalmem_ioctl(struct file *filp, unsigned int cmd,unsigned long arg)
{return 0;
}static ssize_t globalmem_read(struct file *filp, char __user * buf, size_t size,loff_t * ppos)
{return 0;
}static ssize_t globalmem_write(struct file *filp, const char __user * buf,size_t size, loff_t * ppos)
{return 0;
}/* 定義一個file_operations結構體 */
static const struct file_operations globalmem_fops = {.owner = THIS_MODULE,.read = globalmem_read,.write = globalmem_write,.unlocked_ioctl = globalmem_ioctl,.open = globalmem_open,.release = globalmem_release,
};

定義操作函數如open、read、write、release函數,填充file_operations結構體

相關結構體

cdev結構體

// <include/linux/cdev.h>
struct cdev { struct kobject kobj;                  //內嵌的內核對象.struct module *owner;                 //該字符設備所在的內核模塊的對象指針.const struct file_operations *ops;    //該結構描述了字符設備所能實現的方法struct list_head list;                //用來將已經向內核注冊的所有字符設備形成鏈表.dev_t dev;                            //字符設備的設備號,由主設備號和次設備號構成.unsigned int count;                   //隸屬于同一主設備號的次設備號的個數.
};// 操作cdev結構體的函數// 對struct cdev結構體做初始化, 最重要的就是建立cdev 和 file_operations之間的連接:
void cdev_init(struct cdev *, const struct file_operations *); // 分配一個struct cdev結構,動態申請一個cdev內存,并對cdev進行初始化
struct cdev *cdev_alloc(void);void cdev_put(struct cdev *p);// 向內核注冊一個struct cdev結構體
int cdev_add(struct cdev *, dev_t, unsigned);// 向內核注銷一個struct cdev結構體
void cdev_del(struct cdev *);void cd_forget(struct inode *);

字符設備驅動結構cdev介紹 - 知乎 (zhihu.com)

這篇文章對cdev結構體講解的很清楚。

總的來說就是:

模塊加載時會調用cdev_init初始化字符設備結構體,調用cdev_add把這個字符設備添加進內核

模塊卸載時調用cdev_del把這個字符設備從內核中銷毀

// linux/fs/char_dev.c
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{int error;p->dev = dev;p->count = count;error = kobj_map(cdev_map, dev, count, NULL,exact_match, exact_lock, p);if (error)return error;kobject_get(p->kobj.parent);return 0;
}
// linux/drivers/base/Map.c
struct kobj_map {struct probe {struct probe *next;dev_t dev;unsigned long range;struct module *owner;kobj_probe_t *get;int (*lock)(dev_t, void *);void *data;} *probes[255];struct mutex *lock;
};int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range,struct module *module, kobj_probe_t *probe,int (*lock)(dev_t, void *), void *data)
{unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;unsigned index = MAJOR(dev);unsigned i;struct probe *p;if (n > 255)n = 255;p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL);if (p == NULL)return -ENOMEM;for (i = 0; i < n; i++, p++) {p->owner = module;p->get = probe;p->lock = lock;p->dev = dev;p->range = range;p->data = data;}mutex_lock(domain->lock);for (i = 0, p -= n; i < n; i++, p++, index++) {struct probe **s = &domain->probes[index % 255];while (*s && (*s)->range < range)s = &(*s)->next;p->next = *s;*s = p;}mutex_unlock(domain->lock);return 0;
}

簡單地說,設備驅動程序通過調用cdev_add把它所管理的設備對象的指針嵌入到一個類型為struct probe的節點之中,然后再把該節點加入到cdev_map(kobj_map )所實現的哈希鏈表中。當后續要打開一個字符設備文件時,通過調用 kobj_lookup() 函數,根據設備編號就可以找到 cdev 結構變量,從而取出其中的 ops 字段。

kobj_map函數中哈希表的實現原理和后面注冊分配設備號中的幾乎完全一樣,通過要加入系統的設備的主設備號major(major=MAJOR(dev))來獲得probes數組的索引值i(i = major % 255),然后把一個類型為struct probe的節點對象加入到probes[i]所管理的鏈表中。

file_oparations結構體

file_operation就是把系統調用和驅動程序關聯起來的關鍵數據結構。這個結構的每一個成員都對應著一個系統調用。讀取file_operation中相應的函數指針,接著把控制權轉交給函數,從而完成了Linux設備驅動程序的工作。

在通讀file_operations 方法的列表時, 不少參數包含字串 __user. 這種注解是一種文檔形式, 注意, 一個指針是一個不能被直接解引用的用戶空間地址. 對于正常的編譯,__user沒有效果, 但是它可被外部檢查軟件使用來找出對用戶空間地址的錯誤使用。

struct file_operations被定義在include/linux/fs.h

// include/linux/fs.h
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 (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, 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 *);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 *);void (*mremap)(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 (*aio_fsync) (struct kiocb *, 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, unsigned 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_MMUunsigned (*mmap_capabilities)(struct file *);
#endif
};

**struct module *owner:**是一個指向擁有這個結構的模塊的指針. 這個成員用來在它的操作還在被使用時阻止模塊被卸載. 幾乎所有時間中, 它被簡單初始化為**THIS_MODULE**, 一個在 <linux/module.h> 中定義的宏.

設備號

一個字符設備或塊設備都有一個主設備號和一個次設備號。主設備號用來標識與設備文件相連的驅動程序,用來反映設備類型。次設備號被驅動程序用來辨別操作的是哪個設備,用來區分同類型的設備。
linux內核中,設備號用dev_t來描述。

typedef u_long dev_t;

在32位機中是4個字節,高12位表示主設備號,低20位表示次設備號。
因此,主設備號范圍為2^12 = 4096 = 4K,次設備號范圍為2^20 = 1048576 = 1M
1)從dev_t設備號中提取major和minor
MAJOR(dev_t dev)
MINOR(dev_t dev)
2)使用下列宏則可以通過主設備號和次設備號生成dev_t,構建設備號:
MKDEV(int major, int minor)

#define MINORBITS       20
#define MINORMASK       ((1U << MINORBITS) - 1)
#define MAJOR(dev)      ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)      ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))

分配設備號

靜態分配:register_chrdev_region()
動態分配:alloc_chrdev_region()

register_chrdev_region()函數用于已知起始設備的設備號的情況,而alloc_chrdev_region()用于設備號未知,向系統動態申請未被占用的設備號的情況,函數調用成功之后,會把得到的設備號放入第一個參數dev中。alloc_chrdev_region()相比于register_chrdev_region()的優點在于它會自動避開設備號重復的沖突。

靜態分配和動態分配的函數都調用了__register_chrdev_region函數

// linux\fs\char_dev.c
static struct kobj_map *cdev_map;static DEFINE_MUTEX(chrdevs_lock);static struct char_device_struct {struct char_device_struct *next;unsigned int major;unsigned int baseminor;int minorct;char name[64];struct cdev *cdev;		/* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE]; // 全局的哈希表static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor,int minorct, const char *name)
{struct char_device_struct *cd, **cp;int ret = 0;int i;cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);if (cd == NULL)return ERR_PTR(-ENOMEM);// 上鎖,防止并發訪問chrdevs全局數組mutex_lock(&chrdevs_lock);/* 如果major為0,那么尋找第一個未被占用的設備號 */if (major == 0) {for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {if (chrdevs[i] == NULL)break;}if (i == 0) {ret = -EBUSY;goto out;}major = i;}cd->major = major;cd->baseminor = baseminor; // 起始次設備號cd->minorct = minorct; // 次設備號的數量strlcpy(cd->name, name, sizeof(cd->name));// 插入設備到鏈表i = major_to_index(major);// 尋找合適的插入位置for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)if ((*cp)->major > major ||((*cp)->major == major &&(((*cp)->baseminor >= baseminor) ||((*cp)->baseminor + (*cp)->minorct > baseminor))))break;/* 檢查次設備號范圍重疊  */if (*cp && (*cp)->major == major) {int old_min = (*cp)->baseminor;int old_max = (*cp)->baseminor + (*cp)->minorct - 1;int new_min = baseminor;int new_max = baseminor + minorct - 1;// 分別判斷左半邊和右半邊是否重疊/* New driver overlaps from the left.  */if (new_max >= old_min && new_max <= old_max) {ret = -EBUSY;goto out;}/* New driver overlaps from the right.  */if (new_min <= old_max && new_min >= old_min) {ret = -EBUSY;goto out;}}// 插入設備并解鎖cd->next = *cp;*cp = cd;mutex_unlock(&chrdevs_lock);return cd;
out: // 錯誤處理mutex_unlock(&chrdevs_lock);kfree(cd);return ERR_PTR(ret);
}

在這里插入圖片描述

linux采用哈希表(chrdevs全局的指針數組)維護設備號。對于主設備號major,通過major_to_index哈希函數(major % 255)計算哈希表的鍵值作為數組下標i,在以數組下標i的指針元素構成的單鏈表中尋找合適的插入位置進行插入。

插入點計算如下代碼:

// 尋找合適的插入位置for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)if ((*cp)->major > major ||((*cp)->major == major &&(((*cp)->baseminor >= baseminor) ||((*cp)->baseminor + (*cp)->minorct > baseminor))))break;

首先根據主設備號進行判斷,保證主設備號是在鏈表中是遞增的;如果主設備號相等,那么就判斷次設備號,保證次設備號也是遞增的。

另外,全局的chrdevs數組作為哈希表屬于共享資源,需要保證互斥訪問,因此使用一個互斥鎖進行保護。

注銷設備號

void unregister_chrdev_region(dev_t from, unsigned count)
void unregister_chrdev_region(dev_t from, unsigned count)
{dev_t to = from + count;dev_t n, next;for (n = from; n < to; n = next) {next = MKDEV(MAJOR(n)+1, 0);if (next > to)next = to;kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));}
}static struct char_device_struct *
__unregister_chrdev_region(unsigned major, unsigned baseminor, int minorct)
{struct char_device_struct *cd = NULL, **cp;int i = major_to_index(major);// 上鎖mutex_lock(&chrdevs_lock);// 在chrdevs鏈表中尋找匹配的字符設備結構體for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)if ((*cp)->major == major &&(*cp)->baseminor == baseminor &&(*cp)->minorct == minorct)break;// 如果找到了,就從鏈表中移除if (*cp) {cd = *cp;*cp = cd->next;}// 解鎖mutex_unlock(&chrdevs_lock);return cd;
}

注銷設備號的過程其實就是分配設備號的反過程,先對哈希表chrdevs上鎖,然后尋找要注銷的設備號對應的鏈表節點,然后將其從單鏈表中移除。

注意:設備號通過哈希表進行管理,cdev字符設備結構體也是通過哈希表進行管理。

創建設備文件

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/15595.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/15595.shtml
英文地址,請注明出處:http://en.pswp.cn/web/15595.shtml

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

相關文章

C# System.Span<T>、ref struct

1. Span<T>的特性 system.span<T>在.net core 2.0版本引入它適用于對連續內存的操作&#xff0c;而不產生新的內存分配&#xff0c;比如數組、字符串、堆外內存類型為ref struct&#xff0c;不能作為參數傳遞&#xff0c;不能被裝箱(不能作為類的字段)&#xff0c…

信號處理技術:現代通信技術的基石

隨著信息技術的飛速發展&#xff0c;通信技術的每一次革新都極大地改變了人們的生活方式。而在這背后&#xff0c;信號處理技術作為通信技術的核心&#xff0c;通過深入分析信號特性、提取有用信息、轉換信號形式等一系列手段&#xff0c;為現代通信技術的發展提供了強有力的支…

機器學習7大方面,30個硬核數據集。純干貨分享

在剛剛開始學習算法的時候&#xff0c;大家有沒有過這種感覺&#xff0c;最最重要的那必須是算法本身&#xff01; 其實在一定程度上忽略了數據的重要性。 而事實上一定是&#xff0c;質量高的數據集可能是最重要的&#xff01; 數據集在機器學習算法項目中具有非常關鍵的重…

Python讀寫文件

最近得以空閑&#xff0c;然后繼續學習py。 學習一下py中最頻繁用到的文件讀寫的方法。 在py中&#xff0c;操作是通過文件對象【File obj】實現的&#xff0c;通過文件對象可以讀寫文本文件和一些二進制文件。 1.打開文件 使用Python中的open函數。有8個參數&#xff0c;但…

2024.5.25.python.exercise

# # 導入數據處理的包 # # from pyecharts.charts import Line # # from pyecharts.options import TitleOpts, LegendOpts, ToolboxOpts, VisualMapOpts, LabelOpts # # import json # # # # # 打開文件 # # file_us open("美國.txt", "r", encoding&quo…

C++ Primer Plus第十八章復習題

1、使用用大括號括起的初始化列表語法重寫下述代碼。重寫后的代碼不應使用數組ar。 class z200 { private:int j;char ch;double z; public:Z200(int jv,char chv&#xff0c;zv) : j(jv), ch (chv), z(zv){} };double x 8.8; std::string s "what a bracing effect ! …

頭歌OpenGauss數據庫-H.存儲過程第1關:創建存儲過程

編程要求 數據庫中已經存在三個基礎表&#xff1a; student ( num integer, name char(20), age integer, level integer, dept char(20) ); course ( id integer, name char(20) ); sel_course ( studentid integer, courseid integer, score integer ); 創建存儲過程&#xf…

Vue3/Vite引入EasyPlayer.js播放H265視頻錯誤的問題

一、引入EasyPlayer.js github鏈接:GitHub - EasyDarwin/EasyPlayer.js: EasyPlayer.js H5播放器 將demo/html目錄下的 EasyPlayer-element.min.js、EasyPlayer-lib.min.js、EasyPlayer.wasm、jquery.min.js 復制到vue3工程的public目錄下,注意,vue3 vite的index.html文件…

win10配置wsl的深度學習環境

# 1、一步完成wsl&#xff1a;開啟虛擬機、linux子系統、并下載ubuntu # 官方文檔: https://learn.microsoft.com/zh-cn/windows/wsl/install wsl --install# 2、打開windows terminal&#xff0c;選ubuntu交互環境 # 第一次需要配置用戶名和密碼 # 接下來正常使用即可# 3、cud…

自然資源-做好用地用海國土空間規劃符合性審查

自然資源-做好用地用海國土空間規劃符合性審查 為發揮國土空間規劃戰略引領和剛性管控作用&#xff0c;強化國土空間規劃對各專項規劃的指導約束作用&#xff0c;依法依規加強自然資源要素保障&#xff0c;做好用地用海用島國土空間規劃符合性審查工作&#xff1a; 一、加快地…

深入了解數據庫設計中的規范化與反規范化

目錄 零、前言 一、一些基本術語 二、關系模式 2.1. 什么是關系模式 2.2. 示例 三、數據依賴 3.1. 函數依賴 3.1.1. 完全函數依賴 3.1.2. 部分函數依賴 3.1.3. 傳遞函數依賴 3.2. 多值依賴 3.3. 連接依賴 四、規范化 4.1. 第一范式&#xff08;1NF&#xff09; …

什么是 DNS 轉發?

DNS轉發是一種網絡傳輸技術&#xff0c;主要用于解決本地DNS服務器無法直接解析某個特定域名的情況。當本地DNS服務器收到一個無法解析的域名請求時&#xff0c;它會將該請求轉發給其他可信的DNS服務器&#xff0c;以獲取所需的解析結果。這種技術有助于優化網絡性能和安全性&a…

【Flutter】有狀態組件StatefulWidgetScaffold組件屬性

&#x1f525; 本文由 程序喵正在路上 原創&#xff0c;CSDN首發&#xff01; &#x1f496; 系列專欄&#xff1a;Flutter學習 &#x1f320; 首發時間&#xff1a;2024年5月26日 &#x1f98b; 歡迎關注&#x1f5b1;點贊&#x1f44d;收藏&#x1f31f;留言&#x1f43e; 目…

AWS聯網和內容分發之VPC

Amazon Virtual Private Cloud&#xff08;VPC&#xff09;是一項用于在AWS云中創建一個邏輯隔離的虛擬網絡的服務&#xff0c;使用戶能夠在云中啟動AWS資源&#xff08;例如EC2實例&#xff09;&#xff0c;并將其放置在自己定義的虛擬網絡中。 Amazon VPC讓您能夠全面地控制…

AEE運行機制深入剖析——閱讀筆記

AEE運行機制深入剖析——閱讀筆記 在移動設備和嵌入式系統的開發中&#xff0c;應用執行環境&#xff08;Application Execution Environment&#xff0c;簡稱AEE&#xff09;起著至關重要的作用。AEE是操作系統層面的一個框架&#xff0c;負責管理應用程序的生命周期&#xf…

JVM性能調優:內存模型及垃圾收集算法

JVM內存結構 根據Java虛擬機規范&#xff0c;JVM內存主要劃分為以下區域&#xff1a; 年輕代&#xff08;New Generation&#xff09; 包括Eden空間&#xff0c;用于存放新創建的對象。Survivor區由兩個相同大小的Survivor1和Survivor2組成&#xff0c;用于存放經過初次垃圾回…

AI菜鳥向前飛 — LangChain系列之十四 - Agent系列:從現象看機制(上篇)

上一篇介紹了Agent與LangGraph的基礎技能Tool的必知必會 AI菜鳥向前飛 — LangChain系列之十三 - 關于Tool的必知必會 前面已經詳細介紹了Promp、RAG&#xff0c;終于來到Agent系列&#xff08;別急后面還有LangGraph&#xff09;&#xff0c;大家可以先看下這張圖&#xff1…

leetcode328. 奇偶鏈表,附詳細解析和代碼注釋

leetcode328. 奇偶鏈表 給定單鏈表的頭節點 head &#xff0c;將所有索引為奇數的節點和索引為偶數的節點分別組合在一起&#xff0c;然后返回重新排序的列表。 第一個節點的索引被認為是 奇數 &#xff0c; 第二個節點的索引為 偶數 &#xff0c;以此類推。 請注意&#xff0…

Java的反射機制詳解:動態操作類和對象

Java反射是一種強大的機制&#xff0c;允許程序在運行時查詢和操作類、方法、接口等。這種能力使得Java應用可以在運行時動態地創建對象、調用方法和訪問屬性&#xff0c;極大地提升了程序的靈活性和可擴展性。本文將深入探討Java反射的原理、核心API和實際應用場景&#xff0c…

Flutter 中的 CupertinoSlidingSegmentedControl 小部件:全面指南

Flutter 中的 CupertinoSlidingSegmentedControl 小部件&#xff1a;全面指南 在Flutter框架中&#xff0c;CupertinoSlidingSegmentedControl是一個用于創建類似iOS風格的滑動分段控制器的小部件。這種控制器通常用于允許用戶在不同的視圖或設置之間切換。本文將為您提供一個…