Linux read的核心函数generic_file_buffered_read

Linux 文件系统解析(四)IO模式 文件系统是IO流程的起点,为了满足应用程序对IO操作的各种需求,在文件系统层设计了多种IO模式,上一篇介绍的bufferIO是最常见的一种,本篇来梳理一下其他IO模式,它们的作用,流程以及是如何实现的。 DirectIO DAX 异步IO aio iouring 多路复用 select poll epoll mmap ... 阅读详情

内核:5.9.0

流程图

 generic_file_buffered_read一种调用路径(cat某个文件触发):

#0  ondemand_readahead (mapping=0xffff888005c61340, ra=0xffff888005927598, filp=0xffff888005927500, hit_readahead_marker=false, index=0, req_size=16) at mm/readahead.c:445
#1  0xffffffff812eeea1 in page_cache_sync_readahead (req_count=<optimized out>, index=<optimized out>, filp=<optimized out>, ra=<optimized out>, mapping=<optimized out>) at mm/readahead.c:585
#2  page_cache_sync_readahead (mapping=<optimized out>, ra=0xffff888005927598, filp=0xffff888005927500, index=<optimized out>, req_count=16) at mm/readahead.c:567
#3  0xffffffff812dcae7 in generic_file_buffered_read (iocb=0xffffc90000033cc0, iter=<optimized out>, written=0) at mm/filemap.c:2199
#4  0xffffffff812dd8ed in generic_file_read_iter (iocb=0xffffc90000033cc0, iter=0xffffc90000033c98) at mm/filemap.c:2507
#5  0xffffffff814c7fc9 in ext4_file_read_iter (to=<optimized out>, iocb=<optimized out>) at fs/ext4/file.c:131
#6  ext4_file_read_iter (iocb=0xffffc90000033cc0, to=0xffffc90000033c98) at fs/ext4/file.c:114
#7  0xffffffff81405c0f in call_read_iter (file=<optimized out>, iter=<optimized out>, kio=<optimized out>) at ./include/linux/fs.h:1876
#8  generic_file_splice_read (in=0xffff888005927500, ppos=0xffffc90000033da8, pipe=<optimized out>, len=<optimized out>, flags=<optimized out>) at fs/splice.c:312
#9  0xffffffff81407b51 in do_splice_to (in=0xffff888005927500, ppos=0xffffc90000033da8, pipe=0xffff8880058fb6c0, len=65536, flags=<optimized out>) at fs/splice.c:890
#10 0xffffffff81407cab in splice_direct_to_actor (in=<optimized out>, sd=0xffffc90000033e00, actor=<optimized out>) at fs/splice.c:970
#11 0xffffffff81408012 in do_splice_direct (in=<optimized out>, ppos=0xffffc90000033ea8, out=0xffff888005927400, opos=0xffffc90000033eb0, len=16777216, flags=<optimized out>) at fs/splice.c:1079
#12 0xffffffff813ae9b1 in do_sendfile (out_fd=<optimized out>, in_fd=<optimized out>, ppos=0x0 <fixed_percpu_data>, count=<optimized out>, max=<optimized out>) at fs/read_write.c:1548
#13 0xffffffff813af30b in __do_sys_sendfile64 (count=<optimized out>, offset=<optimized out>, in_fd=<optimized out>, out_fd=<optimized out>) at fs/read_write.c:1609
#14 __se_sys_sendfile64 (count=<optimized out>, offset=<optimized out>, in_fd=<optimized out>, out_fd=<optimized out>) at fs/read_write.c:1595

generic_file_buffered_read核心逻辑:

  • 尝试从page cache(address_space数据结构)中查找,如果命中返回。
  • 如果未在page cache中,readpage从磁盘读取数据到page cache中。
  • 按需(不是所有场景下才会预读)预读(readahead),提升IO性能。

/**
 * generic_file_buffered_read - generic file read routine
 * @iocb:	the iocb to read
 * @iter:	data destination
 * @written:	already copied
 *
 * This is a generic file read routine, and uses the
 * mapping->a_ops->readpage() function for the actual low-level stuff.
 *
 * 本段代码自己注释说逻辑实现的真的“丑陋”....
 * This is really ugly. But the goto's actually try to clarify some
 * of the logic when it comes to error handling etc.
 *
 * Return:
 * * total number of bytes copied, including those the were already @written
 * * negative error code if nothing was copied
 */

ssize_t generic_file_buffered_read(struct kiocb *iocb,
		struct iov_iter *iter, ssize_t written)
{
	struct file *filp = iocb->ki_filp;
	struct address_space *mapping = filp->f_mapping;
	struct inode *inode = mapping->host;
    //预读的数据结构,预读后面会有专门的文章分析
	struct file_ra_state *ra = &filp->f_ra;

    //文件读取位置,即在文件内偏移量,字节为单位
	loff_t *ppos = &iocb->ki_pos;
	pgoff_t index;
	pgoff_t last_index;
	pgoff_t prev_index;
	unsigned long offset;      /* offset into pagecache page */
	unsigned int prev_offset;
	int error = 0;

	if (unlikely(*ppos >= inode->i_sb->s_maxbytes))
		return 0;
	iov_iter_truncate(iter, inode->i_sb->s_maxbytes);

    //把文档按4K分成数据页,index代表是数据页索引
    //|----index 0----|----index 1----|----index 2----|
    //       4K             4K              4K
	index = *ppos >> PAGE_SHIFT;

    //上次预读的位置
	prev_index = ra->prev_pos >> PAGE_SHIFT;
	prev_offset = ra->prev_pos & (PAGE_SIZE-1);

    //iter->count 要读取得字节数,last_index是要读取得最后一个index
	last_index = (*ppos + iter->count + PAGE_SIZE-1) >> PAGE_SHIFT;
	offset = *ppos & ~PAGE_MASK;

    //循环读取iter->count bytes数据(即应用程序要求读取的数据)
	for (;;) {
		struct page *page;
		pgoff_t end_index;
		loff_t isize;
		unsigned long nr, ret;

		cond_resched();
find_page:
		if (fatal_signal_pending(current)) {
			error = -EINTR;
			goto out;
		}
        //mapping指向的address_space缓存中,根据index查找是否已经有缓存数据
		page = find_get_page(mapping, index);
		if (!page) {
            //如果执行了不进行时机的磁盘io操作(必须在cache中)返回错误
			if (iocb->ki_flags & IOCB_NOIO)
				goto would_block;
            //"同步预读",注意,这里同步并非真的同步等待,本质上也是向block layer
            //提交一个io请求就返回了
			page_cache_sync_readahead(mapping,
					ra, filp,
					index, last_index - index);
			page = find_get_page(mapping, index);

            //如果没有成功(因为sync_readahead给page申请内存时,不会进入慢路径,所以
            //有可能失败。失败后goto no_cache_page:分配page页面,并readpage进行磁盘io操作
			if (unlikely(page == NULL))
				goto no_cached_page;
		}

        //走到这里,代表readahead成功,也就是当前想读取的页面,已经被预读了
        //理解这句话要看readahead机制和数据结构,PageReadahead page是怎么设置的
		if (PageReadahead(page)) {
			if (iocb->ki_flags & IOCB_NOIO) {
				put_page(page);
				goto out;
			}
            //触发一次异步read_ahead,因为异步读成功命中,重新取预读更多的页面,也会
            //更新ra数据结构
			page_cache_async_readahead(mapping,
					ra, filp, page,
					index, last_index - index);
		}

        //如果页面数据无效,比如系统I/O非常繁忙,上面异步读没有完成,这里就需要等待了
		if (!PageUptodate(page)) {
			/*
			 * See comment in do_read_cache_page on why
			 * wait_on_page_locked is used to avoid unnecessarily
			 * serialisations and why it's safe.
			 */
            //IOCB_WAITQ代表异步io,比如io_uring
			if (iocb->ki_flags & IOCB_WAITQ) {
				if (written) {
					put_page(page);
					goto out;
				}
				error = wait_on_page_locked_async(page,
								iocb->ki_waitq);
			} else {
                //指定不等待,就返回错误
				if (iocb->ki_flags & IOCB_NOWAIT) {
					put_page(page);
					goto would_block;
				}
                //block等待IO完成,systrace上显示进程block i/o就是等在此处。
				error = wait_on_page_locked_killable(page);
			}
			if (unlikely(error))
				goto readpage_error;

            //上面的等待成功了
			if (PageUptodate(page))
				goto page_ok;

			if (inode->i_blkbits == PAGE_SHIFT ||
					!mapping->a_ops->is_partially_uptodate)
				goto page_not_up_to_date;
			/* pipes can't handle partially uptodate pages */
			if (unlikely(iov_iter_is_pipe(iter)))
				goto page_not_up_to_date;
			if (!trylock_page(page))
				goto page_not_up_to_date;
			/* Did it get truncated before we got the lock? */
			if (!page->mapping)
				goto page_not_up_to_date_locked;
			if (!mapping->a_ops->is_partially_uptodate(page,
							offset, iter->count))
				goto page_not_up_to_date_locked;
			unlock_page(page);
		}
page_ok:
		/*
		 * i_size must be checked after we know the page is Uptodate.
		 *
		 * Checking i_size after the check allows us to calculate
		 * the correct value for "nr", which means the zero-filled
		 * part of the page is not copied back to userspace (unless
		 * another truncate extends the file - this is desired though).
		 */

		isize = i_size_read(inode);
		end_index = (isize - 1) >> PAGE_SHIFT;
		if (unlikely(!isize || index > end_index)) {
			put_page(page);
			goto out;
		}

		/* nr is the maximum number of bytes to copy from this page */
		nr = PAGE_SIZE;
		if (index == end_index) {
			nr = ((isize - 1) & ~PAGE_MASK) + 1;
			if (nr <= offset) {
				put_page(page);
				goto out;
			}
		}
		nr = nr - offset;

		/* If users can be writing to this page using arbitrary
		 * virtual addresses, take care about potential aliasing
		 * before reading the page on the kernel side.
		 */
		if (mapping_writably_mapped(mapping))
			flush_dcache_page(page);

		/*
		 * When a sequential read accesses a page several times,
		 * only mark it as accessed the first time.
		 */
		if (prev_index != index || offset != prev_offset)
			mark_page_accessed(page);
		prev_index = index;

		/*
		 * Ok, we have the page, and it's up-to-date, so
		 * now we can copy it to user space...
		 */

        //数据copy给用户空间,iter->count会减去copy的数据大小
		ret = copy_page_to_iter(page, offset, nr, iter);
		offset += ret;
		index += offset >> PAGE_SHIFT;
		offset &= ~PAGE_MASK;
		prev_offset = offset;

		put_page(page);
		written += ret;
        //如果iter->count = 0,即已经完成数据读取goto out跳出循环。
		if (!iov_iter_count(iter))
			goto out;
		if (ret < nr) {
			error = -EFAULT;
			goto out;
		}
		continue;

page_not_up_to_date:
		/* Get exclusive access to the page ... */
		if (iocb->ki_flags & IOCB_WAITQ)
			error = lock_page_async(page, iocb->ki_waitq);
		else
			error = lock_page_killable(page);
		if (unlikely(error))
			goto readpage_error;

page_not_up_to_date_locked:
		/* Did it get truncated before we got the lock? */
		if (!page->mapping) {
			unlock_page(page);
			put_page(page);
			continue;
		}

		/* Did somebody else fill it already? */
		if (PageUptodate(page)) {
			unlock_page(page);
			goto page_ok;
		}

readpage:
        //low-level page read,即调用readpage触发磁盘i/o
		if (iocb->ki_flags & (IOCB_NOIO | IOCB_NOWAIT)) {
			unlock_page(page);
			put_page(page);
			goto would_block;
		}
		/*
		 * A previous I/O error may have been due to temporary
		 * failures, eg. multipath errors.
		 * PG_error will be set again if readpage fails.
		 */
		ClearPageError(page);
		/* Start the actual read. The read will unlock the page. */
		error = mapping->a_ops->readpage(filp, page);

		if (unlikely(error)) {
			if (error == AOP_TRUNCATED_PAGE) {
				put_page(page);
				error = 0;
				goto find_page;
			}
			goto readpage_error;
		}

		if (!PageUptodate(page)) {
			if (iocb->ki_flags & IOCB_WAITQ)
				error = lock_page_async(page, iocb->ki_waitq);
			else
				error = lock_page_killable(page);

			if (unlikely(error))
				goto readpage_error;
			if (!PageUptodate(page)) {
				if (page->mapping == NULL) {
					/*
					 * invalidate_mapping_pages got it
					 */
					unlock_page(page);
					put_page(page);
					goto find_page;
				}
				unlock_page(page);
				shrink_readahead_size_eio(ra);
				error = -EIO;
				goto readpage_error;
			}
			unlock_page(page);
		}

		goto page_ok;

readpage_error:
		/* UHHUH! A synchronous read error occurred. Report it */
		put_page(page);
		goto out;

no_cached_page:
        //address space没有缓存页面,alloc page,然后插入cache中。
		/*
		 * Ok, it wasn't cached, so we need to create a new
		 * page..
		 */
		page = page_cache_alloc(mapping);
		if (!page) {
			error = -ENOMEM;
			goto out;
		}
		error = add_to_page_cache_lru(page, mapping, index,
				mapping_gfp_constraint(mapping, GFP_KERNEL));
		if (error) {
			put_page(page);
			if (error == -EEXIST) {
				error = 0;
				goto find_page;
			}
			goto out;
		}
		goto readpage;
	}

would_block:
	error = -EAGAIN;
out:
	ra->prev_pos = prev_index;
	ra->prev_pos <<= PAGE_SHIFT;
	ra->prev_pos |= prev_offset;

	*ppos = ((loff_t)index << PAGE_SHIFT) + offset;
	file_accessed(filp);
	return written ? written : error;
}

重点提示:代码中为什么先触发一次sync_readahead,再触发一次async_readahead?

  • 如果代码注释的一样,这两个函数命令很容易误解,其实这两个从应用调用的角度来看,都是”异步"的,以为他们都只是向block layer submit io,所以是异步,从代码上下文也能推测出来,比如sync_readahead调用完之后,紧接调用了find_get_page还是判断了page == NULL的情况,从这点就充分看出来sync_readahead也是异步调用。
  • 如果系统io情况正常,100%顺序读取的情况下,sync_readahead调用一次,触发了预读,后面再顺序读就不会触发sync_readhead了,而是触发async_readahead,async_readahead触发的条件是要读取的page满足PageReadahead(page),即预读页面被命中了。就像网络协议栈中的滑动窗口,既然请求已经来到预读的窗口中,那么就要重新触发新的预读,预读更多的页面。

具体上面这段话不理解的话,参考我后面readahead算法介绍的文章。

wait_on_page_locked_killable返回时机

IO完成之前会一直等待IO完成,在IO完成后,块设备通过中断通知cpu。在中断处理函数中,会进一步触发BLOCK_SOFTIRQ。在软中断处理例程中,回调最终会触发bio->bi_end_io(对ext4来说是mpage_end_io,解锁(unlock_page)之前在锁定的页面,同时调用SetPageUptodate设置update状态,调用栈如下:

#0  SetPageUptodate (page=<optimized out>) at ./include/linux/page-flags.h:542
#1  __read_end_io (bio=0xffff88800607be40) at fs/ext4/readpage.c:85
#2  0xffffffff8150ed00 in mpage_end_io (bio=0xffff88800607be40) at fs/ext4/readpage.c:183
#3  0xffffffff8168859f in bio_endio (bio=0xffff88800607be40) at block/bio.c:1449
#4  0xffffffff8168f9d7 in req_bio_endio (error=<optimized out>, nbytes=<optimized out>, bio=<optimized out>, rq=<optimized out>) at block/blk-core.c:259
#5  blk_update_request (req=0xffff8880062fe040, error=0 '\000', nr_bytes=131072) at block/blk-core.c:1577
#6  0xffffffff816a2d6a in blk_mq_end_request (rq=0xffff8880062fe040, error=<optimized out>) at ./include/linux/blkdev.h:976
#7  0xffffffff81b780c9 in virtblk_request_done (req=0xffff8880062fe040) at drivers/block/virtio_blk.c:171
#8  0xffffffff8169e6fb in blk_done_softirq (h=<optimized out>) at block/blk-mq.c:586
#9  0xffffffff826000d1 in __do_softirq () at kernel/softirq.c:298
#10 0xffffffff82400f82 in asm_call_on_stack () at arch/x86/entry/entry_64.S:708
#11 0xffffffff810ea498 in __run_on_irqstack (func=<optimized out>) at ./arch/x86/include/asm/irq_stack.h:26

 wait_on_page_locked_killable函数代码

static inline int wait_on_page_locked_killable(struct page *page)                                                                                                        
{
    if (!PageLocked(page))
        return 0;
    return wait_on_page_bit_killable(compound_head(page), PG_locked);
}

/* Convenience macros for the sake of set_current_state: */
#define TASK_KILLABLE			(TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)


int wait_on_page_bit_killable(struct page *page, int bit_nr)                                                                                                             
{
    wait_queue_head_t *q = page_waitqueue(page);
    return wait_on_page_bit_common(q, page, bit_nr, TASK_KILLABLE, false);
}

static inline int wait_on_page_bit_common(wait_queue_head_t *q,
        struct page *page, int bit_nr, int state, bool lock)
{
    struct wait_page_queue wait_page;
    wait_queue_entry_t *wait = &wait_page.wait;
    int ret = 0;

    init_wait(wait);
    wait->flags = lock ? WQ_FLAG_EXCLUSIVE : 0;
    wait->func = wake_page_function;
    wait_page.page = page;
    wait_page.bit_nr = bit_nr;

    for (;;) {
        spin_lock_irq(&q->lock);

        if (likely(list_empty(&wait->entry))) {
            __add_wait_queue_entry_tail(q, wait);
            SetPageWaiters(page);
        }

        set_current_state(state);

        spin_unlock_irq(&q->lock);

        if (likely(test_bit(bit_nr, &page->flags))) {
            io_schedule();
        }

        if (lock) {
            if (!test_and_set_bit_lock(bit_nr, &page->flags))
                break;
        } else {
            if (!test_bit(bit_nr, &page->flags))
                break;
        }

        if (unlikely(signal_pending_state(state, current))) {
            ret = -EINTR;
            break;
        }
    }

    finish_wait(q, wait);

    /*
     * A signal could leave PageWaiters set. Clearing it here if
     * !waitqueue_active would be possible (by open-coding finish_wait),
     * but still fail to catch it in the case of wait hash collision. We
     * already can fail to clear wait hash collision cases, so don't
     * bother with signals either.
     */

    return ret;
}

IO没有完成前,PG_Locked一直置位,调用进入io_schedule,进程主动让出CPU,状态切入TASK_KILLABLE(同时也是一个TASK_UNINTERRUPTIBLE)。io_schedule会设置current->io_wait = 1,io等待期间,cpu idle期间检测到io_wait就会累计iowait的时间。

IO完成解锁页面,唤醒进程

static void __read_end_io(struct bio *bio)
{
    struct page *page;
    struct bio_vec *bv;
    struct bvec_iter_all iter_all;

    bio_for_each_segment_all(bv, bio, iter_all) {
        page = bv->bv_page;

        /* PG_error was set if any post_read step failed */
        if (bio->bi_status || PageError(page)) {
            ClearPageUptodate(page);
            /* will re-read again later */
            ClearPageError(page);
        } else {
            //设置Uptodate状态
            SetPageUptodate(page);
        }
        //解锁页面,会wakeup等待的线程
        unlock_page(page);                                                                                                                                               
    }
    if (bio->bi_private)
        mempool_free(bio->bi_private, bio_post_read_ctx_pool);
    bio_put(bio);
}

/**
 * unlock_page - unlock a locked page
 * @page: the page
 *
 * Unlocks the page and wakes up sleepers in ___wait_on_page_locked().
 * Also wakes sleepers in wait_on_page_writeback() because the wakeup
 * mechanism between PageLocked pages and PageWriteback pages is shared.
 * But that's OK - sleepers in wait_on_page_writeback() just go back to sleep.
 *
 * Note that this depends on PG_waiters being the sign bit in the byte
 * that contains PG_locked - thus the BUILD_BUG_ON(). That allows us to
 * clear the PG_locked bit and test PG_waiters at the same time fairly
 * portably (architectures that do LL/SC can test any bit, while x86 can
 * test the sign bit).
 */
void unlock_page(struct page *page)
{
	BUILD_BUG_ON(PG_waiters != 7);
	page = compound_head(page);
	VM_BUG_ON_PAGE(!PageLocked(page), page);
	if (clear_bit_unlock_is_negative_byte(PG_locked, &page->flags))
		wake_up_page_bit(page, PG_locked);
}
EXPORT_SYMBOL(unlock_page);

mpage_end_io 回调是哪里赋值的 - ext4读文件为例

fs/ext4/readpage.c : ext4_mpage_readpages

int ext4_mpage_readpages(struct address_space *mapping,
			 struct list_head *pages, struct page *page,
			 unsigned nr_pages)
{
    ...
	for (; nr_pages; nr_pages--) {
        ...
		bio_set_dev(bio, bdev);
		bio->bi_iter.bi_sector = blocks[0] << (blkbits - 9);
		bio->bi_end_io = mpage_end_io;
		bio->bi_private = ctx;
		ext4_set_bio_ctx(inode, bio);
		bio_set_op_attrs(bio, REQ_OP_READ, 0);
        ...
	}
	BUG_ON(pages && !list_empty(pages));
	if (bio)
		ext4_submit_bio_read(bio);
	return 0;
}

参考文章:

Linux 通用块层 bio 详解 – 字节岛技术分享

Life of an ext4 write request - Ext4

深入分析Linux内核File cache机制(上篇) - 知乎

Linux内核中跟踪文件PageCache预读_读取

Windows内核驱动开发:跨进程内存读写原理与实战 在操作系统层面,虚拟内存与进程隔离机制是保障系统稳定与安全的核心基础。每个进程运行在独立的虚拟地址空间,用户态程序通过系统调用(如ReadProcessMemory)请求内核进行内存访问,但受限于权限检查,无法直接访问其他进程的私有内存。驱动开发作为突破用户态权限壁垒的关键技术,以内核模式运行,能够绕过常规访问限制,直接操作物理内存。这一能力在系统级调试、性能监控、安全软件及自动化工具开发中具有重要价值。通过MmCopyVirtualMemory等内核API,驱动可以安全高效地实现跨进程内存读写,为处理高并 阅读详情

相关推荐

百度2019校招计算与存储系统笔试题解析:底层系统研发核心考点与备考思路

在底层系统研发领域,操作系统、并发编程与分布式存储是绕不开的核心能力。理解进程线程模型、锁与内存屏障,掌握文件系统IO路径与WAL机制,再深入Raft一致性协议,构成了高性能存储系统的技术基石。这些知识不仅支撑着数据库内核、分布式文件系统的工程实践,也是大厂基础架构岗位笔试面试的高频考点。本文结合百度2019校招计算与存储系统研发工程师笔试题,剖析题目背后的考察逻辑与备考策略,帮助开发者构建完整的知识体系,应对系统研发岗的技术挑战。

weixin_30565327的博客 545

linux内核篇-文件系统(硬盘、虚拟文件系统、文件缓存)

文件系统的意义之前说的都是在进程在物理内存保存的数据,内存就像一个纸箱子,。如果我们想要进程结束之后,数据依然能够保存下来,就不能只保存在内存里,而是应该保存在外部存储中。我们最常用的外部存储就是硬盘,数据是以文件的形式保存在硬盘上的。为了管理这些文件,我们在规划文件系统的时候,需要考虑如下几点。文件系统的几个要点:1、严格的组织形式。以单位进行存储,比如图书馆的书架分成很多小格子;2、要有索引,实现快速查找;3、要有缓存,实现热点文件的快速应用;

weixin_53344209的博客 1197

滴滴Linux内核工程师笔试解析:从C语言到内核机制

操作系统底层原理是理解计算机系统运行的核心,其中进程调度、内存管理、并发同步等机制共同决定了系统的性能与稳定性。C语言作为内核开发的基础语言,其指针、内存对齐等知识点直接影响对底层实现的理解。掌握这些概念,不仅有助于分析内核源代码,还能在实际工程中定位性能瓶颈,例如通过ftrace、perf等工具优化高并发场景下的网络协议栈与块I/O路径。从通用技术原理出发,逐步深入到中断处理、文件系统等细节,能够构建完整的知识体系。本文以滴滴2018校招Linux内核工程师笔试为切入点,系统梳理了笔试中的高频考点与答题思

weixin_33491377的博客 288

VFS基础学习笔记 - 5.读文件过程

目录1. 前言2. buffer_head与page的关联3. ksys_read|- -generic_file_read_iter|- - -generic_file_buffered_read参考文档 1. 前言 本专题我们开始学习虚拟文件系统VFS的相关内容。本专题主要参考了《存储技术原理分析》、ULA、ULK的相关内容。本文主要记录读文件的过程。 kernel版本:5.10 FS: minix 平台:arm64 注: 为方便阅读,正文标题采用分级结构标识,每一级用一个"-“表示,如:两级为”|-

HZero 1292

从TOCTOU到物理内存读写:内核驱动漏洞链实战剖析

TOCTOU(Time-of-Check Time-of-Use)竞争条件漏洞是操作系统内核中一类经典的安全问题,其本质在于程序对某个条件(如资源状态)的检查与使用之间存在可被攻击者利用的时间窗口。理解其原理需要深入内核的并发执行机制与内存管理模型。该漏洞的技术价值在于,单个TOCTOU缺陷可能无法直接造成严重破坏,但通过与内核池分配器行为(Pool Feng Shui)等系统机制结合,攻击者可以构建漏洞链,将有限的权限逐步提升,最终可能实现任意地址读写甚至物理内存操纵,从而完全绕过系统的安全边界。在Win

lizaochengwen的专栏 358

在 DigitalOcean A10 GPU Droplet 上部署 BAGEL 多模态大模型

多模态大模型(VLM)是实现图文理解与生成的核心技术,其部署需兼顾模型精度、显存效率与云环境适配性。BAGEL 作为轻量高精度视觉语言模型,采用 CLIP-ViT-L 主干剪枝与动态稀疏注意力,在低延迟推理和显存优化方面表现突出;而 DigitalOcean GPU Droplet 提供独占 A10(24GB GDDR6,sm_86 架构)资源,具备确定性硬件环境与透明计费优势,成为中小规模 VLM 工程落地的理想平台。本文聚焦于 CUDA 版本锁死、FlashAttention 取舍、vLLM 推理加速及

angw337679452的博客 410

ad file type not recognised_一个神奇的Linux内核补丁:generic_file_buffered_read

我们都知道linux内核中的代码非常精妙,但是有些代码由于历史原因,一个函数为了兼容处理各种情况,到最后可能变得非常糟糕,到处都是goto 和if。如果在系统中读一个文件时会调用generic_file_buffered_read这个函数的功能是把磁盘中的数据读到page之后,或者直接获取cache中的page,然后调用copy_page_to_iter把page拷贝到用户层的buffer中。一天...

weixin_40003767的博客 659

基于mbed OS的Seeed nRF52蓝牙开发实战:从环境搭建到低功耗优化

蓝牙低功耗(BLE)技术是物联网设备实现无线通信的核心协议,它通过GAP(通用访问规范)管理设备发现与连接,并通过GATT(通用属性规范)定义服务与特征值的数据交换框架。其工作原理基于事件驱动的异步通信模型,在保证低功耗的前提下实现可靠的数据传输。这项技术的核心价值在于为资源受限的嵌入式设备提供了标准化的无线互联能力,极大地拓展了其在可穿戴设备、智能家居传感器、远程监控等场景中的应用。本文聚焦于如何利用ARM mbed OS这一面向物联网的嵌入式操作系统,在Seeed Studio的nRF52840开发板上

weixin_34357887的博客 359

linux文件读取中的Readahead预读机制

Readahead预读机制 由于内存的速度比磁盘速度快很多,如果每一次访问文件数据都要从磁盘读取一次数据,就会导致非常严重的时延。因此Linux为了提高性能,通过page cache机制,将多个用户数据缓存在内存当中,从而避免多次再磁盘读取。Readahead预读机制正是将用户数据缓存到内存的方法之一。 Readahead机制的介绍 Readahead预读机制是Linux针对顺序读的性能优化机制。它的核心思想是当用户访问连续多个page的时候,一次性将多个连续的页从磁盘读取到内存中,从而避免多次与磁盘

jinking01的专栏 7440

docker一键部署笔记

默认 登录用户名: postgres如果遇到 FATAL: password authentication failed for user "XXX"有可能防火墙的问题,禁止外部访问解决方法: 在 data 的 pg_hba.conf 中的 ipv4 添加以下一行。

Angindem博客 459

AI小智语音服务架构与嵌入式成本优化实践

语音交互系统是智能硬件的核心能力,其本质是端云协同的实时数据处理链路,涵盖音频采集、流式传输、语义理解与语音合成四大技术环节。原理上需严格划分物理层(确定性音频I/O)、协议层(WebSocket/Range可靠性通信)和语义层(ASR/LLM/TTS职责解耦),以保障低延迟与高可用。技术价值在于通过VAD前置过滤、分块下载、Token截断等嵌入式级优化,显著降低云端调用成本与网络开销。典型应用场景包括儿童故事机、智能音箱、教育机器人等对响应速度、音色自然度和单次交互成本敏感的AI小智设备。本文聚焦火山引擎

weixin_30653091的博客 349

博客摘录「 Linux read的核心函数generic_file_buffered_read」2024年10月31日

IO没有完成前,PG_Locked一直置位,调用进入io_schedule,进程主动让出CPU,状态切入TASK_KILLABLE(同时也是一个TASK_UNINTERRUPTIBLE)。io_schedule会设置current->io_wait = 1,io等待期间,cpu idle期间检测到io_wait就会累计iowait的时间。

tompig撒欢的地方 239

基于PLC的全自动洗衣机控制系统+控制程序

本设计使用PLC控制系统替换了传统的继电器控制系统,实现了全自动洗衣机在硬件方面的提升,丰富了全自动洗衣机在软件方面的功能,提高了全自动洗衣机的洗衣效率,基本可以满足用户的洗衣需求。

zzzz114231的博客 8953

Linux 内核 I/O 子系统实战:从 write() 调用到磁盘中断的 5 层追踪

本文深入解析Linux内核I/O子系统的工作机制,从write()系统调用出发,详细追踪其穿越用户空间I/O软件层、设备独立性软件层、设备驱动层、中断处理层和硬件层的完整路径。通过分析每层的关键数据结构和函数调用链,揭示Linux I/O子系统的实现原理,并提供性能调优的关键点,帮助开发者编写高效代码和排查I/O问题。

congtaixiao7151的博客 412

统计信号处理基础 习题解答1-1

1-1:在雷达习题中,双程延时时间的估计量具有PDF τ0~Nτ0,στ02,其中τ0是真值。假如要估计距离请提出一种估计量R,并求它的PDF。然后,确定标准偏差στ0,使得距离估计值的99%在真值的100m以内,对电磁波传播速度取c=3×108m/s。 解答: 问题1: 由于电磁波传播速度c,信号可观察的双程传播延时τ0和双程传播距离τ0满足关系: R=cτ02 因此可采用如下估计量: R=cτ02 问题2: 由于τ0满足正态分布,且: Eτ0=τ0 varτ0=στ02 那么: E

weixin_43270276的博客 2805

56、Cortex - M33处理器浮点单元(FPU)的使用与优化

本文详细介绍了Cortex-M33处理器浮点单元(FPU)的使用与优化方法,涵盖单双精度浮点计算的选择、编译器命令行配置、ABI模式对比、特殊FPU模式设置(如Flush-to-Zero、Default NaN)、浮点异常处理机制以及FPU电源管理策略。结合代码示例和流程图,提供了在不同应用场景下的最佳实践建议,并总结了常见问题及解决方案,帮助开发者提升嵌入式系统中浮点运算的性能、精度与能效。

bread的博客 153

F2FS源码分析-2.3 [F2FS 读写部分] F2FS的一般文件读流程分析

F2FS的读流程 读流程介绍 F2FS的读流程包含了以下几个子流程: vfs_read函数 generic_file_read_iter函数: 根据访问类型执行不同的处理 generic_file_buffered_read: 根据用户传入的文件偏移,读取尺寸等信息,计算起始位置和页数,然后遍历每一个page,通过预读或者单个读取的方式从磁盘中读取出来 f2fs_read_data_page&a...

Hubery的存储点滴 3479

(2)NPW(监控片)【中】---业务场景精讲

自己在日常工作当中,由于非产线人员出生,对NPW的知识一直一知半解,业务场景认识的不够透彻,做系统的时候很难理解甲方的NPW业务应用,特收集了不少关于NPW的一些业务场景,下面都是一些摘抄,大家关注我标红标粗的部分,

阿拉伯梳子的专栏 1586

Windows PC上实现微秒级实时任务:内核驱动与WSL2方案全解析

实时操作系统(RTOS)的核心在于确定性,要求系统在可预测的时间间隔内响应事件,关键指标是延迟和抖动。其技术原理通常基于优先级抢占式调度、中断快速响应和内存锁定等机制,以确保任务执行的时序可控。在工业控制、机器人、音视频处理等应用场景中,实时性对系统稳定性和精度至关重要。本文聚焦于在通用Windows平台上实现实时任务这一挑战,深入探讨了通过Windows内核驱动(KMDF)构建高精度定时调度引擎的方案,并对比了基于WSL2运行实时Linux内核的备选路径。文章详细拆解了驱动开发中的HPET定时器配置、DP

weixin_33795093的博客 416

手把手打通全流程:在华为昇腾310/Atlas 200DK上实现深度学习模型从移植到部署

通过以上步骤,你已成功在华为昇腾310 Atlas 200DK上完成深度学习模型的移植与推理。这一流程可复用到目标检测、语义分割等其他任务,快去尝试将自己的AI模型部署到昇腾设备上,打造属于你的边缘智能应用吧!上部署深度学习模型,这篇教程将带你走完从模型转换到推理运行的完整流程。无需担心技术壁垒,我们以通俗易懂的方式拆解每一步,让你轻松在昇腾嵌入式设备上实现AI应用。是专为边缘AI设计的高性能处理器,Atlas 200DK是其开发板形态,能高效运行图像分类、目标检测等深度学习任务。

FJN110的博客 181

华为od 面试八股文_C语言_02_含答案

华为od 面试八股文_C语言_02_含答案

MISAYAONE的博客 2229

OpenArk:终极Windows内核安全分析工具完整指南

OpenArk是一款面向Windows平台的开源反Rootkit(ARK)工具,专为安全研究人员、逆向工程师和系统管理员设计。作为新一代的系统安全分析工具,OpenArk提供了深度内核访问能力和全面的系统监控功能,帮助用户检测隐藏的恶意软件、分析系统行为并进行底层调试。本文将深入探讨OpenArk的核心功能、技术实现原理以及实际应用场景,为技术开发者提供完整的操作指南。 ## 为什么需要专业的内

gitblog_00735的博客 490

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Rust构建Windows原生Gemini CLI:集成Nushell与thinkingConfig实战

Gemini API 是 Google 提供的大语言模型服务接口,其核心能力依赖于稳定、低延迟、结构化强的客户端实现。在 Windows 平台,传统 Python/Node.js 客户端常因 SSL 证书链、流式响应解析、进程管道语义不一致等问题失效;而 Rust 凭借内存安全、零运行时依赖、MSVC 工具链深度适配等特性,成为构建高可靠性 Gemini 本地客户端的理想选择。结合 Nushell 的表格化管道和 Gemini 3.0 Pro 的 thinkingConfig 高级推理控制,可实现命令行级

weixin_34335458的博客 504

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

1.数据,运算符和输入输出【由浅入深.c】

本文介绍数据类型和变数据,运算符和输入输出相关内容。

插花弄玉的博客 1224

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

MYSQL 网络连接

assert(mp!= nullptr);#endif首先断言mp非空。为结构体动态分配内存,并将指针赋给。调用来初始化这个安全互斥锁,同时传入源文件名和行号。内部通常会:再调用初始化真正的原生互斥体。记录锁的创建位置、线程 ID 等信息,以便在死锁检测、锁定顺序检查或错误报告时提供调试信息。这种模式主要用于调试版本,帮助开发者发现互斥锁使用问题(如重复锁定、死锁、未释放等)。#else#endif直接调用初始化中的原生互斥体(例如或 Windows 的。

Piemon&Anokata的博客 497
上一篇: buffer_head数据结构
下一篇: Linux 读文件 - readahead预读算法
OS技术解密
博客等级 码龄16年 3760粉丝 189原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值