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、前言

  1. 学习参考书籍:李文山的《Linux驱动开发进阶》
  2. 本文是为了学习上述书籍时,不能囫囵吞枣,才写的。等实际遇到问题了,我也只会去回看原书籍。所以本文不太具备教学功能。

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/nvme
  • s_blocksize_bits:块大小占用的位数,例如块大小为4096则该值为12
  • s_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:分配一个inode
  • destroy_inode:释放一个硬盘上的inode
  • free_inode:释放内存中的inode
  • dirty_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:指向目录或文件的inode
  • d_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驱动开发进阶》):

当文件系统需要访问一个文件时,以下步骤会发生:

  1. 路径解析:
    • 从根目录开始,逐级解析路径中的每个目录项,找到目标文件的 dentry。
    • 每个目录项在目录文件中存储了文件名和对应的 inode 号。
  2. 找到 inode:
    • 通过 dentry 的 d_inode 字段,找到与该文件名关联的 inode 对象。
    • 如果 inode 对象尚未加载到内存中,文件系统会从磁盘读取对应的 inode 数据,并将其加载到内存中的 VFS inode 结构中。
  3. 访问文件数据:
    • 通过 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字节时,此时值为12
  • i_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下查找inode
  • create:在dentry下创建一个inode
  • link:为一个indoe创建一个inode
  • unlink:删除一个连接文件
  • mkdir:在dentry下创建一个目录inode
  • rmdir:在dentry下删除一个inode
  • mknod:创建设备节点
  • 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和vfsmout
  • f_inode:执行inode指针
  • f_op:文件操作方法集合
  • f_count:文件对象使用计数
  • f_flags:文件被打开时指定的标志,例如O_RDONLY,O_WRONLY
  • f_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驱动开发进阶》):

图片来自作者李文山的《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、测试

  1. 将编译好的驱动程序拷贝到板卡,安装驱动:
insmod my_memfs.ko

此时,已经将一个名为memfs的虚拟文件系统注册进了内核。

  1. 挂载文件系统:
# -t 表示文件系统的类型
# none 表示没有IO设备
# /mnt 为挂载点
mount -t memfs none /mnt
  1. 进入/mnt,创建文件:

  1. 创建目录:

  1. 删除空目录:

  1. 删除文件:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/36446.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WEB攻防-PHP反序列化-字符串逃逸

目录 前置知识 字符串逃逸-减少 字符串逃逸-增多 前置知识 1.PHP 在反序列化时&#xff0c;语法是以 ; 作为字段的分隔&#xff0c;以 } 作为结尾&#xff0c;在结束符}之后的任何内容不会影响反序列化的后的结果 class people{ public $namelili; public $age20; } var_du…

蓝桥杯真题——洛谷Day13 找规律(修建灌木)、字符串(乘法表)、队列(球票)

目录 找规律 P8781 [蓝桥杯 2022 省 B] 修剪灌木 字符串 P8723 [蓝桥杯 2020 省 AB3] 乘法表 队列 P8641 [蓝桥杯 2016 国 C] 赢球票 找规律 P8781 [蓝桥杯 2022 省 B] 修剪灌木 思路&#xff1a;对某个特定的点来说有向前和向后的情况&#xff0c;即有向前再返回到该位置…

C语言内存函数

一、memcpy使用和模拟实现 函数原型: void * memcpy ( void * destination, const void * source, size_t num ); dest指向目标内存区域的指针&#xff0c;即数据要复制的地方。sour指向内存区域的指针&#xff0c;即数据要复制的地方。num要复制的字节数。 memcpy函数会将s…

Springboot项目打包成war包

1、首先创建一个springboot工程&#xff0c;然后我们改造启动类如&#xff1a; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuil…

【大模型基础_毛玉仁】3.3 思维链

目录 3.3 思维链3.3.1 思维链提示的定义3.3.2 按部就班1&#xff09;Zero-Shot CoT2&#xff09;Auto-CoT 3.3.3 三思后行1&#xff09;思维树&#xff08;Tree of Thoughts, ToT&#xff09;2&#xff09;思维图&#xff08;Graph of Thoughts, GoT&#xff09; 3.3.4 集思广益…

虚拟电商-延迟任务系统的微服务改造(二)

一、微服务注册中心Consul 编写完延迟任务系统的web层接口&#xff0c;也就是说可以基于http协议来访问延迟系统&#xff0c;接下来要将延迟任务改造成一个服务。首要考虑的问题就是服务的注册与发现&#xff0c;服务的注册与发现都离不开服务的注册中心&#xff0c;本项目选取…

场景题:如何设计一个抢红包随机算法

面试官&#xff1a;咱来写个算法题吧 设计一个抢红包的随机算法&#xff0c;比如一个人在群里发了100块钱的红包&#xff0c;群里有10个人一起来抢红包&#xff0c;每人抢到的金额随机分配。 1.所有人抢到的金额之和要等于红包金额&#xff0c;不能多也不能少。 2.每个人至少抢…

Java开发经验——Throwable/Exception异常处理方式

摘要 文章主要探讨了 Java 开发中 Throwable 和 Exception 的异常处理方式。阿里巴巴 Java 开发手册规定&#xff0c;RPC 调用、二方包、动态代理类等场景推荐使用 Throwable&#xff0c;因为这些场景可能会出现类似 NoClassDefFoundError 这样的严重错误&#xff0c;使用 Thr…

[Mysql]创建数据库基础

数据库意义 更加利于管理的东西-数据库&#xff0c;他能有效的管理数据 举例一个生活化的案例说明 如果说&#xff0c;图书馆是保存书籍的&#xff0c;那么数据库技术保存数据的 数据库的简单原理图 Mysql数据库三层结构与本质 数据库管理系统与 mysqld&#xff1a;MySQL 数…

AMBA-CHI协议详解(二十五)

AMBA-CHI协议详解&#xff08;一&#xff09;- Introduction AMBA-CHI协议详解&#xff08;二&#xff09;- Channel fields / Read transactions AMBA-CHI协议详解&#xff08;三&#xff09;- Write transactions AMBA-CHI协议详解&#xff08;四&#xff09;- Other transac…

【RabbitMQ】RabbitMQ的基本架构是什么?包括哪些核心组件?

RabbitMQ基于AMQP协议实现&#xff0c;由多个核心组件组成&#xff0c;确保消息的可靠传递。 Rabbit的架构图&#xff1a; 1.RabbitMQ的基本架构&#xff1a; 1.核心组件&#xff1a; 1.Producer(生产者)&#xff1a; 发送消息到RabbitMQ。 2.Exchange(交换机)&#xff1a;接…

【PCB工艺】基础:电子元器件

电子原理图&#xff08;Schematic Diagram&#xff09;是电路设计的基础&#xff0c;理解电子元器件和集成电路&#xff08;IC&#xff09;的作用&#xff0c;是画好原理图的关键。 本专栏将系统讲解 电子元器件分类、常见 IC、电路设计技巧&#xff0c;帮助你快速掌握电子电路…

Html label标签中的for属性(关联表单控件:将标签与特定的表单元素(如输入框、复选框等)关联起来;提高可用性;无障碍性)

文章目录 示例代码for属性含义完整代码示例 示例代码 <div class"form-group"> <!-- 表单组&#xff0c;包含省份输入框和标签 --><label for"province">省份名称&#xff1a;</label> <!-- 省份输入框的标签 --><input…

S32K144外设实验(二):ADC单通道单次采样(软件触发)

文章目录 1. 概述1.1 理论回顾1.1.1 时钟系统1.1.2 采样通道1.2 实验目的2. 配置与代码编写1. 概述 1.1 理论回顾 S32K144的ADC应该说是特别灵活,笔者采用循序渐进的方式来学习使用这个很重要的外设。 在《入门笔记系列》专栏中对用户手册进行了翻译和解读,这里在回顾一下A…

进程控制~

一.进程控制 1.进程创建 我们可以通过./cmd来运行我们的程序&#xff0c;而我们运行的程序就是bash进程常见的子进程。当然我们也可以通过fork()系统调用来创建进程。 NAME fork - create a child process SYNOPSIS #include <unistd.h> pid_t fork(void…

经历过的IDEA+Maven+JDK一些困惑

注意事项&#xff1a;由于使用过程中是IDEA绑定好另外2个工具&#xff0c;所以报错统一都显示在控制台&#xff0c;但要思考和分辨到底是IDEA本身问题导致的报错&#xff0c;还是maven导致的 标准配置 maven Java Compiler Structure 编辑期 定义&#xff1a;指的是从open pr…

将bin文件烧录到STM32

将bin文件烧录到STM32 CoFlash下载生成hex文件hex2bin使用下载bin到单片机 CoFlash下载 选择需要安装的目录 在Config中可以选择目标芯片的类型 我演示的是 stm32f103c8t6 最小系统板 Adapter&#xff1a;烧录器类型 Max Clock&#xff1a;下载速度 Por&#xff1a;接口类型&am…

硬件基础(5):(2)二极管分类

文章目录 &#x1f4cc; 二极管的分类与详细介绍1. **整流二极管&#xff08;Rectifier Diode&#xff09;**特点&#xff1a;选型依据&#xff1a;补充说明&#xff1a; 2. **快恢复二极管&#xff08;Fast Recovery Diode&#xff09;**特点&#xff1a;选型依据&#xff1a;…

【MySQL】MySQL如何存储元数据?

目录 1.数据字典的作用 2. MySQL 8.0 之前的数据字典 3. MySQL 8.0 及之后的数据字典 4.MySQL 8 中的事务数据字典的特征 5.数据字典的序列化 6. .sdi文件的作用&#xff1a; 7..sdi的存储方式 在 MySQL 中&#xff0c;元数据&#xff08;Metadata&#xff09; 是描述数…

瑞萨RA系列使用JLink RTT Viewer输出调试信息

引言 还在用UART调试程序么?试试JLINK的RTT Viewer吧!不需占用UART端口、低资源暂用、实时性高延时微秒级,这么好的工具还有什么理由不用了! 目录 一、JLink RTT Viewer 简介 二、软件安装 三、工程应用 3.1 SEGGER_RTT驱动包 3.2 手搓宏定义APP_PRINT 3.3 使用APP_…