【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.mzph.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风格的滑动分段控制器的小部件。这种控制器通常用于允许用户在不同的视图或设置之间切换。本文将为您提供一个…