当前位置: 首页 > news >正文

Linux文件数据写入

结构体

fd

fd也就是文件描述符,用于标识已经打开的文件、管道、socket等。是进程和内核的桥梁,允许进程执行各种文件操作

struct fd {struct file *file;unsigned int flags;
};
file

Linux内核中表示打开文件的结构体,包含了文件操作所需的各种信息和元数据。这是文件系统操作的核心结构之一,允许内核跟踪每个打开的文件及其相关的状态。

struct file {// 用于链接或者引用计数union {// 链表节点struct llist_node	fu_llist;// Read-Copy-Update头struct rcu_head 	fu_rcuhead;} f_u;// 文件路径信息struct path		f_path;// 文件的 inode 结构体,表示文件的具体内容和属性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 */// 用于事件轮询(epoll)系统调用的链表结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;
} __randomize_layout__attribute__((aligned(4)));	/* lest something weird decides that 2 is OK */
inode

inode包含文件的所有元数据,支撑访问控制、文件操作、同步、状态管理和特定文件类型支持

/** Keep mostly read-only and often accessed (especially for* the RCU path lookup and 'stat' data) fields at the beginning* of the 'struct inode'*/
struct inode {// 文件的模式,包括文件类型和文件权限umode_t			i_mode;// 操作标志,标识文件系统特定的操作unsigned short		i_opflags;// 文件所有者的用户 IDkuid_t			i_uid;// 文件所有者的组 IDkgid_t			i_gid;// 文件标志unsigned int		i_flags;#ifdef CONFIG_FS_POSIX_ACLstruct posix_acl	*i_acl;struct posix_acl	*i_default_acl;
#endif// 指向 inode 操作函数的指针const 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 */// inode 号,唯一标识一个文件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*/// 链接数,表示有多少个目录项指向此 inodeunion {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;// 特定结构:hash链表节点、IO列表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;
#endif// 用于缓存回收的LRU列表struct list_head	i_lru;		/* inode LRU list */// 用于管理同一超级块中的 inode的超级块链表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;};// inode版本号atomic64_t		i_version;// 引用计数atomic_t		i_count;// 直接IO计数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 */
} __randomize_layout;

写入——从write()到vfs

write()系统调用在内核的实现为sys_write。

本部分在真正文件系统操作调用之外,只是获取释放文件描述符、更新位置指针、写入前检查等操作

ssize_t ksys_write(unsigned int fd, const char __user *buf, size_t count)
{// 获取文件描述符fdstruct fd f = fdget_pos(fd);ssize_t ret = -EBADF;if (f.file) {// 获取文件当前位置指针loff_t pos, *ppos = file_ppos(f.file);if (ppos) {pos = *ppos;ppos = &pos;}// VFS执行实际写操作ret = vfs_write(f.file, buf, count, ppos);// 更新文件指针位置if (ret >= 0 && ppos)f.file->f_pos = pos;// 释放文件描述符,减少其引用计数fdput_pos(f);}return ret;
}

接着进入vfs,vfs实际也是调用真正文件系统的接口实现

ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{ssize_t ret;// 检查文件是否可写if (!(file->f_mode & FMODE_WRITE))return -EBADF; // 文件不可写,返回错误码 EBADFif (!(file->f_mode & FMODE_CAN_WRITE))return -EINVAL; // 文件模式不支持写操作,返回错误码 EINVAL// 检查用户空间指针是否有效if (unlikely(!access_ok(buf, count)))return -EFAULT; // 用户空间指针无效,返回错误码 EFAULT// 验证写操作范围ret = rw_verify_area(WRITE, file, pos, count);if (!ret) {// 限制最大写入字节数if (count > MAX_RW_COUNT)count = MAX_RW_COUNT;// 开始文件写入file_start_write(file);// 实际执行写操作ret = __vfs_write(file, buf, count, pos);// 如果写入成功,发送文件系统通知并更新写字节数if (ret > 0) {fsnotify_modify(file);add_wchar(current, ret);}// 更新系统调用写计数inc_syscw(current);// 结束文件写入file_end_write(file);}return ret;
}
static ssize_t __vfs_write(struct file *file, const char __user *p,size_t count, loff_t *pos)
{// 首先检查文件操作结构是否有write方法,有直接用if (file->f_op->write)return file->f_op->write(file, p, count, pos);else if (file->f_op->write_iter)return new_sync_write(file, p, count, pos);elsereturn -EINVAL;
}

以下是ext4文件系统实现vfs接口的方法

const struct file_operations ext4_file_operations = {.llseek		= ext4_llseek,.read_iter	= ext4_file_read_iter,.write_iter	= ext4_file_write_iter,.unlocked_ioctl = ext4_ioctl,
#ifdef CONFIG_COMPAT.compat_ioctl	= ext4_compat_ioctl,
#endif.mmap		= ext4_file_mmap,.mmap_supported_flags = MAP_SYNC,.open		= ext4_file_open,.release	= ext4_release_file,.fsync		= ext4_sync_file,.get_unmapped_area = thp_get_unmapped_area,.splice_read	= generic_file_splice_read,.splice_write	= iter_file_splice_write,.fallocate	= ext4_fallocate,
};

ext4 buffered or direct

在Linux中存在几种不同的IO写入方式

  • DAX: 字节级别的操作。要求额外的硬件支持

  • DIO:直接从用户态写入数据到硬盘中,跳过内核缓冲区,减少了上下文切换和数据复制开销

    块级别操作,数据的读写需要是设备的块大小和linux系统的页大小的整数倍

  • BIO:默认标准方式。数据会先从应用程序的地址空间拷贝到 操作系统内核地址空间的页缓存,然后再写入磁盘。根据Linux的延迟写机制,当数据写到操作系统内核地址空间的页缓存就意味write

    缓冲写入操作通常是异步的,数据首先写入页缓存,后续由内核的pdflush守护进程或kworker线程将缓存数据写入磁盘。直接I/O则是同步的,数据直接写入磁盘。

static ssize_t
ext4_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
{// 获取文件关联的inodestruct inode *inode = file_inode(iocb->ki_filp);if (unlikely(ext4_forced_shutdown(EXT4_SB(inode->i_sb))))return -EIO;// 如果文件系统配置支持直接访问,且inode也允许,则进行直接写入
#ifdef CONFIG_FS_DAXif (IS_DAX(inode))return ext4_dax_write_iter(iocb, from);
#endif// 如果IO控制块设置了IOCB_DIRECT,则执行直接IO写入,绕过页缓存if (iocb->ki_flags & IOCB_DIRECT)return ext4_dio_write_iter(iocb, from);// 否则进行缓存写入return ext4_buffered_write_iter(iocb, from);
}
extent

在以下代码中出现了extent,那么extent是什么呢?

extent是一段连续的物理块,表示文件数据在磁盘上的位置和长度。

  • 起始块
  • 物理块
  • 长度

每个文件都有一个与之关联的 extent 树,其根节点存储在 inode 中。树中的节点包含 extent 或指向子节点的指针。

  • 叶子节点:存储实际的 extent 信息(起始块、物理块和长度)

  • 内部节点:存储指向下一级节点的指针。

内联数据

内联数据适用于包含大量小文件场景,将小文件数据直接储存到文件系统的元数据结构中,可以减少空间浪费

孤儿列表

孤儿列表用于跟踪在文件操作中可能会被中途删除或者截断的文件,确保即使在系统崩溃的情况下也能被正确处理

比如,在文件删除中,inode被更新表示文件被删除了,但是系统中途崩溃了,而实际删除工作在后面进行,就会导致这些文件变为孤儿,文件元数据仍然存在,可是文件本身被逻辑删除了

ext4 buffered IO

buffered IO部分主要做了以下事情

  • 锁定inode,防止并发修改,保证page缓存的一致性
  • 检查写入操作是否合法,并进行一些预处理
  • 写入
static ssize_t ext4_buffered_write_iter(struct kiocb *iocb,struct iov_iter *from)
{ssize_t ret;struct inode *inode = file_inode(iocb->ki_filp);// 如果 iocb 的标志中包含 IOCB_NOWAIT,则返回不支持的操作错误if (iocb->ki_flags & IOCB_NOWAIT)return -EOPNOTSUPP;// 加锁以保护 inode 数据结构inode_lock(inode);// 检查写入操作是否合法,并进行一些预处理ret = ext4_write_checks(iocb, from);if (ret <= 0)goto out;// 设置当前进程的 backing_dev_info 为 inode 对应的设备current->backing_dev_info = inode_to_bdi(inode);// 执行通用的写入操作,将数据写入到文件中ret = generic_perform_write(iocb->ki_filp, from, iocb->ki_pos);// 清除当前进程的 backing_dev_infocurrent->backing_dev_info = NULL;out:// 解锁 inodeinode_unlock(inode);// 如果写入操作成功,则更新文件位置,并同步写入数据if (likely(ret > 0)) {iocb->ki_pos += ret;ret = generic_write_sync(iocb, ret);}// 返回写入的字节数或者错误码return ret;
}

写入的执行最后还是回到了VFS。generic_perform_write处理从用户空间到文件的写入数据,方法是遍历数据块、与页面缓存交互以定位或分配页面、将数据复制到这些页面、更新文件的元数据、将页面标记为脏页面以便稍后回写到存储,以及确保整个过程中的数据完整性和错误处理。

ssize_t generic_perform_write(struct file *file,struct iov_iter *i, loff_t pos)
{struct address_space *mapping = file->f_mapping;const struct address_space_operations *a_ops = mapping->a_ops;long status = 0;ssize_t written = 0;unsigned int flags = 0;do {struct page *page;unsigned long offset;	/* Offset into pagecache page */unsigned long bytes;	/* Bytes to write to page */size_t copied;		/* Bytes copied from user */void *fsdata;offset = (pos & (PAGE_SIZE - 1));bytes = min_t(unsigned long, PAGE_SIZE - offset,iov_iter_count(i));again:/** Bring in the user page that we will copy from _first_.* Otherwise there's a nasty deadlock on copying from the* same page as we're writing to, without it being marked* up-to-date.** Not only is this an optimisation, but it is also required* to check that the address is actually valid, when atomic* usercopies are used, below.*/// 错误处理和信号检测if (unlikely(iov_iter_fault_in_readable(i, bytes))) {status = -EFAULT;break;}if (fatal_signal_pending(current)) {status = -EINTR;break;}// 负责将目标文件对应的页加载到内存中,准备好缓冲区以便写入数据。这个函数可能会涉及到文件系统特定的逻辑,例如预分配块或者处理写入锁。status = a_ops->write_begin(file, mapping, pos, bytes, flags,&page, &fsdata);if (unlikely(status < 0))break;// 如果页面映射到用户空间并且可能被写入,则确保缓存一致性,以防止缓存中的旧数据与内存中的新数据冲突。if (mapping_writably_mapped(mapping))flush_dcache_page(page);// 从用户空间缓冲区复制数据到内核页面缓存copied = iov_iter_copy_from_user_atomic(page, i, offset, bytes);flush_dcache_page(page);// 负责处理写操作后的收尾工作,例如更新文件大小、标记页面脏、解除页面锁定等status = a_ops->write_end(file, mapping, pos, bytes, copied,page, fsdata);if (unlikely(status < 0))break;copied = status;cond_resched();iov_iter_advance(i, copied);if (unlikely(copied == 0)) {/** If we were unable to copy any data at all, we must* fall back to a single segment length write.** If we didn't fallback here, we could livelock* because not all segments in the iov can be copied at* once without a pagefault.*/bytes = min_t(unsigned long, PAGE_SIZE - offset,iov_iter_single_seg_count(i));goto again;}pos += copied;written += copied;balance_dirty_pages_ratelimited(mapping);} while (iov_iter_count(i));return written ? written : status;
}
EXPORT_SYMBOL(generic_perform_write);
ext4 write begin

ext4_write_begin处理将数据写入文件的准备工作。确保正确设置数据结构和状态,以便实际的数据写入操作顺利进行

锁定inode、在页面缓存中分配页面以及初始化日志事务以确保文件系统的一致性、确定需要修改的特定块,并在必要时从磁盘读取任何现有数据,以避免覆盖块的未初始化部分。

static int ext4_write_begin(struct file *file, struct address_space *mapping,loff_t pos, unsigned len, unsigned flags,struct page **pagep, void **fsdata)
{struct inode *inode = mapping->host;int ret, needed_blocks;handle_t *handle;int retries = 0;struct page *page;pgoff_t index;unsigned from, to;// 检查文件系统是否被强制关闭if (unlikely(ext4_forced_shutdown(EXT4_SB(inode->i_sb))))return -EIO;// 记录写入操作的跟踪信息trace_ext4_write_begin(inode, pos, len, flags);/** Reserve one block more for addition to orphan list in case* we allocate blocks but write fails for some reason*/// 计算写操作所需的块数,包括一个额外的块用于孤儿列表(orphan list)的添加needed_blocks = ext4_writepage_trans_blocks(inode) + 1;// 计算写入位置的页索引、起始偏移量和结束偏移量index = pos >> PAGE_SHIFT;from = pos & (PAGE_SIZE - 1);to = from + len;// 如果文件可能包含内联数据,尝试写入内联数据if (ext4_test_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA)) {ret = ext4_try_to_write_inline_data(mapping, inode, pos, len,flags, pagep);if (ret < 0)return ret;if (ret == 1)return 0;}// 进行事务处理之前需要先调用 grab_cache_page_write_begin() 获取页面。这样做可以避免在高系统负载或内存压力下造成的长时间等待,同时允许更灵活的内存分配,从而减少潜在的死锁风险。这种策略有助于确保文件系统写入操作的效率和可靠性
retry_grab:// 获取要写入的缓存页。如果获取失败page = grab_cache_page_write_begin(mapping, index, flags);if (!page)return -ENOMEM;unlock_page(page);retry_journal:// 开始一个新的Ext4事务handle = ext4_journal_start(inode, EXT4_HT_WRITE_PAGE, needed_blocks);if (IS_ERR(handle)) {put_page(page);return PTR_ERR(handle);}// 锁定页面并确保页面稳定。如果页面映射发生变化,重新获取页面lock_page(page);if (page->mapping != mapping) {/* The page got truncated from under us */unlock_page(page);put_page(page);ext4_journal_stop(handle);goto retry_grab;}/* In case writeback began while the page was unlocked */wait_for_stable_page(page);// 根据文件系统状态选择写入方法,并执行实际的写入操作
#ifdef CONFIG_FS_ENCRYPTIONif (ext4_should_dioread_nolock(inode))ret = ext4_block_write_begin(page, pos, len,ext4_get_block_unwritten);elseret = ext4_block_write_begin(page, pos, len,ext4_get_block);
#elseif (ext4_should_dioread_nolock(inode))ret = __block_write_begin(page, pos, len,ext4_get_block_unwritten);elseret = __block_write_begin(page, pos, len, ext4_get_block);
#endifif (!ret && ext4_should_journal_data(inode)) {ret = ext4_walk_page_buffers(handle, page_buffers(page),from, to, NULL,do_journal_get_write_access);}// 处理写入过程中出现的错误。如果需要重试分配块,重新开始事务if (ret) {bool extended = (pos + len > inode->i_size) &&!ext4_verity_in_progress(inode);unlock_page(page);/** __block_write_begin may have instantiated a few blocks* outside i_size.  Trim these off again. Don't need* i_size_read because we hold i_mutex.** Add inode to orphan list in case we crash before* truncate finishes*/if (extended && ext4_can_truncate(inode))ext4_orphan_add(handle, inode);ext4_journal_stop(handle);if (extended) {ext4_truncate_failed_write(inode);/** If truncate failed early the inode might* still be on the orphan list; we need to* make sure the inode is removed from the* orphan list in that case.*/if (inode->i_nlink)ext4_orphan_del(NULL, inode);}if (ret == -ENOSPC &&ext4_should_retry_alloc(inode->i_sb, &retries))goto retry_journal;put_page(page);return ret;}*pagep = page;return ret;
}

block_write_begin通过映射页中必要的块来准备要写的页。遍历每个块,确保将其映射并标记为最新的,如果有必要,还会对需要从磁盘读取的块发起读取,以避免覆盖未初始化的数据

// 	•	page: 需要写入数据的页面。
//	•	pos: 写操作的起始位置。
//	•	len: 写入数据的长度。
//	•	get_block: 用于映射逻辑块号到物理块号的回调函数。
//	•	iomap: I/O 映射结构体,用于描述 I/O 操作。
int __block_write_begin(struct page *page, loff_t pos, unsigned len,get_block_t *get_block)
{return __block_write_begin_int(page, pos, len, get_block, NULL);
}
EXPORT_SYMBOL(__block_write_begin);int __block_write_begin_int(struct page *page, loff_t pos, unsigned len,get_block_t *get_block, struct iomap *iomap)
{unsigned from = pos & (PAGE_SIZE - 1);unsigned to = from + len;struct inode *inode = page->mapping->host;unsigned block_start, block_end;sector_t block;int err = 0;unsigned blocksize, bbits;struct buffer_head *bh, *head, *wait[2], **wait_bh=wait;BUG_ON(!PageLocked(page));BUG_ON(from > PAGE_SIZE);BUG_ON(to > PAGE_SIZE);BUG_ON(from > to);// 为页面分配缓冲头,并设置缓冲区大小和块大小的位数head = create_page_buffers(page, inode, 0);blocksize = head->b_size;bbits = block_size_bits(blocksize);block = (sector_t)page->index << (PAGE_SHIFT - bbits);// 遍历页面的每个缓冲头(buffer head),处理每个块for(bh = head, block_start = 0; bh != head || !block_start;block++, block_start=block_end, bh = bh->b_this_page) {block_end = block_start + blocksize;if (block_end <= from || block_start >= to) {if (PageUptodate(page)) {if (!buffer_uptodate(bh))set_buffer_uptodate(bh);}continue;}if (buffer_new(bh))clear_buffer_new(bh);// 如果缓冲区尚未映射,则调用get_block或者iomap_to_bh进行块映射if (!buffer_mapped(bh)) {WARN_ON(bh->b_size != blocksize);if (get_block) {err = get_block(inode, block, bh, 1);if (err)break;} else {iomap_to_bh(inode, block, bh, iomap);}if (buffer_new(bh)) {clean_bdev_bh_alias(bh);if (PageUptodate(page)) {clear_buffer_new(bh);set_buffer_uptodate(bh);mark_buffer_dirty(bh);continue;}if (block_end > to || block_start < from)zero_user_segments(page,to, block_end,block_start, from);continue;}}if (PageUptodate(page)) {if (!buffer_uptodate(bh))set_buffer_uptodate(bh);continue; }// 如果缓冲区未更新且未延迟,也未写入,则从磁盘读取块数据if (!buffer_uptodate(bh) && !buffer_delay(bh) &&!buffer_unwritten(bh) &&(block_start < from || block_end > to)) {ll_rw_block(REQ_OP_READ, 0, 1, &bh);*wait_bh++=bh;}}/** If we issued read requests - let them complete.*/// 等待所有读取操作完成while(wait_bh > wait) {wait_on_buffer(*--wait_bh);if (!buffer_uptodate(*wait_bh))err = -EIO;}if (unlikely(err))page_zero_new_buffers(page, from, to);return err;
}
ext4 write end

ext4_write_end对页的数据写入做收尾工作。

如果写入扩展了文件,则更新inode大小,必要时将inode标记为脏的,并处理任何清理,包括处理日志事务,如果写入部分失败,则截断超出新文件大小的未初始化块。保证写操作后数据的完整性和一致性。

/** We need to pick up the new inode size which generic_commit_write gave us* `file' can be NULL - eg, when called from page_symlink().** ext4 never places buffers on inode->i_mapping->private_list.  metadata* buffers are managed internally.*/
static int ext4_write_end(struct file *file,struct address_space *mapping,loff_t pos, unsigned len, unsigned copied,struct page *page, void *fsdata)
{handle_t *handle = ext4_journal_current_handle();struct inode *inode = mapping->host;loff_t old_size = inode->i_size;int ret = 0, ret2;int i_size_changed = 0;int inline_data = ext4_has_inline_data(inode);bool verity = ext4_verity_in_progress(inode);trace_ext4_write_end(inode, pos, len, copied);// 包含内联数据,处理内联数据写入,否则进行块写入if (inline_data) {ret = ext4_write_inline_data_end(inode, pos, len,copied, page);if (ret < 0) {unlock_page(page);put_page(page);goto errout;}copied = ret;} elsecopied = block_write_end(file, mapping, pos,len, copied, page, fsdata);/** it's important to update i_size while still holding page lock:* page writeout could otherwise come in and zero beyond i_size.** If FS_IOC_ENABLE_VERITY is running on this inode, then Merkle tree* blocks are being written past EOF, so skip the i_size update.*/if (!verity)i_size_changed = ext4_update_inode_size(inode, pos + copied);unlock_page(page);put_page(page);// 如果旧文件大小小于写入位置,且没有正在进行的文件校验扩展操作,更新页面缓存的文件大小if (old_size < pos && !verity)pagecache_isize_extended(inode, old_size, pos);/** Don't mark the inode dirty under page lock. First, it unnecessarily* makes the holding time of page lock longer. Second, it forces lock* ordering of page lock and transaction start for journaling* filesystems.*/// 如果文件大小发生变化或包含内联数据,标记inode为脏if (i_size_changed || inline_data)ext4_mark_inode_dirty(handle, inode);// 如果写入位置加上写入长度超过了文件大小,并且文件系统允许截断,添加inode到孤儿列表if (pos + len > inode->i_size && !verity && ext4_can_truncate(inode))/* if we have allocated more blocks and copied* less. We will have blocks allocated outside* inode->i_size. So truncate them*/ext4_orphan_add(handle, inode);
errout:ret2 = ext4_journal_stop(handle);if (!ret)ret = ret2;if (pos + len > inode->i_size && !verity) {ext4_truncate_failed_write(inode);/** If truncate failed early the inode might still be* on the orphan list; we need to make sure the inode* is removed from the orphan list in that case.*/if (inode->i_nlink)ext4_orphan_del(NULL, inode);}return ret ? ret : copied;
}

ext4 direct IO

static ssize_t ext4_dio_write_iter(struct kiocb *iocb, struct iov_iter *from)
{ssize_t ret;size_t count;loff_t offset;handle_t *handle;struct inode *inode = file_inode(iocb->ki_filp);bool extend = false, overwrite = false, unaligned_aio = false;// 锁定inode。if (iocb->ki_flags & IOCB_NOWAIT) {if (!inode_trylock(inode))return -EAGAIN;} else {inode_lock(inode);}// 检查是否支持直接IOif (!ext4_dio_supported(inode)) {inode_unlock(inode);/** Fallback to buffered I/O if the inode does not support* direct I/O.*/return ext4_buffered_write_iter(iocb, from);}// 写入前检查ret = ext4_write_checks(iocb, from);if (ret <= 0) {inode_unlock(inode);return ret;}// 同步未对齐的异步direct IO,防止数据损坏offset = iocb->ki_pos;count = iov_iter_count(from);if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS) &&!is_sync_kiocb(iocb) && ext4_unaligned_aio(inode, from, offset)) {unaligned_aio = true;inode_dio_wait(inode);}/** Determine whether the I/O will overwrite allocated and initialized* blocks. If so, check to see whether it is possible to take the* dioread_nolock path.*/// 如果IO对齐且I/O覆盖已分配和初始化的块且 inode 支持无锁直接读取,则设置 overwrite 并降级写锁if (!unaligned_aio && ext4_overwrite_io(inode, offset, count) &&ext4_should_dioread_nolock(inode)) {overwrite = true;downgrade_write(&inode->i_rwsem);}// 检查写操作的结束(offset + count)是否超过了inode的当前磁盘大小,启动一个日志句柄来安全地管理对inode的更改,将该inode添加到孤立列表中,以处理写操作期间可能发生的崩溃,并设置一个标志(extend),表示将扩展inode的大小。然后停止日志记录句柄if (offset + count > EXT4_I(inode)->i_disksize) {handle = ext4_journal_start(inode, EXT4_HT_INODE, 2);if (IS_ERR(handle)) {ret = PTR_ERR(handle);goto out;}ret = ext4_orphan_add(handle, inode);if (ret) {ext4_journal_stop(handle);goto out;}extend = true;ext4_journal_stop(handle);}// 执行直接 I/O 写入ret = iomap_dio_rw(iocb, from, &ext4_iomap_ops, &ext4_dio_write_ops,is_sync_kiocb(iocb) || unaligned_aio || extend);// 如果是扩展操作,需要再次启动一个事务并更新磁盘大小if (extend)ret = ext4_handle_inode_extension(inode, offset, ret, count);out:if (overwrite)inode_unlock_shared(inode);elseinode_unlock(inode);if (ret >= 0 && iov_iter_count(from)) {ssize_t err;loff_t endbyte;// 回退到缓冲IOoffset = iocb->ki_pos;err = ext4_buffered_write_iter(iocb, from);if (err < 0)return err;// 在当前 I/O 操作覆盖的范围内,确保页面缓存中的页面被写入磁盘并失效(即使缓存无效)。这是为了在必要时回退到缓冲 I/O 时,尽量保持直接 I/O 的语义ret += err;endbyte = offset + err - 1;err = filemap_write_and_wait_range(iocb->ki_filp->f_mapping,offset, endbyte);if (!err)invalidate_mapping_pages(iocb->ki_filp->f_mapping,offset >> PAGE_SHIFT,endbyte >> PAGE_SHIFT);}return ret;
}

ext4 BIO与DIO代码有感

ext4 BIO(Buffered IO)与DIO(Direct IO)

  • ext4 BIO与DIO都尝试对inode进行锁定。不同的是DIO还允许无等待,也就是在锁已经被获取的情况下,直接返回

  • BIO经过内核page缓存,而DIO则直接从用户空间写入到设备

  • DIO还确保写入操作覆盖范围内的缓存页面被写入磁盘并失效,以保证直接 I/O 语义,和未对齐的异步直接 I/O 写入,防止数据损坏

Ref

  1. https://elixir.bootlin.com/linux/v5.5-rc2/source

相关文章:

Linux文件数据写入

结构体 fd fd也就是文件描述符&#xff0c;用于标识已经打开的文件、管道、socket等。是进程和内核的桥梁&#xff0c;允许进程执行各种文件操作 struct fd {struct file *file;unsigned int flags; };file Linux内核中表示打开文件的结构体&#xff0c;包含了文件操作所需…...

vue2 中如何使用 vuedraggable 库实现拖拽功能

1.通过 npm 或 yarn 安装 vuedraggable 库 npm install vuedraggableyarn add vuedraggable 2. 引入组件内部使用&#xff0c;以下代码是一个Demo&#xff0c;可直接复制粘贴演示 注意&#xff1a;因项目使用了 vant&#xff0c;需要安装 vant 才能正常运行 <template&g…...

0基础学C++ | 第13天 | 基础知识 | 类 | 对象

目录 前言 封装 封装的意义 struct 和 class 的区别 成员属性设置为私有 前言 众所周知&#xff0c; C是一个面向对象的编程语言&#xff08;面向对象的C语言的特点就是&#xff1a;封装、继、 多态&#xff09;&#xff0c;它与面向过程的C语言不通&#xff0c;对面向…...

Java | Leetcode Java题解之第212题单词搜索II

题目&#xff1a; 题解&#xff1a; class Solution {int[][] dirs {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};public List<String> findWords(char[][] board, String[] words) {Trie trie new Trie();for (String word : words) {trie.insert(word);}Set<String> a…...

Flink面试题总结

一、简单介绍一下 Flink Apache Flink 是一个实时计算框架和分布式处理引擎&#xff0c;用于在无边界和有边界数据流上进行有状态的计算 二、Flink集群有哪些角色&#xff1f;各自有什么作用&#xff1f;&#xff08;flink架构&#xff09; --JobManager&#xff1a; JobManag…...

人工智能与云计算

项目要求 一个简单的集群。您需要在此项目中创建计算机集群。这些机器是 docker 容器。集群管理器是一个 Python 程序。群集的状态将写入文件。 希望通过这个 Python 文件,首先它能够通过获取输入来得到要创建的集群中包含的容器数量,并与用户进行交互(用户可以执行此集群…...

9.(vue3.x+vite)修改el-input,el-data-picker样式

效果预览 二:相关代码 <template><div style="padding: 50px"><el-input placeholder="请输入模型名称" style="width: 260px" /><br /...

java反射和注解

反射 获取class对象的三种方法 ①&#xff1a;Class.forName("全类名"); ②&#xff1a;类名.class ③&#xff1a;对象.getclass(); 代码样例 package com.ithema;public class Main {public static void main(String[] args) throws ClassNotFoundException {//第…...

react_后台管理_项目

目录 1.运行项目 2. 项目结构 ①项目顶部导航栏 ②项目左侧导航栏 ③主页面-路由切换区 本项目使用的是 reacttsscss 技术栈。 1.运行项目 在当前页面顶部下载本项目&#xff0c;解压后使用编辑器打开&#xff0c;然后再终端输入命令&#xff1a; npm i 下载依赖后&am…...

【C语言】使用C语言编写并使用gcc编译动态链接库

【C语言】使用C 语言编写并使用 gcc 编译动态链接库 1.背景2.使用C编写代码3.使用gcc编译代码1.背景 在windows下开发很多程序接口被封装到动态链接库供其它开发者使用。 本博客使用C语言编写并使用gcc 编译 一个动态链接库文件FpdSys.dll; 然后使用C/C++/C#/Python去调用动态…...

使用supportFragmentManager管理多个fragment切换

android studio创建的项目就没有一个简单点的框架&#xff0c;生成的代码都是繁琐而复杂&#xff0c;并且不实用。 国内的页面一般都是TAB页面的比较多&#xff0c;老外更喜欢侧边菜单。 如果我们使用一个activity来创建程序&#xff0c;来用占位符管理多个fragment切换&…...

开源模型应用落地-FastAPI-助力模型交互-WebSocket篇(六)

一、前言 使用 FastAPI 可以帮助我们更简单高效地部署 AI 交互业务。FastAPI 提供了快速构建 API 的能力,开发者可以轻松地定义模型需要的输入和输出格式,并编写好相应的业务逻辑。 FastAPI 的异步高性能架构,可以有效支持大量并发的预测请求,为用户提供流畅的交互体验。此外,F…...

独立开发者系列(17)——MYSQL的常见异常整理

虽然安装MYSQL到本地很简单&#xff0c;但是数据库报错还是经常出现&#xff0c;这个时候&#xff0c;需要我们进行逐步检查与修复。作为我们最常用的开发软件&#xff0c;无论切换php/go/python/node/java&#xff0c;数据库的身影都少不了&#xff0c;对于我们储存数据而言&a…...

【ajax实战02】数据管理网站—验证码登录

一&#xff1a;数据提交&#xff08;提交手机验证码&#xff09; 核心思路整理 利用form-serialize插件&#xff0c;收集对象形式的表单数据后&#xff0c;一并提交给服务器。后得到返回值&#xff0c;进一步操作 基地址&#xff1a; axios.defaults.baseURL http://geek.…...

人工智能在反无人机中的应用介绍

人工智能技术在无人机的发展中扮演着至关重要的角色&#xff0c;这一作用在反无人机技术领域同样显著。随着无人机技术的发展&#xff0c;飞行器具备了微小尺寸、高速机动性&#xff0c;以及可能采用的隐蔽或低空飞行轨迹等特性。这些特性使得传统的人工监视和控制手段面临着重…...

【力扣 - 每日一题】3115. 质数的最大距离(一次遍历、头尾遍历、空间换时间、埃式筛、欧拉筛、打表)Golang实现

原题链接 题目描述 给你一个整数数组 nums。 返回两个&#xff08;不一定不同的&#xff09;质数在 nums 中 下标 的 最大距离。 示例 1&#xff1a; 输入&#xff1a; nums [4,2,9,5,3] 输出&#xff1a; 3 解释&#xff1a; nums[1]、nums[3] 和 nums[4] 是质数。因此答…...

【Gin】项目搭建 一

环境准备 首先确保自己电脑安装了Golang 开始项目 1、初始化项目 mkdir gin-hello; # 创建文件夹 cd gin-hello; # 需要到刚创建的文件夹里操作 go mod init goserver; # 初始化项目&#xff0c;项目名称&#xff1a;goserver go get -u github.com/gin-gonic/gin; # 下载…...

C++ 和C#的差别

首先把眼睛瞪大&#xff0c;然后憋住一口气&#xff0c;读下去&#xff1a; 1、CPP 就是C plus plus的缩写&#xff0c;中国大陆的程序员圈子中通常被读做"C加加"&#xff0c;而西方的程序员通常读做"C plus plus"&#xff0c;它是一种使用非常广泛的计算…...

Vue2组件传值(通信)的方式

目录 1.父传后代 ( 后代拿到了父的数据 )1. 父组件引入子组件&#xff0c;绑定数据2. 子组件直接使用父组件的数据3. 依赖注入(使用 provide/inject API)1.在祖先组件中使用 provide2.在后代组件中使用 inject 2.后代传父 &#xff08;父拿到了后代的数据&#xff09;1. 子组件…...

【数据结构 - 时间复杂度和空间复杂度】

文章目录 <center>时间复杂度和空间复杂度算法的复杂度时间复杂度大O的渐进表示法常见时间复杂度计算举例 空间复杂度实例 时间复杂度和空间复杂度 算法的复杂度 算法在编写成可执行程序后&#xff0c;运行时需要耗费时间资源和空间(内存)资源 。因此衡量一个算法的好坏&…...

uniapp 对接腾讯云IM群组成员管理(增删改查)

UniApp 实战&#xff1a;腾讯云IM群组成员管理&#xff08;增删改查&#xff09; 一、前言 在社交类App开发中&#xff0c;群组成员管理是核心功能之一。本文将基于UniApp框架&#xff0c;结合腾讯云IM SDK&#xff0c;详细讲解如何实现群组成员的增删改查全流程。 权限校验…...

Python爬虫实战:研究MechanicalSoup库相关技术

一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...

Python|GIF 解析与构建(5):手搓截屏和帧率控制

目录 Python&#xff5c;GIF 解析与构建&#xff08;5&#xff09;&#xff1a;手搓截屏和帧率控制 一、引言 二、技术实现&#xff1a;手搓截屏模块 2.1 核心原理 2.2 代码解析&#xff1a;ScreenshotData类 2.2.1 截图函数&#xff1a;capture_screen 三、技术实现&…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】

微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来&#xff0c;Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂

蛋白质结合剂&#xff08;如抗体、抑制肽&#xff09;在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上&#xff0c;高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术&#xff0c;但这类方法普遍面临资源消耗巨大、研发周期冗长…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练

前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1)&#xff1a;从基础到实战的深度解析-CSDN博客&#xff0c;但实际面试中&#xff0c;企业更关注候选人对复杂场景的应对能力&#xff08;如多设备并发扫描、低功耗与高发现率的平衡&#xff09;和前沿技术的…...

【配置 YOLOX 用于按目录分类的图片数据集】

现在的图标点选越来越多&#xff0c;如何一步解决&#xff0c;采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集&#xff08;每个目录代表一个类别&#xff0c;目录下是该类别的所有图片&#xff09;&#xff0c;你需要进行以下配置步骤&#x…...

2025盘古石杯决赛【手机取证】

前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来&#xff0c;实在找不到&#xff0c;希望有大佬教一下我。 还有就会议时间&#xff0c;我感觉不是图片时间&#xff0c;因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

Device Mapper 机制

Device Mapper 机制详解 Device Mapper&#xff08;简称 DM&#xff09;是 Linux 内核中的一套通用块设备映射框架&#xff0c;为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程&#xff0c;并配以详细的…...