Linux驱动开发进阶 - 文件系统
文章目录
- 1、前言
- 2、学习目标
- 3、VFS虚拟文件系统
- 3.1、超级块(Super Block)
- 3.2、dentry
- 3.3、inode
- 3.4、file
- 4、文件系统的挂载
- 5、文件系统的注册
- 5.1、文件系统的注册过程
- 5.1.2、定义文件系统类型
- 5.1.3、注册文件系统
- 5.1.4、注销文件系统
- 5.2、文件系统的挂载与注册的关系
- 6、实现一个虚拟文件系统
- 6.1、定义文件系统结构
- 6.2、完整的驱动程序
- 6.3、测试
1、前言
- 学习参考书籍:李文山的《Linux驱动开发进阶》
- 本文是为了学习上述书籍时,不能囫囵吞枣,才写的。等实际遇到问题了,我也只会去回看原书籍。所以本文不太具备教学功能。
2、学习目标
在Linux中,文件系统可以分为两大类:虚拟文件系统(如sysfs、procfs、devtmpfs)和实际物理存储设备的文件系统(如ext2、ext3、ext4、vfat、fat32)。那Linux如何管理这些文件系统呢?同时本文将在最后编写一个虚拟的文件系统驱动程序。
3、VFS虚拟文件系统
Linux内核的设计哲学非常注重抽象和模块化,这种设计使得系统更加灵活、可扩展且易于维护。Linux为了管理各种类型的文件系统(sysfs、procfs、ext2、ext3、ext4、fat32、…),抽象出了 VFS(Virtual File System Switch,虚拟文件系统切换层)。
一个功能的诞生肯定是为了解决实际问题,那VFS虚拟文件系统的诞生也是为了管理众多不同的文件系统。在Linux的VFS虚拟文件系统中,有四个核心对象是理解和实现文件系统的关键,分别是:超级块(Super Block)、索引节点(Inode)、目录项(Dentry)和文件对象(File)。
3.1、超级块(Super Block)
在Linux文件系统中,许多文件系统本身就存在显示的超级块,如ext2、ext3、ext4。它们在分区的第一个块(或前几个块)中存储了超级块。超级块包含了文件系统的元数据,如总块数、总Inode数、块大小、文件系统状态等。
但不是所有的文件系统都显示存在超级块,如Windows下常用的文件系统,如ntfs、fat32等,它们有自己的结构来存储文件系统的元数据和管理信息,这些结构在功能上类似于超级块,但名称和具体实现不同。
在Linux下,无论该文件系统是否存在超级块,挂载时必须初始化超级块数据结构。
struct super_block {struct list_head s_list; /* Keep this first */dev_t s_dev; /* search index; _not_ kdev_t */unsigned char s_blocksize_bits;unsigned long s_blocksize;loff_t s_maxbytes; /* Max file size */struct file_system_type *s_type;const struct super_operations *s_op;const struct dquot_operations *dq_op;const struct quotactl_ops *s_qcop;const struct export_operations *s_export_op;unsigned long s_flags;unsigned long s_iflags; /* internal SB_I_* flags */unsigned long s_magic;struct dentry *s_root;struct rw_semaphore s_umount;int s_count;atomic_t s_active;
#ifdef CONFIG_SECURITYvoid *s_security;
#endifconst struct xattr_handler **s_xattr;
#ifdef CONFIG_FS_ENCRYPTIONconst struct fscrypt_operations *s_cop;
#ifdef __GENKSYMS__/** Android ABI CRC preservation due to commit 391cceee6d43 ("fscrypt:* stop using keyrings subsystem for fscrypt_master_key") changing this* type. Size is the same, this is a private field.*/struct key *s_master_keys; /* master crypto keys in use */
#elsestruct fscrypt_keyring *s_master_keys; /* master crypto keys in use */
#endif
#endif
#ifdef CONFIG_FS_VERITYconst struct fsverity_operations *s_vop;
#endif
#ifdef CONFIG_UNICODEstruct unicode_map *s_encoding;__u16 s_encoding_flags;
#endifstruct hlist_bl_head s_roots; /* alternate root dentries for NFS */struct list_head s_mounts; /* list of mounts; _not_ for fs use */struct block_device *s_bdev;struct backing_dev_info *s_bdi;struct mtd_info *s_mtd;struct hlist_node s_instances;unsigned int s_quota_types; /* Bitmask of supported quota types */struct quota_info s_dquot; /* Diskquota specific options */struct sb_writers s_writers;/** Keep s_fs_info, s_time_gran, s_fsnotify_mask, and* s_fsnotify_marks together for cache efficiency. They are frequently* accessed and rarely modified.*/void *s_fs_info; /* Filesystem private info *//* Granularity of c/m/atime in ns (cannot be worse than a second) */u32 s_time_gran;/* Time limits for c/m/atime in seconds */time64_t s_time_min;time64_t s_time_max;
#ifdef CONFIG_FSNOTIFY__u32 s_fsnotify_mask;struct fsnotify_mark_connector __rcu *s_fsnotify_marks;
#endifchar s_id[32]; /* Informational name */uuid_t s_uuid; /* UUID */unsigned int s_max_links;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 *//** Filesystem subtype. If non-empty the filesystem type field* in /proc/mounts will be "type.subtype"*/const char *s_subtype;const struct dentry_operations *s_d_op; /* default d_op for dentries *//** Saved pool identifier for cleancache (-1 means none)*/int cleancache_poolid;struct shrinker s_shrink; /* per-sb shrinker handle *//* Number of inodes with nlink == 0 but still referenced */atomic_long_t s_remove_count;/* Pending fsnotify inode refs */atomic_long_t s_fsnotify_inode_refs;/* Being remounted read-only */int s_readonly_remount;/* per-sb errseq_t for reporting writeback errors via syncfs */errseq_t s_wb_err;/* AIO completions deferred from interrupt context */struct workqueue_struct *s_dio_done_wq;struct hlist_head s_pins;/** Owning user namespace and default context in which to* interpret filesystem uids, gids, quotas, device nodes,* xattrs and security labels.*/struct user_namespace *s_user_ns;/** The list_lru structure is essentially just a pointer to a table* of per-node lru lists, each of which has its own spinlock.* There is no need to put them into separate cachelines.*/struct list_lru s_dentry_lru;struct list_lru s_inode_lru;struct rcu_head rcu;struct work_struct destroy_work;struct mutex s_sync_lock; /* sync serialisation lock *//** Indicates how deep in a filesystem stack this SB is*/int s_stack_depth;/* s_inode_list_lock protects s_inodes */spinlock_t s_inode_list_lock ____cacheline_aligned_in_smp;struct list_head s_inodes; /* all inodes */spinlock_t s_inode_wblist_lock;struct list_head s_inodes_wb; /* writeback inodes */ANDROID_KABI_RESERVE(1);ANDROID_KABI_RESERVE(2);ANDROID_KABI_RESERVE(3);ANDROID_KABI_RESERVE(4);
} __randomize_layout;
s_list
:链表结构体,包含prev和next指针,用来连接前驱和后继节点s_dev
:块设备标识符,例如/dev/sda、/dev/nvmes_blocksize_bits
:块大小占用的位数,例如块大小为4096则该值为12s_blocksize
:数据块大小,单位为字节s_maxbytes
:文件的最大长度,单位字节s_type
:文件系统类型s_op
:超级块操作方法,指向具体的文件系统dp_op
:和s_op一样,但用于特定操作方法s_qcop
:和s_op一样,但用于配置磁盘的特定的操作方法s_flags
:文件系统是否安装标志s_magic
:文件系统魔数,每个文件系统都有各自的魔数s_root
:文件系统的根目录文件s_umount
:用于文件系统对文件进行读写同步s_count
:超级块的使用计数s_active
:超级块的引用计数s_security
:用于安全的私有指针s_d_op
:dentry操作方法集合
其中超级块的操作函数结构体struct super_operations内容如下:
struct super_operations {struct inode *(*alloc_inode)(struct super_block *sb);void (*destroy_inode)(struct inode *);void (*free_inode)(struct inode *);void (*dirty_inode) (struct inode *, int flags);int (*write_inode) (struct inode *, struct writeback_control *wbc);int (*drop_inode) (struct inode *);void (*evict_inode) (struct inode *);void (*put_super) (struct super_block *);int (*sync_fs)(struct super_block *sb, int wait);int (*freeze_super) (struct super_block *);int (*freeze_fs) (struct super_block *);int (*thaw_super) (struct super_block *);int (*unfreeze_fs) (struct super_block *);int (*statfs) (struct dentry *, struct kstatfs *);int (*remount_fs) (struct super_block *, int *, char *);void (*umount_begin) (struct super_block *);int (*show_options)(struct seq_file *, struct dentry *);int (*show_devname)(struct seq_file *, struct dentry *);int (*show_path)(struct seq_file *, struct dentry *);int (*show_stats)(struct seq_file *, struct dentry *);
#ifdef CONFIG_QUOTAssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);struct dquot **(*get_dquots)(struct inode *);
#endifint (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);long (*nr_cached_objects)(struct super_block *,struct shrink_control *);long (*free_cached_objects)(struct super_block *,struct shrink_control *);ANDROID_KABI_RESERVE(1);ANDROID_KABI_RESERVE(2);ANDROID_KABI_RESERVE(3);ANDROID_KABI_RESERVE(4);
};
这些操作方法不用全部实现,下面对部分成员进行说明:
alloc_inode
:分配一个inodedestroy_inode
:释放一个硬盘上的inodefree_inode
:释放内存中的inodedirty_inode
:用于将“脏”块标记的方法write_inode
:写入数据到inode中drop_inode
:当最后一个用户释放inode时调用该函数put_super
:释放超级块,卸载文件系统时调用此函数sync_fs
:同步文件系统,当有“脏页”时,更新数据statfs
:查看文件系统信息,例如魔数、文件名最长多少、页大小
3.2、dentry
dentry翻译过来叫“目录项”。在上面的struct super_block结构体里有一个成员s_root就是struct dentry类型:
struct dentry {/* RCU lookup touched fields */unsigned int d_flags; /* protected by d_lock */seqcount_spinlock_t d_seq; /* per dentry seqlock */struct hlist_bl_node d_hash; /* lookup hash list */struct dentry *d_parent; /* parent directory */struct qstr d_name;struct inode *d_inode; /* Where the name belongs to - NULL is* negative */unsigned char d_iname[DNAME_INLINE_LEN]; /* small names *//* Ref lookup also touches following */struct lockref d_lockref; /* per-dentry lock and refcount */const struct dentry_operations *d_op;struct super_block *d_sb; /* The root of the dentry tree */unsigned long d_time; /* used by d_revalidate */void *d_fsdata; /* fs-specific data */union {struct list_head d_lru; /* LRU list */wait_queue_head_t *d_wait; /* in-lookup ones only */};struct list_head d_child; /* child of parent list */struct list_head d_subdirs; /* our children *//** d_alias and d_rcu can share memory*/union {struct hlist_node d_alias; /* inode alias list */struct hlist_bl_node d_in_lookup_hash; /* only for in-lookup ones */struct rcu_head d_rcu;} d_u;ANDROID_KABI_RESERVE(1);ANDROID_KABI_RESERVE(2);
} __randomize_layout;
d_flags
:目录项标志d_hash
:哈希表表项链表d_parent
:父目录d_name
:目录名称d_inode
:指向目录或文件的inoded_iname
:短文件名,当文件名小于DNAME_INLINE_LEN时,文件名存储在数组中d_op
:目录项操作方法集合d_sb
:指向该目录项的超级块指针d_child
:同级目录链表d_subdirs
:子目录项链表
对于某些文件系统(如 ext2/ext3/ext4、XFS等),它们在磁盘上有一个物理的超级块。dentry并不存在于磁盘中,它是vfs虚拟文件系统抽象出来的一个对象,它只存在于内存中。目录项是文件系统中用于将文件名与inode号关联起来的数据结构,作用是快速定位文件路径,减少路径解析的时间。
struct dentry中的d_op操作方法集合如下:
struct dentry_operations {int (*d_revalidate)(struct dentry *, unsigned int);int (*d_weak_revalidate)(struct dentry *, unsigned int);int (*d_hash)(const struct dentry *, struct qstr *);int (*d_compare)(const struct dentry *,unsigned int, const char *, const struct qstr *);int (*d_delete)(const struct dentry *);int (*d_init)(struct dentry *);void (*d_release)(struct dentry *);void (*d_prune)(struct dentry *);void (*d_iput)(struct dentry *, struct inode *);char *(*d_dname)(struct dentry *, char *, int);struct vfsmount *(*d_automount)(struct path *);int (*d_manage)(const struct path *, bool);struct dentry *(*d_real)(struct dentry *, const struct inode *);void (*d_canonical_path)(const struct path *, struct path *);ANDROID_KABI_RESERVE(1);ANDROID_KABI_RESERVE(2);ANDROID_KABI_RESERVE(3);ANDROID_KABI_RESERVE(4);
} ____cacheline_aligned;
- d_revalidate:使一个目录项重新生效
- d_hash:生成一个哈希值,用于VFS向哈希表中加入一个目录项
- d_compare:比较两个目录项名称
- d_delete:删除目录项
- d_init:初始化目录项
- d_release:释放目录项
- d_iput:当目录项的inode为NULL时,此时会调用该函数
- d_dname:设置目录项名称
上面有例举到,在struct dentry中有一个成员是d_inode,这里d_inode就是我们将要介绍的第三个核心对象。
3.3、inode
inode描述了磁盘上的文件信息,将所有文件的索引拿出来组成一个表,即inode表(inode table)。下图展示inode和dentry的关系(图片来自作者李文山的《Linux驱动开发进阶》):
当文件系统需要访问一个文件时,以下步骤会发生:
- 路径解析:
- 从根目录开始,逐级解析路径中的每个目录项,找到目标文件的 dentry。
- 每个目录项在目录文件中存储了文件名和对应的 inode 号。
- 找到 inode:
- 通过 dentry 的
d_inode
字段,找到与该文件名关联的 inode 对象。 - 如果 inode 对象尚未加载到内存中,文件系统会从磁盘读取对应的 inode 数据,并将其加载到内存中的 VFS inode 结构中。
- 通过 dentry 的
- 访问文件数据:
- 通过 inode 中的数据块指针,找到文件数据在磁盘上的实际存储位置。
- 文件系统通过这些数据块指针读取或写入文件的实际数据。
我们通过struct inode来看看inode存储了什么:
struct inode {umode_t i_mode;unsigned short i_opflags;kuid_t i_uid;kgid_t i_gid;unsigned int i_flags;#ifdef CONFIG_FS_POSIX_ACLstruct posix_acl *i_acl;struct posix_acl *i_default_acl;
#endifconst struct inode_operations *i_op;struct super_block *i_sb;struct address_space *i_mapping;#ifdef CONFIG_SECURITYvoid *i_security;
#endif/* Stat data, not accessed from path walking */unsigned long i_ino;/** Filesystems may only read i_nlink directly. They shall use the* following functions for modification:** (set|clear|inc|drop)_nlink* inode_(inc|dec)_link_count*/union {const unsigned int i_nlink;unsigned int __i_nlink;};dev_t i_rdev;loff_t i_size;struct timespec64 i_atime;struct timespec64 i_mtime;struct timespec64 i_ctime;spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */unsigned short i_bytes;u8 i_blkbits;u8 i_write_hint;blkcnt_t i_blocks;#ifdef __NEED_I_SIZE_ORDEREDseqcount_t i_size_seqcount;
#endif/* Misc */unsigned long i_state;struct rw_semaphore i_rwsem;unsigned long dirtied_when; /* jiffies of first dirtying */unsigned long dirtied_time_when;struct hlist_node i_hash;struct list_head i_io_list; /* backing dev IO list */
#ifdef CONFIG_CGROUP_WRITEBACKstruct bdi_writeback *i_wb; /* the associated cgroup wb *//* foreign inode detection, see wbc_detach_inode() */int i_wb_frn_winner;u16 i_wb_frn_avg_time;u16 i_wb_frn_history;
#endifstruct list_head i_lru; /* inode LRU list */struct list_head i_sb_list;struct list_head i_wb_list; /* backing dev writeback list */union {struct hlist_head i_dentry;struct rcu_head i_rcu;};atomic64_t i_version;atomic64_t i_sequence; /* see futex */atomic_t i_count;atomic_t i_dio_count;atomic_t i_writecount;
#if defined(CONFIG_IMA) || defined(CONFIG_FILE_LOCKING)atomic_t i_readcount; /* struct files open RO */
#endifunion {const struct file_operations *i_fop; /* former ->i_op->default_file_ops */void (*free_inode)(struct inode *);};struct file_lock_context *i_flctx;struct address_space i_data;struct list_head i_devices;union {struct pipe_inode_info *i_pipe;struct block_device *i_bdev;struct cdev *i_cdev;char *i_link;unsigned i_dir_seq;};__u32 i_generation;#ifdef CONFIG_FSNOTIFY__u32 i_fsnotify_mask; /* all events this inode cares about */struct fsnotify_mark_connector __rcu *i_fsnotify_marks;
#endif#ifdef CONFIG_FS_ENCRYPTIONstruct fscrypt_info *i_crypt_info;
#endif#ifdef CONFIG_FS_VERITYstruct fsverity_info *i_verity_info;
#endifvoid *i_private; /* fs or device private pointer */ANDROID_KABI_RESERVE(1);ANDROID_KABI_RESERVE(2);
} __randomize_layout;
i_mode
:文件的访问权限i_op
:inode操作函数集合i_sb
:超级块指针,指向文件系统的超级块i_mapping
:地址映射描述i_nlink
:硬连接数目i_rdev
:设备号,在Linux中,所有的设备即是文件i_size
:文件大小,单位字节i_atime
:最后访问时间i_mtime
:最后修改时间i_ctime
:最后改变时间i_blkbits
:块大小表示位数,例如块为4096字节时,此时值为12i_blocks
:文件所占用的block数量i_hash
:哈希表i_dentry
:目录项链表i_fop
:文件操作方法集合i_data
:设备数据地址映射i_devices
:块设备链表i_pipe
:管道文件i_cdev
:字符设备文件i_link
:连接文件i_private
:私有指针,一般用来存放数据块的首地址
其中成员i_op为inode的操作集合:
struct inode_operations {struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);const char * (*get_link) (struct dentry *, struct inode *, struct delayed_call *);int (*permission) (struct inode *, int);struct posix_acl * (*get_acl)(struct inode *, int);int (*readlink) (struct dentry *, char __user *,int);int (*create) (struct inode *,struct dentry *, umode_t, bool);int (*link) (struct dentry *,struct inode *,struct dentry *);int (*unlink) (struct inode *,struct dentry *);int (*symlink) (struct inode *,struct dentry *,const char *);int (*mkdir) (struct inode *,struct dentry *,umode_t);int (*rmdir) (struct inode *,struct dentry *);int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t);int (*rename) (struct inode *, struct dentry *,struct inode *, struct dentry *, unsigned int);int (*setattr) (struct dentry *, struct iattr *);int (*getattr) (const struct path *, struct kstat *, u32, unsigned int);ssize_t (*listxattr) (struct dentry *, char *, size_t);int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start,u64 len);int (*update_time)(struct inode *, struct timespec64 *, int);int (*atomic_open)(struct inode *, struct dentry *,struct file *, unsigned open_flag,umode_t create_mode);int (*tmpfile) (struct inode *, struct dentry *, umode_t);int (*set_acl)(struct inode *, struct posix_acl *, int);ANDROID_KABI_RESERVE(1);ANDROID_KABI_RESERVE(2);ANDROID_KABI_RESERVE(3);ANDROID_KABI_RESERVE(4);
} ____cacheline_aligned;
lookup
:在dentry下查找inodecreate
:在dentry下创建一个inodelink
:为一个indoe创建一个inodeunlink
:删除一个连接文件mkdir
:在dentry下创建一个目录inodermdir
:在dentry下删除一个inodemknod
:创建设备节点update_time
:更新文件时间
3.4、file
file对象是与进程相关的文件描述符的内核表示。它是文件系统和进程之间交互的核心数据结构之一。file对象是Linux内核中用于表示打开文件的结构体。当进程通过系统调用(如 open())打开文件时,内核会创建一个file对象,并将其添加到进程的文件描述符表中。当进程通过系统调用(如 close())关闭文件时,内核会释放对应的file对象。file结构体如下:
struct file {union {struct llist_node fu_llist;struct rcu_head fu_rcuhead;} f_u;struct path f_path;struct inode *f_inode; /* cached value */const struct file_operations *f_op;/** Protects f_ep_links, f_flags.* Must not be taken from IRQ context.*/spinlock_t f_lock;enum rw_hint f_write_hint;atomic_long_t f_count;unsigned int f_flags;fmode_t f_mode;struct mutex f_pos_lock;loff_t f_pos;struct fown_struct f_owner;const struct cred *f_cred;struct file_ra_state f_ra;u64 f_version;
#ifdef CONFIG_SECURITYvoid *f_security;
#endif/* needed for tty driver, and maybe others */void *private_data;#ifdef CONFIG_EPOLL/* Used by fs/eventpoll.c to link all the hooks to this file */struct list_head f_ep_links;struct list_head f_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */struct address_space *f_mapping;errseq_t f_wb_err;errseq_t f_sb_err; /* for syncfs */ANDROID_KABI_RESERVE(1);ANDROID_KABI_RESERVE(2);ANDROID_OEM_DATA(1);
} __randomize_layout__attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
f_path
:文件路径,包含了dentry和vfsmoutf_inode
:执行inode指针f_op
:文件操作方法集合f_count
:文件对象使用计数f_flags
:文件被打开时指定的标志,例如O_RDONLY,O_WRONLYf_mode
:文件读写权限f_pos
:文件当前的偏移量,即当前读写的位置相对于文件开始地址的偏移f_owner
:文件所有者private_data
:私有数据指针,较为常用f_mapping
:文件的页缓冲映射地址
4、文件系统的挂载
当一个磁盘上的分区被挂载时,此时Linux内核会扫描该磁盘上对应的分区的所有索引节点(inode),然后创建struct mount结构体和dentry对象,并将所有的超级块信息保存在struct superblock结构体中,并将所有的inode信息以链表的形式保存在struct inode结构体中,整个过程就建立了从struct mount到inode之间的关系。
5、文件系统的注册
内核维护一个全局链表 file_systems,用于存储所有已注册的文件系统类型。每个文件系统类型通过 file_system_type 结构体注册到这个链表中。
5.1、文件系统的注册过程
5.1.2、定义文件系统类型
文件系统开发者需要定义一个 file_system_type
结构体实例,并实现必要的操作函数(如挂载和卸载函数)。
static struct file_system_type myfs_type = {.owner = THIS_MODULE,.name = "myfs",.mount = myfs_mount,.kill_sb = myfs_kill_sb,.fs_flags = FS_REQUIRES_DEV,
};
5.1.3、注册文件系统
使用 register_filesystem()
函数将文件系统类型注册到内核中。这通常在文件系统模块加载时完成。
static int __init myfs_init(void) {return register_filesystem(&myfs_type);
}
int register_filesystem(struct file_system_type * fs)
{int res = 0;struct file_system_type ** p;if (fs->parameters &&!fs_validate_description(fs->name, fs->parameters))return -EINVAL;BUG_ON(strchr(fs->name, '.'));if (fs->next)return -EBUSY;write_lock(&file_systems_lock);p = find_filesystem(fs->name, strlen(fs->name));if (*p)res = -EBUSY;else*p = fs;write_unlock(&file_systems_lock);return res;
}
5.1.4、注销文件系统
使用 unregister_filesystem()
函数将文件系统从内核中注销。这通常在文件系统模块卸载时完成。
static void __exit myfs_exit(void) {unregister_filesystem(&myfs_type);
}
int unregister_filesystem(struct file_system_type * fs)
{struct file_system_type ** tmp;write_lock(&file_systems_lock);tmp = &file_systems;while (*tmp) {if (fs == *tmp) {*tmp = fs->next;fs->next = NULL;write_unlock(&file_systems_lock);synchronize_rcu();return 0;}tmp = &(*tmp)->next;}write_unlock(&file_systems_lock);return -EINVAL;
}
5.2、文件系统的挂载与注册的关系
- 挂载前的注册:在文件系统被挂载之前,它必须先注册到内核中。只有注册过的文件系统类型才能被挂载。
- 挂载时的识别:当用户尝试挂载一个文件系统时(如通过
mount
命令),内核会遍历file_systems
链表,查找匹配的文件系统类型。 - 动态加载:某些文件系统(如通过模块加载的文件系统)可以在运行时动态注册和注销。例如,
ntfs
文件系统可以通过加载ntfs.ko
模块动态注册。
6、实现一个虚拟文件系统
注:程序源码一样来自李文山的《Linux驱动开发进阶》:https://gitee.com/li-shan-asked/linux-advanced-development-code/blob/master/part1/memfs/memfs.c
但上述源码是基于6.1的kernel,我实验的环境是5.x的kernel,部分接口函数会不一样。所以下面展示的源码是略有修改的。
6.1、定义文件系统结构
一般需要自定义开发文件系统时,可能才需要编写文件系统驱动程序。下图是本次要实现的虚拟文件系统结构(图片来自作者李文山的《Linux驱动开发进阶》):
6.2、完整的驱动程序
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/atomic.h>
#include <asm/uaccess.h>
#include <linux/slab.h>#define MEMFS_INVALID 0xFFFFFFFF
#define MEMFS_FILE_NAME_MAX 16
#define MEMFS_INODE_MAX 128
#define MEMFS_BLK_MAX 128
#define MEMFS_FILE_SIZE_MAX 1024struct memfs_sb {uint32_t blk_size_bit; // 块大小占用的位数uint32_t block_size; // 数据块大小uint32_t magic; // 文件系统魔数uint32_t private; //
};struct memfs_inode {char file_name[MEMFS_FILE_NAME_MAX]; // 文件名称uint32_t mode; // 记录文件或者文件夹的读写权限uint32_t idx; // 记录文件或者文件夹在inode bitmap的索引节点号uint32_t child; // 记录当前文件夹下的目录或者文件的第一个(=MEMFS_INVALID: no child)uint32_t brother; // 记录当前文件或目录的同级目录或者文件(=MEMFS_INVALID: no brother)uint32_t file_size; // uint32_t data; // =MEMFS_INVALID: dir 0~127: file
};struct memfs_block {char data[1024];
};struct memfs_sb g_mf_sb = {.blk_size_bit = 10,.block_size = 1024,.magic = 0x20221001,
};char g_inode_bitmap[16]={0}; // 128bit空间的一个inode bitmap, 每个bit都是一个开关量,标记着inode池中对应的inode是否已使用
char g_block_bitmap[16]={0}; // 128bit空间的一个block bitmapstatic struct memfs_inode *g_mf_inode; // 指向inode池
static struct memfs_block *g_mf_block; // 指向block池static struct inode_operations memfs_inode_ops;void set_bitmap(char *bitmap, uint32_t index)
{*(bitmap + (index>>3)) |= (1<< (index%8));
}void reset_bitmap(char *bitmap, uint32_t index)
{*(bitmap + (index>>3)) &= ~(1<< (index%8));
}uint32_t get_idle_index(char *bitmap)
{uint8_t tmp;for(int i = 0; i < 16; i++) // 循环检查16个字节{if(bitmap[i] != 0xFF) // 如果该字节(8个bit)存在空闲bit{tmp = bitmap[i];for(int j = 0; j<8; j++) // 逐位检查8个bit{if((tmp & 0x1) == 0) // 找到空闲bit{set_bitmap(bitmap, i*8 + j); // 设置改bit为非空闲return i*8 + j; // 返回下标}else {tmp >>= 1; }}}}return MEMFS_INVALID;
}void put_used_index(char *bitmap, uint32_t index)
{reset_bitmap(bitmap, index);
}int memfs_alloc_mem(void)
{/* 分配inode池,大小5120bytes */g_mf_inode = kzalloc(5120, GFP_KERNEL);if(!g_mf_inode)return -1;/* 分配block池,大小128KB */g_mf_block = kzalloc(128*1024, GFP_KERNEL);if(!g_mf_block)return -1;/* 初始化inode的brother和child属性 */for(int i = 0; i < MEMFS_INODE_MAX; i++){g_mf_inode[i].brother = MEMFS_INVALID;g_mf_inode[i].child = MEMFS_INVALID;}return 0;
}void memfs_free_mem(void)
{kfree(g_mf_inode); kfree(g_mf_block);
}static int memfs_readdir(struct file *filp, struct dir_context *ctx)
{struct memfs_inode *mf_inode, *child_inode;if (ctx->pos) return 0;mf_inode = &g_mf_inode[filp->f_path.dentry->d_inode->i_ino]; if (!S_ISDIR(mf_inode->mode)) {return -ENOTDIR;}if(mf_inode->child != MEMFS_INVALID) {child_inode = &g_mf_inode[mf_inode->child];}else {return 0;}while(child_inode->idx != MEMFS_INVALID) {if (!dir_emit(ctx, child_inode->file_name, MEMFS_FILE_NAME_MAX, child_inode->idx, DT_UNKNOWN)) {return 0;}ctx->pos += sizeof(struct memfs_inode);if(child_inode->brother != MEMFS_INVALID)child_inode = &g_mf_inode[child_inode->brother];elsebreak;}return 0;
}ssize_t memfs_read_file(struct file * filp, char __user * buf, size_t len, loff_t *ppos)
{struct memfs_inode *inode;char *buffer;inode = &g_mf_inode[filp->f_path.dentry->d_inode->i_ino]; // 获取实际要操作的inodeif (*ppos >= inode->file_size)return 0;buffer = (char*)&g_mf_block[inode->data]; // 获取block池中对应位置的首地址buffer += *ppos; // len = min((size_t)(inode->file_size - *ppos), len);if (copy_to_user(buf, buffer, len)) // 拷贝到用户态{return -EFAULT;}*ppos += len; // 更新偏移return len;
}ssize_t memfs_write_file(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)
{struct memfs_inode *inode;char *buffer;inode = &g_mf_inode[filp->f_path.dentry->d_inode->i_ino]; // 获取实际要操作的inodeif (*ppos + len > MEMFS_FILE_SIZE_MAX )return 0;buffer = (char*)&g_mf_block[inode->data]; // 获取block池中对应位置的首地址buffer += *ppos; // if (copy_from_user(buffer, buf, len)) {return -EFAULT;}*ppos += len; // 更新偏移inode->file_size = *ppos; // 更新文件大小return len;
}const struct file_operations memfs_file_operations = {.read = memfs_read_file,.write = memfs_write_file,
};const struct file_operations memfs_dir_operations = {.owner = THIS_MODULE,.iterate_shared = memfs_readdir,
};//dir: 当前目录的inode
//dentry:要创建的文件的dentry
//mode:要创建的文件的mode
static int memfs_do_create(struct inode *dir, struct dentry *dentry, umode_t mode)
{struct inode *inode;struct super_block *sb;struct memfs_inode *mf_inode, *p_mf_inode, *tmp_mf_inode;uint32_t idx_inode;/* 获取sb指针 */sb = dir->i_sb;/* 判断是否是目录和常规文件,如果不是,返回错误 */if (!S_ISDIR(mode) && !S_ISREG(mode)) {return -EINVAL;}if (strlen(dentry->d_name.name) > MEMFS_FILE_NAME_MAX) {return -ENAMETOOLONG;}inode = new_inode(sb);if (!inode) {return -ENOMEM;}/* 初始化现在要创建的inode的sb */idx_inode = get_idle_index(g_inode_bitmap); // 获取一个空闲的inode,用于保存当前创建的目录或者文件的inode信息if (idx_inode == MEMFS_INVALID) {return -ENOSPC;}inode->i_sb = sb;inode->i_op = &memfs_inode_ops; // 初始化当前的inode的opsinode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode); // 初始化创建时间和修改时间为当前时间inode->i_ino = idx_inode;mf_inode = &g_mf_inode[idx_inode]; // mf_inode->idx = idx_inode; // mf_inode->mode = mode;/* 接下来都是inode的初始化 */if (S_ISDIR(mode)) // 如果创建的是一个文件,则分配一个block,如果是一个目录则不用分配block{mf_inode->data = MEMFS_INVALID;inode->i_fop = &memfs_dir_operations;} else if (S_ISREG(mode)) {mf_inode->child = MEMFS_INVALID;mf_inode->file_size = 0;inode->i_fop = &memfs_file_operations;mf_inode->data = get_idle_index(g_block_bitmap);if(mf_inode->data == MEMFS_INVALID) {return -ENOSPC;}}p_mf_inode = &g_mf_inode[dir->i_ino]; // 获取当前新创建的父目录节点if(p_mf_inode->child == MEMFS_INVALID) // 当前目录为空目录{ p_mf_inode->child = mf_inode->idx; // 父目录节点的child直接指向现在要创建的inode}else // 非空目录,找到最后一个child{ tmp_mf_inode = &g_mf_inode[p_mf_inode->child]; // 第一个childwhile(tmp_mf_inode->brother != MEMFS_INVALID) {tmp_mf_inode = &g_mf_inode[tmp_mf_inode->brother]; // 找到父目录最后一个child}tmp_mf_inode->brother = mf_inode->idx; // 最后一个child,并设置brother}strcpy(mf_inode->file_name, dentry->d_name.name); // 初始化内核的dentry名称inode_init_owner(inode, dir, mode); // 添加inode到dir中d_add(dentry, inode); // 绑定内核dentry与inodereturn 0;
}static int memfs_inode_mkdir(struct inode *dir, struct dentry *direntry, umode_t mode)
{return memfs_do_create(dir, direntry, S_IFDIR | mode);
}static int memfs_inode_create(struct inode *dir, struct dentry *direntry, umode_t mode,bool excl)
{return memfs_do_create(dir, direntry, mode);
}//parent_inode: 父目录节点
//find_dentry: 要查找的dentry
static struct dentry *memfs_inode_lookup(struct inode *parent_inode, struct dentry *find_dentry, unsigned int flags)
{return NULL;
}//删除空目录
//dentry: 待删除空目录的dentry
int memfs_inode_rmdir(struct inode *dir, struct dentry *dentry)
{uint32_t index = dentry->d_inode->i_ino; // 待删除的空目录的inode下标struct memfs_inode *p_mf_inode, *child_mf_inode, *tmp_mf_inode;if(g_mf_inode[index].child != MEMFS_INVALID) // 如果是非空目录,返回错误return -ENOTEMPTY;p_mf_inode = &g_mf_inode[dir->i_ino]; // 获取当前目录inodechild_mf_inode = &g_mf_inode[p_mf_inode->child]; // 获取父目录inode的第一个childput_used_index(g_inode_bitmap, index);if(p_mf_inode->child == index) // 如果要删除的空目录是父目录的第一个child{if(child_mf_inode->brother == MEMFS_INVALID) // 如果当前node没有brother了p_mf_inode->child = MEMFS_INVALID; // 那么父目录inode也不会有childelsep_mf_inode->child = child_mf_inode->brother; // 否则父目录inode指向当前inode的brother}else // 如果要删除的空目录不是父目录的第一个child{while(child_mf_inode->idx != MEMFS_INVALID) {if(child_mf_inode->brother != MEMFS_INVALID) {tmp_mf_inode = child_mf_inode;child_mf_inode = &g_mf_inode[child_mf_inode->brother]; // 获取brotherif(child_mf_inode->idx == index) // 找到待删除的空目录了{ if(child_mf_inode->brother != MEMFS_INVALID) tmp_mf_inode->brother = child_mf_inode->brother; // 在链表关系中,移除了待删除的这个空目录elsetmp_mf_inode->brother = MEMFS_INVALID;break;}}}}g_mf_inode[index].idx = MEMFS_INVALID;g_mf_inode[index].brother = MEMFS_INVALID;return simple_unlink(dir, dentry);
}//删除文件操作
int memfs_inode_unlink(struct inode *dir, struct dentry *dentry)
{uint32_t index = dentry->d_inode->i_ino;struct memfs_inode *p_mf_inode, *child_mf_inode, *tmp_mf_inode;p_mf_inode = &g_mf_inode[dir->i_ino];//获取第一个childchild_mf_inode = &g_mf_inode[p_mf_inode->child];put_used_index(g_inode_bitmap, index);put_used_index(g_block_bitmap, g_mf_inode[index].data);if(p_mf_inode->child == index) {if(child_mf_inode->brother == MEMFS_INVALID)p_mf_inode->child = MEMFS_INVALID;elsep_mf_inode->child = child_mf_inode->brother;}else {while(child_mf_inode->idx != MEMFS_INVALID) {if(child_mf_inode->brother != MEMFS_INVALID) {tmp_mf_inode = child_mf_inode;child_mf_inode = &g_mf_inode[child_mf_inode->brother];if(child_mf_inode->idx == index) {if(child_mf_inode->brother != MEMFS_INVALID)tmp_mf_inode->brother = child_mf_inode->brother;elsetmp_mf_inode->brother = MEMFS_INVALID;break;}}}}g_mf_inode[index].idx = MEMFS_INVALID;g_mf_inode[index].brother = MEMFS_INVALID;return simple_unlink(dir, dentry);
}static struct inode_operations memfs_inode_ops = {.create = memfs_inode_create, // 在dentry下创建一个inode.lookup = memfs_inode_lookup, // 在dentry下查找inode.mkdir = memfs_inode_mkdir, // 在dentry下创建一个目录inode.rmdir = memfs_inode_rmdir, // 在dentry下删除一个inode.unlink = memfs_inode_unlink, // 删除文件
};/* 每挂载一个块设备,都需要初始化相应的超级块,* 该函数就是初始化超级块。*/
static int memfs_demo_fill_super(struct super_block *sb, void *data, int silent)
{struct inode *root_inode;int mode = S_IFDIR | 0755;root_inode = new_inode(sb); // 新建inode,用于保存根节点root_inode->i_ino = 0; // 设置根节点的编号为0root_inode->i_mode = mode; // 初始化根节点权限root_inode->i_sb = sb; // 设置根节点的超级块root_inode->i_op = &memfs_inode_ops; // 设置根节点的节点操作集合root_inode->i_fop = &memfs_dir_operations; // 设置根节点目录操作集合root_inode->i_atime = root_inode->i_mtime = root_inode->i_ctime = current_time(root_inode); //设置根节点的创建修改时间为当前时间/* 初始化inode池中的第0个inode,第0个inode就是根节点 */strcpy(g_mf_inode[0].file_name, "memfs"); g_mf_inode[0].mode = mode;g_mf_inode[0].idx = 0;g_mf_inode[0].child = MEMFS_INVALID;g_mf_inode[0].brother = MEMFS_INVALID;set_bitmap(g_inode_bitmap, g_mf_inode[0].idx); // 置inode bitmap的第0位为1root_inode->i_private = &g_mf_inode[0]; // 将根节点保存到root_inode的私有数据中/* 初始化磁盘的描述信息(super block) */sb->s_root = d_make_root(root_inode); // 设置上面分配的inode为根目录 sb->s_magic = g_mf_sb.magic; // 文件系统魔数sb->s_blocksize_bits = g_mf_sb.blk_size_bit; // 页大小所占的位数为12sb->s_blocksize = g_mf_sb.blk_size_bit; // 页大小为1024Bytesreturn 0;
}static struct dentry *memfs_demo_mount(struct file_system_type *fs_type, int flags, const char *dev_name, void *data)
{return mount_nodev(fs_type, flags, data, memfs_demo_fill_super);
}static void memfs_kill_sb(struct super_block *sb)
{kill_anon_super(sb);
}/* 文件系统类型 */
static struct file_system_type memfs_type = {.owner = THIS_MODULE,.name = "memfs", // 文件系统名字.mount = memfs_demo_mount, // 挂载文件系统时,所执行的函数.kill_sb = memfs_kill_sb, // 卸载文件系统时,所执行的函数
};static int __init memfs_demo_init(void)
{/* 分配inode池和block池 */if(memfs_alloc_mem()){printk(KERN_ERR "alloc memory failed\n");return -ENOMEM;}/* 注册文件系统 */return register_filesystem(&memfs_type);
}static void __exit memfs_demo_exit(void)
{/* 释放inode池和block池 */memfs_free_mem();/* 卸载文件系统 */unregister_filesystem(&memfs_type);
}module_init(memfs_demo_init);
module_exit(memfs_demo_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("1477153217@qq.com");
MODULE_DESCRIPTION("memory fs demo");
MODULE_ALIAS("fs:memfs");
MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver);
Makefile如下:
KERN_DIR = /home/cohen/sdk/docker/rk356x-sdk/kernel/all:make -C $(KERN_DIR) M=$(PWD) modulesclean:make -C $(KERN_DIR) M=$(PWD) cleanrm -f modules.order obj-m += my_memfs.occflags-y += -std=gnu99
6.3、测试
- 将编译好的驱动程序拷贝到板卡,安装驱动:
insmod my_memfs.ko
此时,已经将一个名为memfs的虚拟文件系统注册进了内核。
- 挂载文件系统:
# -t 表示文件系统的类型
# none 表示没有IO设备
# /mnt 为挂载点
mount -t memfs none /mnt
- 进入/mnt,创建文件:
- 创建目录:
- 删除空目录:
- 删除文件:
相关文章:
Linux驱动开发进阶 - 文件系统
文章目录 1、前言2、学习目标3、VFS虚拟文件系统3.1、超级块(Super Block)3.2、dentry3.3、inode3.4、file 4、文件系统的挂载5、文件系统的注册5.1、文件系统的注册过程5.1.2、定义文件系统类型5.1.3、注册文件系统5.1.4、注销文件系统 5.2、文件系统的…...
Mac:JMeter 下载+安装+环境配置(图文详细讲解)
📌 下载JMeter 下载地址:https://jmeter.apache.org/download_jmeter.cgi 📌 无需安装 Apache官网下载 JMeter 压缩包,无需安装,下载解压后放到自己指定目录下即可。 按我自己的习惯,我会在用户 jane 目…...
蓝桥杯备考:图论之Prim算法
嗯。通过我们前面的学习,我们知道了,一个具有n个顶点的连通图,它的生成树包括n-1个边,如果边多一条就会变成图,少一条就不连通了 接下来我们来学一下把图变成生成树的一个算法 Prim算法,我们从任意一个结…...
Linux 文件操作-标准IO函数3- fread读取、fwrite写入、 fprintf向文件写入格式化数据、fscanf逐行读取格式化数据的验证
目录 1. fread 从文件中读取数据 1.1 读取次数 每次读取字节数 < 原内容字节数 1.2 读取次数 每次读取字节数 > 原内容字节数 2.fwrite 向文件中写入数据 2.1写入字符串验证 2.2写入结构体验证 3. fprintf 将数据写入到指定文件 4. fscanf 从文件中逐行读取内容…...
汽车一键启动系统使用方便,舒适出行,轻松匹配
汽车一键启动系统 系统定义 移动管家汽车一键启动系统是装置在智能汽车上的一部分,是实现简约打火和熄火过程的一个按钮装置。它可以在原车钥匙锁头的位置改装,也能独立面板改装,现在很多高低配置的车辆都可安装。 功能特点 基本功能 启…...
python函数的多种参数使用形式
目录 1. 位置参数(Positional Arguments) 2. 关键字参数(Keyword Arguments) 3. 默认参数(Default Arguments) 4. 可变参数(Variable Positional Arguments) 5. 关键字可变参数&…...
Qt带参数的信号和槽,以及信号与槽的连接方式
1.带参数的信号和槽 Qt的信号与槽也支持带有参数,同时也可以支持重载 此处我们要求,信号函数的参数列表要和对应连接的槽函数参数列表一致 此时信号触发,调用到槽函数的时候,信号函数中的实参就能够被传递到槽函数的形参中 示例…...
深度解析ECharts.js:构建现代化数据可视化的利器
引言:数据可视化的新时代挑战 在数字化转型浪潮中,数据可视化已成为企业决策和用户体验的关键环节。面对海量数据的呈现需求,传统表格已无法满足用户对直观洞察的渴求。作为百度开源的JavaScript可视化库,ECharts.js凭借其强大的…...
Flutter:页面滚动,导航栏背景颜色过渡动画
记录:导航默认透明,页面发生滚动后,导航背景色由0-1,过渡到白色背景。 view import package:ducafe_ui_core/ducafe_ui_core.dart; import package:flutter/material.dart; import package:get/get.dart; import package:redo…...
一文了解ThreadLocal
什么是ThreadLocal? ThreadLocal是每个线程私有的,线程可以把自己的私有数据放到ThreadLocal里面,不用担心其他线程访问到自己ThreadLocal。 通过set()方法将值存入ThreadLocal或者修改值,get()方法取出值,remove()方…...
日常学习开发记录-input组件
实现 1.实现2.inline-table和table-cell实现2.1 表格布局的特性2.2 示例 3.clear清除事件未生效3.1 原因3.2 解决 4. 增加type为text和textarea4.1 rows,autosize的实现 5.拓展-composition事件 1.实现 <template><div class"my-input":class"{is-dis…...
【数据库系统原理】简答题
真题 2024-10 31.数据模型的三大要素是什么? 32.简述关系模型的参照完整性规则。 33.什么是视图?视图的作用是什么? 34.简述两个实体型之间联系的三种形式,并举例说明。 35.什么是数据库备份?MySQL使用什么语句实现备份与恢复数据库中表的数据? 2024-04 31.请解释数据…...
20250319在荣品的PRO-RK3566开发板的buildroot系统下使用集成的QT应用调试串口UART3
stty -F /dev/ttyS3 115200 -echo cat /dev/ttyS3 & echo serialdata > /dev/ttyS3 20250319在荣品的PRO-RK3566开发板的buildroot系统下使用集成的QT应用调试串口UART3 2025/3/19 14:17 缘起:在荣品的PRO-RK3566开发板的buildroot系统下,在命令…...
C#基于MVC模式实现TCP三次握手,附带简易日志管理模块
C#基于MVC模式实现TCP三次握手 1 Model1.1 ServerModel1.2 ClientModel1.3 配置参数模块1.4 日志管理模块1.4.1 数据结构1.4.1 日志管理工具类1.4.1 日志视图展示1.4.1.1 UcLogManage.cs1.4.1.2 UcLogManage.Designer.cs 2 视图(View)2.1 ViewServer2.1.…...
大语言模型的多垂类快速评估与 A/B 测试
简介 行业领先的模型构建企业携手澳鹏(Appen)开展了一项极具挑战性的项目。针对 3 至 6 个大型语言模型(LLM),在广泛的通用领域及复杂专业领域(如医疗保健、法律、金融、编程、数学和汽车行业等࿰…...
一个成功的Git分支模型
本作品原发布账号为【白鸽子中文网】,现转至当前账号【飞翔中文网】。 反思备录(2020/3/5) 这个模型构思于2010年,现已过去10余年,(2010年)那时正处于Git诞生后不久。在这10年间,git-flow(本文中提到的分支模型) 在许多软件队伍里…...
MySQL 在 CentOS 7 上安装的步骤指南
🏝️专栏:Mysql_猫咪-9527的博客-CSDN博客 🌅主页:猫咪-9527-CSDN博客 “欲穷千里目,更上一层楼。会当凌绝顶,一览众山小。” 目录 1. 卸载不需要的环境 2. 获取 MySQL YUM 仓库 3. 安装 MySQL 4. 启动…...
DeepSeek-R1深度解读
deepseek提出了一种通过强化学习(RL)激励大语言模型(LLMs)推理能力的方法,个人认为最让人兴奋的点是:通过RL发现了一个叫“Aha Moment”的现象,这个时刻发生在模型的中间版本中。在这个阶段&…...
吴恩达机器学习笔记复盘(六)梯度下降算法
简介 梯度下降(Gradient Descent)是一种常用的优化算法,广泛应用于机器学习、深度学习等领域,在这里是用于求J(w,b)局部最小值。 我自己觉得这样说有点过于抽象。换个直观点的说法就是,一个人…...
【环境配置】windows下vscode下无法激活conda环境、创建虚拟环境报错
前言 我的本地的系统,绝大部分是使用的ubuntu。去年下半年开始切换成windows,然后windows下使用vscode还需要注意一些小的配置。为了避免反复搜索,这里记录下。 当已经在 windows 下安装了anaconda、vscode,之后的使用有可能存…...
【Linux笔记】动态库与静态库的理解与加载
🔥个人主页🔥:孤寂大仙V 🌈收录专栏🌈:Linux 🌹往期回顾🌹:【Linux笔记】动态库与静态库的制作 🔖流水不争,争的是滔滔不 一、ELF文件二、ELF的形…...
ollama docker设置模型常驻显存
参考: https://github.com/ollama/ollama/issues/5272 https://deepseek.csdn.net/67cfd7c93b685529b708fdee.html 通过-e传入环境变量,ollama运行: docker run -d --gpusall -e OLLAMA_KEEP_ALIVE-1 -v ollama:/root/.ollama -p 11434:114…...
SAP-ABAP:SAP 主数据管理体系深度解析与学习路径介绍
Ⅰ. 主数据体系全景认知 1.1 主数据核心定位 #mermaid-svg-Lf3tZAfcROs5hlN4 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Lf3tZAfcROs5hlN4 .error-icon{fill:#552222;}#mermaid-svg-Lf3tZAfcROs5hlN4 .error-t…...
Redis解决缓存击穿问题——两种方法
目录 引言 解决办法 互斥锁(强一致,性能差) 逻辑过期(高可用,性能优) 设计逻辑过期时间 引言 缓存击穿:给某一个key设置了过期时间,当key过期的时候,恰好这个时间点对…...
FGPA学习(二)实现LED流水灯
目录 一、6个LED灯实现流水灯 (一)实验逻辑 1、时钟和复位信号的处理 2、按键停止信号的处理 3、计数器的计数逻辑 4、LED 状态更新逻辑 (二)代码实现 (三)效果展示 二、vscode插件下载及其模块分…...
【蓝桥杯】每天一题,理解逻辑(4/90)【Leetcode 二进制求和】
题目描述 我们解析一下题目 我们可以理解到两个主要信息 给的是二进制的字符串返回他们的和 我们知道,十进制的加减法需要进位,例如:9716是因为91之后进了一位,二进制也是如此,只不过十进制是逢10进1,二…...
docker利用ollama +Open WebGUI在本地搭建部署一套Deepseek-r1模型
系统:没有限制,可以运行docker就行 磁盘空间:至少预留50GB; 内存:8GB docker版本:4.38.0 桌面版 下载ollama镜像 由于docker镜像地址,网络不太稳定,建议科学上网的一台服务器拉取ollama镜像&am…...
精准git动图拆解
参考原文:精准git动图拆解 该工具可精准识别并提取.git 动图的每一帧,无论是代码运行演示,还是项目流程展示的动图,都能完美处理。 快速格式转换 提取的动图帧会快速转换为 PNG 格式。PNG 无损压缩、支持透明背景&…...
让vscode远程开发也可以图形显示
目录 0. 摘要1. 保存查看2. jupyter内置inline渲染3. jupyter浏览器4. matplot修改后端5. SSH X11转发[※]6. 参考 0. 摘要 vscode登录远程服务器进行开发遇到图形显示需求时,该怎么处理?一般有几种方式: 保存下来查看jupyter内置的inline图…...
996引擎 - 红点系统
996引擎 - 红点系统 总结NPC 红点(TXT红点)Lua 红点1. Red_Point.lua2. UI_Ex.lua参考资料以下内容是在三端 lua 环境下测试的 总结 红点系统分几个部分组成。 M2中设置变量推送。 配置红点表。 Envir\Data\cfg_redpoint.xls 2.1. UI元素中找到ID填写 ids 列。 主界面挂载…...
Springboot List集合的校验方式
pom.xml 引入 <dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate-validator</artifactId><version>6.2.0.Final</version></dependency><dependency><groupId>org.springframework.b…...
基于图像识别的医学影像大数据诊断系统的设计与实现
标题:基于图像识别的医学影像大数据诊断系统的设计与实现 内容:1.摘要 随着医学影像技术的快速发展,医学影像数据量呈爆炸式增长,传统的人工诊断方式在处理海量数据时效率低下且容易出现误差。本研究的目的是设计并实现一个基于图像识别的医学影像大数据…...
黑马node.js教程(nodejs教程)——AJAX-Day01-04.案例_地区查询——查询某个省某个城市所有地区(代码示例)
文章目录 代码示例效果 代码示例 axiosTest.html <!DOCTYPE html> <!-- 文档类型声明,告诉浏览器这是一个HTML5文档 --> <html lang"en"> <!-- HTML根元素,设置文档语言为英语 --><head> <!-- 头部区域&am…...
PySide(PyQt),使用types.MethodType动态定义事件
以PySide(PyQt)的图片项为例,比如一个视窗的场景底图是一个QGraphicsPixmapItem,需要修改它的鼠标滚轮事件,以实现鼠标滚轮缩放显示的功能。为了达到这个目的,可以重新定义一个QGraphicsPixmapItem类,并重写它的wheelE…...
c语言基础编程入门练习题
[编程入门]成绩评定 题目描述 给出一百分制成绩,要求输出成绩等级‘A’、‘B’、‘C’、‘D’、‘E’。 90分以及90分以上为A,80-89分为B,70-79分为C,60-69分为D,60分以下为E。 输入格式 一个整数0-100…...
汽车安全确认等级-中国等保
1、概念解析 网络安全保证等级(Cybersecurity Assurance Level)通常指在不同标准或框架下,根据系统或数据的敏感性、重要性以及潜在风险划分的等级,用于指导组织采取相应的安全防护措施。以下是几个常见的网络安全保证等级体系及…...
Quartus + VScode 实现模块化流水灯
文章目录 一、通过VScode编写Verilog代码二、模块化编程三、代码示例 一、通过VScode编写Verilog代码 1、下载Vscode 2、下载相关插件 搜索Verilog就会弹出有如图所示的插件,下载并安装 3、创建Quartus项目 4、创建完成后点击Tools,选择Options 然后在…...
从两指到三指:Robotiq机器人自适应夹持器技术解析
工业自动化离不开高效工具的支持。Robotiq机器人工具凭借其模块化设计和智能化编程技术,提升了设备的灵活性和操作效率。Robotiq机器人工具精准的传感器和自适应夹持器技术,能够满足多样化的应用需求,为制造业、物流和科研等领域提供可靠的解…...
网络安全应急入门到实战
奇安信:95015网络安全应急响应分析报告(2022-2024年)官网可以下载 https://github.com/Bypass007/Emergency-Response-Notes 应急响应实战笔记 网络安全应急响应技术实战指南 .pdf 常见场景 第4章 勒索病毒网络安全应急响应 第5章 挖矿木…...
Flutter IconButton完全指南:高效使用与性能优化秘籍
目录 一、引言 二、IconButton 的基本用法 三、 进阶技巧 3.1 自定义形状与背景 3.2 带文本的 IconButton(使用 Column 组合) 3.3 自定义交互反馈 3.4 动态图标切换 3.5 组合式按钮(图标 文字) 四、高级应用 4.1 与主题…...
跨国生产制造企业:如何破解远距离数据传输难题?
在全球制造业数字化转型的背景下,跨国生产制造企业的文件传输需求正呈现指数级增长。无论是设计图纸、生产计划、质量控制数据,还是供应链协同信息,跨国文件传输已成为制造业高效运营的核心环节。 然而,制造业文件大数据传输具有文…...
大模型如何赋能安全防御?威胁检测与漏洞挖掘的“AI革命”
🚀 引言:大模型是“安全守护神”还是“双刃剑”? 当黑客用AI生成恶意代码,安全团队也能用大模型“魔法打败魔法”! 划重点:大模型不仅是“生产力工具”,更是安全防御的“智能武器库”࿰…...
uniapp常用组件
写在前面 今天将uniapp中的组件都过了一遍,上手难度不大,但是还是遇到了一些问题: HBuilder实在是太难用,不管是插件生态还是设计之类的,总之就是用的哪哪不顺手虽然打开内置浏览器是挺方便的,但是不知道…...
Oracle OCP认证没落了吗?
Oracle OCP认证没落了吗? Oracle的OCP认证是数据库领域必考的一个认证,但随着国产化的发展,国内很多企业开发了自己的数据库产品,这种情况对很多人造成了错误的认识:OCP被淘汰了吗?不然,从行业需求、技术趋势、认证体…...
洛谷 P3986 斐波那契数列
P3986 斐波那契数列 题目描述 定义一个数列: f ( 0 ) a , f ( 1 ) b , f ( n ) f ( n − 1 ) f ( n − 2 ) f(0) a, f(1) b, f(n) f(n - 1) f(n - 2) f(0)a,f(1)b,f(n)f(n−1)f(n−2) 其中 a, b 均为正整数,n ≥ 2。 问有多少种 (a, b)&…...
使用fastapi部署stable diffusion模型
使用vscode运行stable diffusion模型,每次加载模型都需要10分钟,为算法及prompt调试带来了极大麻烦。使用jupyter解决自然是一个比较好的方案,但如果jupyter由于种种原因不能使用时,fastapi无疑成为了一个很好的选择。 参考github…...
PyTorch使用(3)-张量类型转换
文章目录 张量类型转换1. 张量转换为 numpy 数组1.1. 默认行为:共享内存1.2. 避免内存共享1.2.1. 使用 .copy()1.2.2. 使用 torch.clone() .numpy() 1.3. 处理 GPU 张量1.4. 分离梯度跟踪1.5. 代码示例1.6. 关键注意事项1.7. 总结 2. 标量张量和数字的转换2.1. tor…...
基于FPGA的DDS连续FFT 仿真验证
基于FPGA的 DDS连续FFT 仿真验证 1 摘要 本文聚焦 AMD LogiCORE IP Fast Fourier Transform (FFT) 核心,深入剖析其在 FPGA 设计中的应用。该 FFT 核心基于 Cooley - Tukey 算法,具备丰富特性,如支持多种数据精度、算术类型及灵活的运行时配置。文中详细介绍了其架构选项、…...
【Spring 默认是否管理 Request 和 Session Bean 的生命周期?】
要测试 Spring 默认是否管理 Request 和 Session 作用域的 Bean 的生命周期,可以通过以下步骤实现: 验证 Spring 是否创建了 Bean:检查 Spring 容器是否成功加载并管理了 Request 和 Session 作用域的 Bean。验证 Bean 的生命周期回调方法是…...
Git的基本指令
一、回滚 1.git init 在项目文件夹中打开bash生成一个.git的子目录,产生一个仓库 2.git status 查看当前目录下的所有文件的状态 3.git add . 将该目录下的所有文件提交到暂存区 4.git add 文件名 将该目录下的指定文件提交到暂存区 5.git commit -m 备注信…...