全面详细介绍Linux 虚拟文件系统(VFS)原理

2023/11/30 10:14:21

一. 通用文件模型

Linux内核支持装载不同的文件系统类型,不同的文件系统有各自管理文件的方式。Linux中标准的文件系统为Ext文件系统族,当然,开发者不能为他们使用的每种文件系统采用不同的文件存取方式,这与操作系统作为一种抽象机制背道而驰。

为支持各种文件系统,Linux内核在用户进程(或C标准库)和具体的文件系统之间引入了一个抽象层,该抽象层称之为“虚拟文件系统(VFS)”。

VFS一方面提供一种操作文件、目录及其他对象的统一方法,使用户进程不必知道文件系统的细节。另一方面,VFS提供的各种方法必须和具体文件系统的实现达成一种妥协,毕竟对几十种文件系统类型进行统一管理并不是件容易的事。

为此,VFS中定义了一个通用文件模型,以支持文件系统中对象(或文件)的统一视图。

Linux对Ext文件系统族的支持是最好的,因为VFS抽象层的组织与Ext文件系统类似,这样在处理Ext文件系统时可以提高性能,因为在Ext和VFS之间转换几乎不会损失时间。

内核处理文件的关键是inode,每个文件(和目录)都有且只有一个对应的inode(struct inode实例),其中包含元数据和指向文件数据的指针,但inode并不包含文件名。系统中所有的inode都有一个特定的编号,用于唯一的标识各个inode。文件名可以随时更改,但是索引节点对文件是唯一的,并且随文件的存在而存在。

对于每个已经挂载的文件系统,VFS在内核中都生成一个超级块结构(struct super_block实例),超级块代表一个已经安装的文件系统,用于存储文件系统的控制信息,例如文件系统类型、大小、所有inode对象、脏的inode链表等。

inode和super block在存储介质中都是有实际映射的,即存储介质中也存在超级块和inode。但是由于不同类型的文件系统差异,超级块和inode的结构不尽相同。而VFS的作用就是通过具体的设备驱动获得某个文件系统中的超级块和inode节点,然后将其中的信息填充到内核中的struct super_block和struct inode中,以此来试图对不同文件系统进行统一管理。

由于块设备速度较慢(于内存而言),可能需要很长时间才能找到与一个文件名关联的inode。Linux使用目录项(dentry)缓存来快速访问此前的查找操作结果。在VFS读取了一个目录或文件的数据之后,则创建一个dentry实例(struct dentry),以缓存找到的数据。

dentry结构的主要用途就是建立文件名和相关的inode之间的联系。一个文件系统中的dentry对象都被放在一个散列表中,同时不再使用的dentry对象被放到超级块指向的一个LRU链表中,在某个时间点会删除比较老的对象以释放内存。

 【文章福利】小编推荐自己的Linux内核技术交流群: 【977878001】整理一些个人觉得比较好得学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100进群领取,额外赠送一份 价值699的内核资料包(含视频教程、电子书、实战项目及代码)

内核资料直通车:Linux内核源码技术学习路线+视频教程代码资料

学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈

另外简单提一下两个数据结构:

每种注册到内核的文件系统类型以struct file_system_type结构表示,每种文件系统类型中都有一个链表,指向所有属于该类型的文件系统的超级块。

当一个文件系统挂载到内核文件系统的目录树上,会生成一个挂载点,用来管理所挂载的文件系统的信息。该挂载点用一个struct vfsmount结构表示,这个结构后面会提到。

上面的这些结构的关系大致如下:

其中红色字体的链表为内核中的全局链表。

二. 挂载文件系统

在用户程序中,使用mount系统调用来挂载文件系统,相应的使用umount卸载文件系统。当然,内核必须支持将要挂载的文件系统类型,在内核启动时或者在安装内核模块时,可以注册特定的文件系统类型到内核,注册的函数为register_filesystem()。

mount命令最常用的方式是mount [-t fstype] something somewhere

其中something是将要被挂载的设备或目录,somewhere指明要挂载到何处。-t选项指明挂载的文件系统类型。由于something指向的设备是一个已知设备,即其上的文件系统类型是确定的,所以-t选项必须设置正确才能挂载成功。

每个装载的文件系统都对应一个vfsmount结构的实例。

由于装载过程是向内核文件系统目录树中添加装载点,这些装载点就存在一种父子关系,这和父目录与子目录的关系类似。例如,我的根文件系统类型是squashfs,装载到根目录“/”,生成一个挂载点,之后我又在/tmp目录挂载了ramfs文件系统,在根文件系统中的tmp目录生成了一个挂载点,这两个挂载点就是父子关系。这种关系存储在struct vfsmount结构中。

在下图中,根文件系统为squashfs,根目录为“/”,然后创建/tmp目录,并挂载为ramfs,之后又创建了/tmp/usbdisk/volume9和/tmp/usbdisk/volume1两个目录,并将/tmp/dev/sda1和/tmp/dev/sdb1两个分区挂载到这两个目录上。其中/tmp/dev/sda1设备上有如下文件:

gccbacktrace/

----> gcc_backtrace.c

---->man_page.log

---->readme.txt

notes-fs.txt

smb.conf

挂载完成后,VFS中相关的数据结构的关系如图所示。

mount系统调用在内核中的入口点是sys_mount函数,该函数将装载的选项从用户态复制一份,然后调用do_mount()函数进行挂载,这个函数做的事情就是通过特定文件系统读取超级块和inode信息,然后建立VFS的数据结构并建立上图中的关系。

在父文件系统中的某个目录上挂载另一个文件系统后,该目录原来的内容就被隐藏了。例如,/tmp/samba/是非空的,然后,我将/tmp/dev/sda1挂载到/tmp/samba上,那这时/tmp/samba/目录下就只能看到/tmp/dev/sda1设备上的文件,直到将该设备卸载,原来目录中的文件才会显示出来。这是通过struct vfsmount中的mnt_mountpoint和mnt_root两个成员来实现的,这两个成员分别保存了在父文件系统中挂载点的dentry和在当前文件系统中挂载点的dentry,在卸载当前挂载点之后,可以找回挂载目录在父文件系统中的dentry对象。

三. 一个进程中与文件系统相关的信息

struct task_struct {
	……
	/* filesystem information */
	struct fs_struct *fs;
	/* open file information */
	struct files_struct *files;
	/* namespaces */
	struct nsproxy *nsproxy;
	……
}

其中fs成员指向进程当前工作目录的文件系统信息。files成员指向了进程打开的文件的信息。nsproxy指向了进程所在的命名空间,其中包含了虚拟文件系统命名空间。

从上图可以看到,fs中包含了文件系统的挂载点和挂载点的dentry信息。而files指向了一系列的struct file结构,其中struct path结构用于将struct file和vfsmount以及dentry联系起来。struct file保存了内核所看到的文件的特征信息,进程打开的文件列表就存放在task_struct->files->fd_array[]数组以及fdtable中。

task_struct结构还存放了其打开文件的文件描述符fd的信息,这是用户进程需要用到的,用户进程在通过文件名打开一个文件后,文件名就没有用处了,之后的操作都是对文件描述符fd的,在内核中,fget_light()函数用于通过整数fd来查找对应的struct file对象。由于每个进程都维护了自己的fd列表,所以不同进程维护的fd的值可以重复,例如标准输入、标准输出和标准错误对应的fd分别为0、1、2。

struct file的mapping成员指向属于文件相关的inode实例的地址空间映射,通常它设置为inode->i_mapping。在读写一个文件时,每次都从物理设备上获取文件的话,速度会很慢,在内核中对每个文件分配一个地址空间,实际上是这个文件的数据缓存区域,在读写文件时只是操作这块缓存,通过内核有相应的同步机制将脏的页写回物理设备。super_block中维护了一个脏的inode的链表。

struct file的f_op成员指向一个struct file_operations实例(图中画错了,不是f_pos),该结构保存了指向所有可能文件操作的指针,如read/write/open等。

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);
	int (*readdir) (struct file *, void *, filldir_t);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
	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 *, struct dentry *, int datasync);
	……
};

四. 打包文件系统

在制作好了文件系统的目录之后,可通过特定于文件系统类型的工具对目录进行打包,即制作文件系统。例如squashfs文件系统的打包工具为mksquashfs。除了打包之外,打包工具还针对特定文件系统生成超级块和inode节点信息,最终生成的文件系统镜像可以被内核解释并挂载。

附录 VFS相关数据结构

inode:

struct inode {
	/* 全局的散列表 */
	struct hlist_node	i_hash;
	/* 根据inode的状态可能处理不同的链表中(inode_unused/inode_in_use/super_block->dirty) */
	struct list_head	i_list;
	/* super_block->s_inodes链表的节点 */
	struct list_head	i_sb_list;
	/* inode对应的dentry链表,可能多个dentry指向同一个文件 */
	struct list_head	i_dentry;
	/* inode编号 */
	unsigned long		i_ino;
	/* 访问该inode的进程数目 */
	atomic_t		i_count;
	/* inode的硬链接数 */
	unsigned int		i_nlink;
	uid_t			i_uid;
	gid_t			i_gid;
	/* inode表示设备文件时的设备号 */
	dev_t			i_rdev;
	u64			i_version;
	/* 文件的大小,以字节为单位 */
	loff_t			i_size;
#ifdef __NEED_I_SIZE_ORDERED
	seqcount_t		i_size_seqcount;
#endif
	/* 最后访问时间 */
	struct timespec		i_atime;
	/* 最后修改inode数据的时间 */
	struct timespec		i_mtime;
	/* 最后修改inode自身的时间 */
	struct timespec		i_ctime;
	/* 以block为单位的inode的大小 */
	blkcnt_t		i_blocks;
	unsigned int		i_blkbits;
	unsigned short          i_bytes;
	/* 文件属性,低12位为文件访问权限,同chmod参数含义,其余位为文件类型,如普通文件、目录、socket、设备文件等 */
	umode_t			i_mode;
	spinlock_t		i_lock;	/* i_blocks, i_bytes, maybe i_size */
	struct mutex		i_mutex;
	struct rw_semaphore	i_alloc_sem;
	/* inode操作 */
	const struct inode_operations	*i_op;
	/* file操作 */
	const struct file_operations	*i_fop;
	/* inode所属的super_block */
	struct super_block	*i_sb;
	struct file_lock	*i_flock;
	/* inode的地址空间映射 */
	struct address_space	*i_mapping;
	struct address_space	i_data;
#ifdef CONFIG_QUOTA
	struct dquot		*i_dquot[MAXQUOTAS];
#endif
	struct list_head	i_devices; /* 若为设备文件的inode,则为设备的打开文件列表节点 */
	union {
		struct pipe_inode_info	*i_pipe;
		struct block_device	*i_bdev; /* 若为块设备的inode,则指向该设备实例 */
		struct cdev		*i_cdev; /* 若为字符设备的inode,则指向该设备实例 */
	};
 
	__u32			i_generation;
 
#ifdef CONFIG_FSNOTIFY
	__u32			i_fsnotify_mask; /* all events this inode cares about */
	struct hlist_head	i_fsnotify_mark_entries; /* fsnotify mark entries */
#endif
 
#ifdef CONFIG_INOTIFY
	struct list_head	inotify_watches; /* watches on this inode */
	struct mutex		inotify_mutex;	/* protects the watches list */
#endif
 
	unsigned long		i_state;
	unsigned long		dirtied_when;	/* jiffies of first dirtying */
 
	unsigned int		i_flags; /* 文件打开标记,如noatime */
 
	atomic_t		i_writecount;
#ifdef CONFIG_SECURITY
	void			*i_security;
#endif
#ifdef CONFIG_FS_POSIX_ACL
	struct posix_acl	*i_acl;
	struct posix_acl	*i_default_acl;
#endif
	void			*i_private; /* fs or device private pointer */
};

super_block:

struct super_block {
	/* 全局链表元素 */
	struct list_head	s_list;
	/* 底层文件系统所在的设备 */
	dev_t			s_dev;
	/* 文件系统中每一块的长度 */
	unsigned long		s_blocksize;
	/* 文件系统中每一块的长度(以2为底的对数) */
	unsigned char		s_blocksize_bits;
	/* 是否需要向磁盘回写 */
	unsigned char		s_dirt;
	unsigned long long	s_maxbytes;	/* Max file size */
	/* 文件系统类型 */
	struct file_system_type	*s_type;
	/* 超级块操作方法 */
	const struct super_operations	*s_op;
	struct dquot_operations	*dq_op;
 	struct quotactl_ops	*s_qcop;
	const struct export_operations *s_export_op;
	unsigned long		s_flags;
	unsigned long		s_magic;
	/* 全局根目录的dentry */
	struct dentry		*s_root;
	struct rw_semaphore	s_umount;
	struct mutex		s_lock;
	int			s_count;
	int			s_need_sync;
	atomic_t		s_active;
#ifdef CONFIG_SECURITY
	void                    *s_security;
#endif
	struct xattr_handler	**s_xattr;
	/* 超级块管理的所有inode的链表 */
	struct list_head	s_inodes;	/* all inodes */
	/* 脏的inode的链表 */
	struct list_head	s_dirty;	/* dirty inodes */
	struct list_head	s_io;		/* parked for writeback */
	struct list_head	s_more_io;	/* parked for more writeback */
	struct hlist_head	s_anon;		/* anonymous dentries for (nfs) exporting */
	/* file结构的链表,该超级块上所有打开的文件 */
	struct list_head	s_files;
	/* s_dentry_lru and s_nr_dentry_unused are protected by dcache_lock */
	/* 不再使用的dentry的LRU链表 */
	struct list_head	s_dentry_lru;	/* unused dentry lru */
	int			s_nr_dentry_unused;	/* # of dentry on lru */
 
	struct block_device	*s_bdev;
	struct mtd_info		*s_mtd;
	/* 相同文件系统类型的超级块链表的节点 */
	struct list_head	s_instances;
	struct quota_info	s_dquot;	/* Diskquota specific options */
 
	int			s_frozen;
	wait_queue_head_t	s_wait_unfrozen;
 
	char s_id[32];				/* Informational name */
 
	void 			*s_fs_info;	/* Filesystem private info */
	fmode_t			s_mode;
 
	/*
	 * The next field is for VFS *only*. No filesystems have any business
	 * even looking at it. You had been warned.
	 */
	struct mutex s_vfs_rename_mutex;	/* Kludge */
 
	/* Granularity of c/m/atime in ns.
	   Cannot be worse than a second */
	u32		   s_time_gran;
 
	/*
	 * Filesystem subtype.  If non-empty the filesystem type field
	 * in /proc/mounts will be "type.subtype"
	 */
	char *s_subtype;
 
	/*
	 * Saved mount options for lazy filesystems using
	 * generic_show_options()
	 */
	char *s_options;
};

dentry:

struct dentry {
	atomic_t d_count;
	unsigned int d_flags;		/* protected by d_lock */
	spinlock_t d_lock;		/* per dentry lock */
	/* 该dentry是否是一个装载点 */
	int d_mounted;
	/* 文件所属的inode */
	struct inode *d_inode;
	/*
	 * The next three fields are touched by __d_lookup.  Place them here so they all fit in a cache line.
	 */
	/* 全局的dentry散列表 */
	struct hlist_node d_hash;	/* lookup hash list */
	/* 父目录的dentry */
	struct dentry *d_parent;	/* parent directory */
	/* 文件的名称,例如对/tmp/a.sh,文件名即为a.sh */
	struct qstr d_name;
	/* 脏的dentry链表的节点 */
	struct list_head d_lru;		/* LRU list */
	/*
	 * d_child and d_rcu can share memory
	 */
	union {
		struct list_head d_child;	/* child of parent list */
	 	struct rcu_head d_rcu;
	} d_u;
	/* 该dentry子目录中的dentry的节点链表 */
	struct list_head d_subdirs;	/* our children */
	/* 硬链接使用几个不同名称表示同一个文件时,用于连接各个dentry */
	struct list_head d_alias;	/* inode alias list */
	unsigned long d_time;		/* used by d_revalidate */
	const struct dentry_operations *d_op;
	/* 所属的super_block */
	struct super_block *d_sb;	/* The root of the dentry tree */
	void *d_fsdata;			/* fs-specific data */
	/* 如果文件名由少量字符组成,在保存在这里,加速访问 */
	unsigned char d_iname[DNAME_INLINE_LEN_MIN];	/* small names */
};

vfsmount:

struct vfsmount {
	/* 全局散列表 */
	struct list_head mnt_hash;
	/* 父文件系统的挂载点 */
	struct vfsmount *mnt_parent;	/* fs we are mounted on */
	/* 父文件系统中该挂载点的dentry */
	struct dentry *mnt_mountpoint;	/* dentry of mountpoint */
	/* 当前文件系统中该挂载点的dentry */
	struct dentry *mnt_root;	/* root of the mounted tree */
	/* 指向super_block */
	struct super_block *mnt_sb;	/* pointer to superblock */
	/* 该挂载点下面的子挂载点列表 */
	struct list_head mnt_mounts;	/* list of children, anchored here */
	/* 父文件系统的子挂载点的列表节点 */
	struct list_head mnt_child;	/* and going through their mnt_child */
	int mnt_flags;
	/* 4 bytes hole on 64bits arches */
	/* 挂载的设备,如/dev/dsk/hda1 */
	const char *mnt_devname;
	/* 虚拟文件系统命名空间中的链表节点 */
	struct list_head mnt_list;
	struct list_head mnt_expire;	/* link in fs-specific expiry list */
	struct list_head mnt_share;	/* circular list of shared mounts */
	struct list_head mnt_slave_list;/* list of slave mounts */
	struct list_head mnt_slave;	/* slave list entry */
	struct vfsmount *mnt_master;	/* slave is on master->mnt_slave_list */
	/* 所在的虚拟文件系统命名空间*/
	struct mnt_namespace *mnt_ns;	/* containing namespace */
	int mnt_id;			/* mount identifier */
	int mnt_group_id;		/* peer group identifier */
	/*
	 * We put mnt_count & mnt_expiry_mark at the end of struct vfsmount
	 * to let these frequently modified fields in a separate cache line
	 * (so that reads of mnt_flags wont ping-pong on SMP machines)
	 */
	atomic_t mnt_count;
	int mnt_expiry_mark;		/* true if marked for expiry */
	int mnt_pinned;
	int mnt_ghosts;
#ifdef CONFIG_SMP
	int *mnt_writers;
#else
	int mnt_writers;
#endif
};

http://www.jnnr.cn/a/131465.html

相关文章

Arduino从零开始(1)——按钮控制LED

0.前言 本文主要介绍Arduino对于开关和条件判断函数的使用。 目录 0.前言 1.介绍 2.按钮控制LED 2.1下拉模式: 2.2上拉模式 3.扩展实验: 1.介绍 前篇介绍了点亮LED,这次案例我们尝试通过一个简单的传感器——按钮,来实现…

cmake 工具 三 add_libary, set_target_properties,link_libary, target_link_libary

一起通过一个例子学一下 add_libary, set_target_properties,link_libary, target_link_libary 四个命令 首先创建如下的文件: 其中 build用于cmake编译,防止大量编译的中间文件污染代码文件夹具体可见cmake 构建工具…

阿里内部目前最完整“Spring全线笔记”,不止是全家桶,太完整了

前言 对于每一位Java开发人员来说,提起Spring定是不陌生的,实际上自Spring框架诞生以来,就备受开发者的青睐,基本上现在的互联网公司都要使用到Spring框架。Spring框架中又包含了SpringMVC、SpringBoot、SpringCloud等&#xff0…

Vue3 - 不再支持 IE11,到底为什么?

前言 咱们的 Vue2 目前仍然支持 IE11,但是到了 Vue3 这里,直接被抛弃了。 IE 浏览器可以说是早期前端开发的噩梦,现在还充斥的大量兼容 IE 浏览器的代码,你可以在网上看到很多类似的信息。 IE 浏览器下 float 布局错乱。IE 浏览器…

0基础学习——了解操作符的那些事(一)

小叮当的任意门操作符分类1. 算数操作符2. 移位操作符二进制(小插曲)左移动操作符右移操作符3. 位操作符& 按位与 &按位或 |按位异或 ^赋值操作符复合赋值符单目操作符操作符分类 在这里我们有:算数操作符 移位操作符 等 今天我们就先…

二十六、设置时序电路初始状态的方法(初始值设置)

----------------------------------------------------------------------------------------------------- 该专栏主要介绍用场效应管设计基本电路,由浅到深,从用场效应管设计最基本的非门、与非门、或非门、与门、或门的设计,到用场效应管设计触发,再到用场效应管设计具…

长时间预测模型DLiner、NLiner模型(论文解读)

前言 今年发布8月份发布的一篇有关长时间序列预测(SOTA)的文章,DLiner、NLine在常用9大数据集(包括ETTh1、ETTh2、ETTm1、ETTm2、Traffic等)上MSE最低,模型单变量、多变量实验数据: 在计算资…

【Google Colab】使用手册、教程;使用 Google Colab 免费使用 python 服务器

Colaboratory 是一个 Google 研究项目,旨在帮助传播机器学习培训和研究成果。它是一个 Jupyter 笔记本环境,不需要进行任何设置就可以使用,并且完全在云端运行。Colaboratory 笔记本存储在 Google 云端硬盘中,并且可以共享&#x…

Java开发高质量代码建议1:三元操作符的类型务必一致

在Java开发中,三元操作符是 if-else 的简化写法,在项目中使用它的地方很多,也非常好用,但是好用又简单的东西并不表示就可以随便用,如下代码: public class Main {public static void main(String[] args) {int i 90…

数据库管理系统

简介 数据库管理系统是一种操纵和管理数据库的大型软件,用于建立、使用和维护数据库,简称 DBMS。它对数据库进行统一的管理和控制,以保证数据库的安全性和完整性。[2] 数据库管理系统是一个能够提供数据录入、修改、查询的数据操作软件&…

Java8 Stream 的这些知识,你了解吗

今天来和大家分享下这个 Stream 。 什么是流呢? 想了好久也不知道怎么表述,感觉很抽象,就是一个很好用的工具🐖。 认真点说辞👇 对 Java集合 的增强,提供了 过滤,计算,转换 等聚合…

阿里SQL又爆神作数据生态:MySQL复制技术与生产实践笔记

前言 在开源国产数据库崛起的今天,这本佳作《数据生态:MySQL复制技术与生产实践》,无疑将为MySQL在各行业的推广和使用做出贡献,这也是像我这样的从商业数据库转到开源数据库的从业者的福音。 MySQL能够成为“最流行的开源数据库”&#xf…

JavaSE之网络编程

目录网络编程网络编程三要素UDPTCP编写UDP的接收端编写UDP的发送端编写TCP的服务端编写TCP的客户端文件上传(多线程)commons-io的使用JunitNIO最后网络编程 编写在不同计算机上进行数据传输的程序。 网络编程场景 网络应用程序、即时通信、网游对战、金融证券、国际贸易、邮…

嵌入式软件调试(Debug)方法

嵌入式软件调试(Debug)方法1 问题定位和分析方法1.1 二分定位法1.2 数据流方法1.3 隔离法1.4 汇编法1.5 ABA法1.6 版本回溯确认法1.7 调试IO法2 调试注意事项3 典型问题类型1 问题定位和分析方法 1.1 二分定位法 方法阐述: 在任务中或者可能…

vue-element-admin动态菜单(后台获取)

vue-element-admin动态菜单(后台获取),此教程面向纯小白攻略,不要嫌我啰嗦,翻到自己需要的地方即可 前提 vue-element-admin官网: vue-element-admin (gitee.io) vue-element-admin页面展示:…

网页信息采集-网页数据采集方法

随着社会不停的发展。人们也是越来越离不开互联网,今天小编就给大家盘点一下免费的网页信息采集,只需要点几下鼠标就能轻松爬取数据,不管是导出excel还是自动发布到网站都支持。详细参考图片一、二、三、四! 企业人员 通过爬取动…

matlab使用NCL提供的colormap

一、自带的colormap matlab默认提供了几个基础的colormap,比如常见的jet和parula matlab里调用colormap的命令是 colormap(jet) jet到底代表什么呢。 可以看到其表示n*3的矩阵,数字介于0-1之间,分别代表红绿蓝。 二、m_map的colormap m_…

『LeetCode|每日一题』---->颜色填充

目录 1.每日一句 2.作者简介 『LeetCode|每日一题』颜色填充 1.每日一题 2.解题思路 2.1 思路分析(DFS) 2.2 核心代码 2.3 完整代码 2.4 运行结果 1.每日一句 我的宇宙为你藏着无数个星球 2.作者简介 🏡个人主页:XiaoXiaoChe…

STM32个人笔记-电源管理

笔记来源于STM32F103VET6,野火指南者,中文参考手册,HAL库开发手册和b站的野火指南者视频。观看过好多次了,但往往理解得不够全面,现记下小笔记,用来回顾。属于个人笔记。 电源监控器 STM32芯片主要通过VDD…

浅析DNS劫持及应对方案

DNS是网络连接中的重要一环,它与路由系统共同组成互联网上的寻址系统,如果DNS遭遇故障,“导航系统”失效,网络连接就会出现无法触达或到达错误地址的情况。由于的DNS重要作用及天生脆弱性,导致DNS自诞生之日起&#xf…
最新文章