Linux内核文件读取流程

本文代码基于Linux5.10。

当上层调用read函数读取一个文件时, Linux 内核究竟如何处理? 本文主要介绍这个问题

数据结构

address_space

linux 的文件在磁盘上可能是不连续的, 但文件读取又需要将文件当成一个连续的字节流, 为了解决这个矛盾, 就引入了address_space。

address_space的作用就是将文件在磁盘上的数据也page的方式连续地呈现出来, 这样读取文件的操作便转换成了先将不连续的磁盘上的内容读取的page中, 再从连续的page中去读取连续的数据。

address_spage的定义如下

include/linux/fs.h
struct address_space {
    struct inode        *host;
    struct xarray       i_pages;
    gfp_t           gfp_mask;
    atomic_t        i_mmap_writable;
#ifdef CONFIG_READ_ONLY_THP_FOR_FS
    /* number of thp, only for non-shmem files */
    atomic_t        nr_thps;
#endif
    struct rb_root_cached   i_mmap;
    struct rw_semaphore i_mmap_rwsem;
    unsigned long       nrpages;
    unsigned long       nrexceptional;
    pgoff_t         writeback_index;
    const struct address_space_operations *a_ops;
    unsigned long       flags;
    errseq_t        wb_err;
    spinlock_t      private_lock;
    struct list_head    private_list;
    void            *private_data;
} __attribute__((aligned(sizeof(long)))) __randomize_layout;

host: 该结构体对应的文件的inode

i_pages: pages对应的数组

nrpages: 页面总数

a_ops: 地址空间的操作函数

整体流程

由于page cache的存在, read 并不是直接文件中读取, 而是从page cache中读,那么整体流程大概是: 如果page cache存在, 则直接从page cache中读取, 如果不存在, 则将文件内容先读到page cache中, 再copy给用户。

入口函数

read 系统调用的定义如下:

fs/read_write.c
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
    return ksys_read(fd, buf, count);
}

ksys_read的定义如下:

fs/read_write.c
ssize_t ksys_read(unsigned int fd, char __user *buf, size_t count)
{
    struct fd f = fdget_pos(fd);
    ssize_t ret = -EBADF;

    if (f.file) {
        loff_t pos, *ppos = file_ppos(f.file);          /*             1         */
        if (ppos) {
            pos = *ppos;
            ppos = &pos;
        } 
        ret = vfs_read(f.file, buf, count, ppos);       /*             2             */
        if (ret >= 0 && ppos)
            f.file->f_pos = pos;                        /*             3              */
        fdput_pos(f);
    }
    return ret;
}

(1) 获取当前文件的偏移

(2) 调用vfs_read执行具体的read流程

(3) 如果读取成功, 更新当前文件的偏移

vfs_read 的定义如下:

fs/read_write.c
ksys_read-> vfs_read
ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
    ssize_t ret;

    if (!(file->f_mode & FMODE_READ))
        return -EBADF;
    if (!(file->f_mode & FMODE_CAN_READ))
        return -EINVAL;
    if (unlikely(!access_ok(buf, count)))            /*           1            */
        return -EFAULT;

    ret = rw_verify_area(READ, file, pos, count);       /*           2          */
    if (ret)
        return ret;
    if (count > MAX_RW_COUNT)
        count =  MAX_RW_COUNT;

    if (file->f_op->read)
        ret = file->f_op->read(file, buf, count, pos);           
    else if (file->f_op->read_iter)
        ret = new_sync_read(file, buf, count, pos);       /*          3       */
    else
        ret = -EINVAL;
    if (ret > 0) {
        fsnotify_access(file);
        add_rchar(current, ret);
    }
    inc_syscr(current);
    return ret;
}

(1) 对文件的模式及用户buf进行检测

(2) 校验用户读取的区域是否有效

(3) 调用new_sync_read进行读取。 由于目前大多数文件系统都是用的新的read_iter回调, 故一般都会走到这里来。 read_iterread的区别是, read_iter一次性可以读取多个文件片段

new_sync_read的实现如下:

fs/read_write.c
ksys_read-> vfs_read->new_sync_read
static ssize_t new_sync_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos)
{
    struct iovec iov = { .iov_base = buf, .iov_len = len };
    struct kiocb kiocb;
    struct iov_iter iter;
    ssize_t ret;

    init_sync_kiocb(&kiocb, filp);
    kiocb.ki_pos = (ppos ? *ppos : 0);
    iov_iter_init(&iter, READ, &iov, 1, len);           /*           1        */

    ret = call_read_iter(filp, &kiocb, &iter);          /*           2          */
    BUG_ON(ret == -EIOCBQUEUED);
    if (ppos)
        *ppos = kiocb.ki_pos;
    return ret;
}

(1) 初始化kiocb 和iov_iter 这两个结构体,他们用来控制read行为。 其定义如下:

include/linux/fs.h
struct kiocb {
    struct file     *ki_filp;

    /* The 'ki_filp' pointer is shared in a union for aio */
    randomized_struct_fields_start

    loff_t          ki_pos;
    void (*ki_complete)(struct kiocb *iocb, long ret, long ret2);
    void            *private;
    int         ki_flags;
    u16         ki_hint;
    u16         ki_ioprio; /* See linux/ioprio.h */
    union {
        unsigned int        ki_cookie; /* for ->iopoll */
        struct wait_page_queue  *ki_waitq; /* for async buffered IO */
    };

    randomized_struct_fields_end
};

include/linux/uio.h
struct iov_iter {
    /*
     * Bit 0 is the read/write bit, set if we're writing.
     * Bit 1 is the BVEC_FLAG_NO_REF bit, set if type is a bvec and
     * the caller isn't expecting to drop a page reference when done.
     */
    unsigned int type;
    size_t iov_offset;
    size_t count;
    union {
        const struct iovec *iov;
        const struct kvec *kvec;
        const struct bio_vec *bvec;
        struct pipe_inode_info *pipe;
    };
    union {
        unsigned long nr_segs;
        struct {
            unsigned int head;
            unsigned int start_head;
        };
    };
};

kiocb 用来控制整个读取流程, kernel 每一次读写都会对应一个kiocb (kernel io control block)

iov_iter 记录了所有读取片段的信息。 由于kernel 支持readv这样的调用, 支持一次读取多个片段, iov_iter就是 用来记录所有的片段信息。其中:

iov 指向一个数组, 代表了所有的片段信息

nr_segs 代表iov的数组长度

count代表所有的iov的总长度,即一次读取的总的文件长度

(2) call_read_iter实际上就是调用了file->f_op->read_iter(kio, iter);

static inline ssize_t call_read_iter(struct file *file, struct kiocb *kio,
                      struct iov_iter *iter)
{
    return file->f_op->read_iter(kio, iter);
}

以exfat文件系统为例, 其read_iter的实现为

fs/exfat/file.c
const struct file_operations exfat_file_operations = {
    .llseek     = generic_file_llseek,
    .read_iter  = generic_file_read_iter,
    .write_iter = generic_file_write_iter,
    .mmap       = generic_file_mmap,
    .fsync      = exfat_file_fsync,
    .splice_read    = generic_file_splice_read,
    .splice_write   = iter_file_splice_write,
};

mm/filemap.c
ksys_read-> vfs_read->new_sync_read->generic_file_read_iter
ssize_t generic_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
{
    size_t count = iov_iter_count(iter);
    ssize_t retval = 0;

    if (!count)
        goto out; /* skip atime */

    if (iocb->ki_flags & IOCB_DIRECT) {
        struct file *file = iocb->ki_filp;
        struct address_space *mapping = file->f_mapping;
        struct inode *inode = mapping->host;
        loff_t size;

        size = i_size_read(inode);
        if (iocb->ki_flags & IOCB_NOWAIT) {
            if (filemap_range_has_page(mapping, iocb->ki_pos,
                           iocb->ki_pos + count - 1))
                return -EAGAIN;
        } else {
            retval = filemap_write_and_wait_range(mapping,
                        iocb->ki_pos,
                            iocb->ki_pos + count - 1);
            if (retval < 0)
                goto out;
        }

        file_accessed(file);

        retval = mapping->a_ops->direct_IO(iocb, iter);
        if (retval >= 0) {
            iocb->ki_pos += retval;
            count -= retval;
        }
        iov_iter_revert(iter, count - iov_iter_count(iter));

        /*
         * Btrfs can have a short DIO read if we encounter
         * compressed extents, so if there was an error, or if
         * we've already read everything we wanted to, or if
         * there was a short read because we hit EOF, go ahead
         * and return.  Otherwise fallthrough to buffered io for
         * the rest of the read.  Buffered reads will not work for
         * DAX files, so don't bother trying.
         */
        if (retval < 0 || !count || iocb->ki_pos >= size ||
            IS_DAX(inode))
            goto out;
    }

    retval = generic_file_buffered_read(iocb, iter, retval);      /*         1       */
out:
    return retval;
}
EXPORT_SYMBOL(generic_file_read_iter);

可以看到, read_iter 的实现为generic_file_read_iter, 如果不是DIO, 则直接调用generic_file_buffered_read

mm/filemap.c
ksys_read-> vfs_read->new_sync_read->generic_file_read_iter->generic_file_buffered_read
ssize_t generic_file_buffered_read(struct kiocb *iocb,
        struct iov_iter *iter, ssize_t written)
{
    struct file *filp = iocb->ki_filp;
    struct file_ra_state *ra = &filp->f_ra;
    struct address_space *mapping = filp->f_mapping;
    struct inode *inode = mapping->host;
    struct page *pages_onstack[PAGEVEC_SIZE], **pages = NULL;
    unsigned int nr_pages = min_t(unsigned int, 512,
            ((iocb->ki_pos + iter->count + PAGE_SIZE - 1) >> PAGE_SHIFT) -
            (iocb->ki_pos >> PAGE_SHIFT));
    int i, pg_nr, error = 0;
    bool writably_mapped;
    loff_t isize, end_offset;

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

    if (nr_pages > ARRAY_SIZE(pages_onstack))
        pages = kmalloc_array(nr_pages, sizeof(void *), GFP_KERNEL);

    if (!pages) {
        pages = pages_onstack;
        nr_pages = min_t(unsigned int, nr_pages, ARRAY_SIZE(pages_onstack));
    }

    do {
        cond_resched();

        /*
         * If we've already successfully copied some data, then we
         * can no longer safely return -EIOCBQUEUED. Hence mark
         * an async read NOWAIT at that point.
         */
        if ((iocb->ki_flags & IOCB_WAITQ) && written)
            iocb->ki_flags |= IOCB_NOWAIT;

        i = 0;
        pg_nr = generic_file_buffered_read_get_pages(iocb, iter,
                                 pages, nr_pages);        /*          1            */
        if (pg_nr < 0) {
            error = pg_nr;
            break;
        }

        /*
         * i_size must be checked after we know the pages are 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);
        if (unlikely(iocb->ki_pos >= isize))
            goto put_pages;

        end_offset = min_t(loff_t, isize, iocb->ki_pos + iter->count);

        while ((iocb->ki_pos >> PAGE_SHIFT) + pg_nr >
               (end_offset + PAGE_SIZE - 1) >> PAGE_SHIFT)
            put_page(pages[--pg_nr]);

        /*
         * Once we start copying data, we don't want to be touching any
         * cachelines that might be contended:
         */
        writably_mapped = mapping_writably_mapped(mapping);

        /*
         * When a sequential read accesses a page several times, only
         * mark it as accessed the first time.
         */
        if (iocb->ki_pos >> PAGE_SHIFT !=
            ra->prev_pos >> PAGE_SHIFT)
            mark_page_accessed(pages[0]);
        for (i = 1; i < pg_nr; i++)
            mark_page_accessed(pages[i]);

        for (i = 0; i < pg_nr; i++) {               /*              2           */
            unsigned int offset = iocb->ki_pos & ~PAGE_MASK;
            unsigned int bytes = min_t(loff_t, end_offset - iocb->ki_pos,
                           PAGE_SIZE - offset);
            unsigned int copied;

            /*
             * 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 (writably_mapped)
                flush_dcache_page(pages[i]);

            copied = copy_page_to_iter(pages[i], offset, bytes, iter);

            written += copied;
            iocb->ki_pos += copied;
            ra->prev_pos = iocb->ki_pos;

            if (copied < bytes) {
                error = -EFAULT;
                break;
            }
        }
put_pages:
        for (i = 0; i < pg_nr; i++)
            put_page(pages[i]);
    } while (iov_iter_count(iter) && iocb->ki_pos < isize && !error);

    file_accessed(filp);

    if (pages != pages_onstack)
        kfree(pages);

    return written ? written : error;
}
EXPORT_SYMBOL_GPL(generic_file_buffered_read);

这个函数的整体逻辑应该是: 将文件内容读取到page中, 然后再拷贝到对应的iov

(1) 找到对应的page, 如果不存在, 则新建, 并将文件内容读取到其中

(2) 遍历相关的page, 将其内容拷贝到iov_iter

文件预读

在读取文件内容到page 的过程中, 存在预读的过程。

预读算法预测即将访问的页面,并提前将页面批量读入内存缓存。

这段逻辑可以参考https://mp.weixin.qq.com/s/8GIeK8C3bz8nbLcwmk1vcA?from=singlemessage&isappinstalled=0&scene=1&clicktime=1646449253&enterid=1646449253, 这里面说的很详细

读取磁盘内容到page

文件预读的最后一步, 是读取文件内容到page中。 这个会调用到address_space_operationsreadpage 函数,在exFAT 文件系统中, 这个函数的实现为:

static const struct address_space_operations exfat_aops = {
    .readpage   = exfat_readpage,
    .readahead  = exfat_readahead,
    .writepage  = exfat_writepage,
    .writepages = exfat_writepages,
    .write_begin    = exfat_write_begin,
    .write_end  = exfat_write_end,
    .direct_IO  = exfat_direct_IO,
    .bmap       = exfat_aop_bmap
};

static int exfat_readpage(struct file *file, struct page *page)
{
    return mpage_readpage(page, exfat_get_block);
}

可以看到, 这个函数会调用到, mpage_readpage

int mpage_readpage(struct page *page, get_block_t get_block)
{
    struct mpage_readpage_args args = {
        .page = page,
        .nr_pages = 1,
        .get_block = get_block,
    };

    args.bio = do_mpage_readpage(&args);
    if (args.bio)
        mpage_bio_submit(REQ_OP_READ, 0, args.bio);
    return 0;
}
EXPORT_SYMBOL(mpage_readpage);

最终调用到do_mpage_readpage。这个是读取文件内容到 page中的关键函数, 在理解这个函数之前, 需要先做几点说明, 否则无法理解其中的逻辑。

文件空洞

linux 中有些文件系统是允许文件空洞的。

例如一个文件只有1M , 但用户可以seek到2M的地方去写1M, 这中间的1M区域就成了空洞(hole) , 你查看文件大小, 显示的是3M, 但实际上在硬盘上只占用了2M的空间,中间1M的区域, 你读取会读到0。

fallocate、truncate 以及GNU dd的seek扩展命令都可以实现创建空洞文件的效果

fallocate -l 10G bigfile
truncate -s 10G bigfile
dd of=bigfile bs=1 seek=10G count=0

bio 和buffer_head

linux 中, vfs 下发一个io 请求到block 层, 一般有两种方式, 一种是通过 bio , 一种是buffer_head。

buffer_head 代表的是一个sector(一般是512字节), 你可以将磁盘上一个sector的内容读到一个buffer_head结构体中。

一个bio代表了物理连续的多个sector, 用于请求一大段连续的空间。

do_mpage_readpage

理解了上面两点, 下面可以来解释这个函数的具体逻辑。 一般来说,一个page 是4k, 一般包含多个 sector(512B)。 如果要读取一个page, 最理想的场景是, 这个page对应的sector在磁盘上是连续的, 那么我们只需要通过下发一个长度为4k的bio下去,就可以读取到所有的内容。 但是, 很有可能一个page对应的内容在磁盘上是不连续的, 甚至可能不存在(文件空洞), 这种情况就没法通过下发一个bio去读取了, 因为文件内容是不连续的, 只能通过一个buffer_head一个sector一个sector去读。

linux 中, 如果要读取某个buffer_head, 需要指定它对应了物理磁盘上哪个sector。 流程一般如下

sector_t phys = x;
struct super_block *sb = ...
struct buffer_head *bh_result
map_bh(bh_result, sb, phys); // 设置buffer_head对应的物理sector
submit_bh(REQ_OP_READ, 0, bh_result); // 读取对应sector的内容到buffer_head中

那么, 读取文件时, 怎么知道某个buffer_head对应的物理sector是哪个呢?

这个就需要用到mpage_readpage传进来的get_block的这个这个回调。

例如, 如果读文件的头4k的内容, 其在文件中的逻辑sector号分别是0,1,2....7, 那通过get_block这个回调, 就可以得到文件中第0个sector对应了物理上的那个sector。 知道了这个物理sector号, 就可以通过buffer_head或者bio 的方式去读取了。

do_mpage_readpage的整体逻辑就是, 尽量通过bio的方式去读取连续的sector, 如果不行, 就转而通过buffer_head的方式一个sector一个sector去读。 一个页面的逻辑块被映射到磁盘可能有以下几种情况(以下内容主要参考《存储技术原理分析, 敖青云》)

(1) 页面所有逻辑块映射到了磁盘上连续的逻辑块, 这种情况下会以bio方式提交。如果传入了bio,并和本页面在磁盘上连续, 那个尽可能合成一个bio

(2) 页面的逻辑块映射到了磁盘 上不连续的逻辑块。如果要作为bio的方式提交的话, 将不只一个请求, 会增加复杂性。所以采用buffer_head的方式处理, 如果传入了bio, 那么先将bio提交。

(3)页面的前面部分逻镇块未被映射到磁盘上 (标记为纯灰色的逻辑块)。尽管只有一个请求段,但它的起始位置不是从0开始,支持这种情况并非不可能,但代码会更复杂。所以也采用buffer_head的方式处理。如果调用时传入了一个bio,那么先把提交执行。

(4)页面的中间部分逻辑块未被映射到磁盘上,也就是说,中间部分为“空洞”。同第二种情况一样,因为有多个请求段,也只能采用缓冲页面的方式来处理。如果调用时传入了一个bio,那么先把提交执行。

(5)页面的后面部分逻强块末被映射到磁盘上,它会以bio 方式来提交。如果函数调用时传入了个bio, 并且和本页面在磁盘上连续,那么会将这个页面添加到传入的 bio 中,如果可行的话。

(6) 页面的所有逻镇块都未被映射到磁盘上,这时会跳过这个页面的处理。如果调用时传入了一个bio,则依旧将它返回,以期可以继续合并后面的页面。

理解了上面的情况, 下面就可理解do_mpage_readpage的逻辑

fs/mpage.c
static struct bio *do_mpage_readpage(struct mpage_readpage_args *args)
{
    struct page *page = args->page;
    struct inode *inode = page->mapping->host;
    const unsigned blkbits = inode->i_blkbits;
    const unsigned blocks_per_page = PAGE_SIZE >> blkbits;
    const unsigned blocksize = 1 << blkbits;
    struct buffer_head *map_bh = &args->map_bh;
    sector_t block_in_file;
    sector_t last_block;
    sector_t last_block_in_file;
    sector_t blocks[MAX_BUF_PER_PAGE];
    unsigned page_block;
    unsigned first_hole = blocks_per_page;
    struct block_device *bdev = NULL;
    int length;
    int fully_mapped = 1;
    int op_flags;
    unsigned nblocks;
    unsigned relative_block;
    gfp_t gfp;

    if (args->is_readahead) {
        op_flags = REQ_RAHEAD;
        gfp = readahead_gfp_mask(page->mapping);
    } else {
        op_flags = 0;
        gfp = mapping_gfp_constraint(page->mapping, GFP_KERNEL);
    }

    if (page_has_buffers(page))
        goto confused;

    block_in_file = (sector_t)page->index << (PAGE_SHIFT - blkbits);
    last_block = block_in_file + args->nr_pages * blocks_per_page;
    last_block_in_file = (i_size_read(inode) + blocksize - 1) >> blkbits;
    if (last_block > last_block_in_file)
        last_block = last_block_in_file;
    page_block = 0;

    /*
     * Map blocks using the result from the previous get_blocks call first.
     */
    nblocks = map_bh->b_size >> blkbits;
    if (buffer_mapped(map_bh) &&
            block_in_file > args->first_logical_block &&
            block_in_file < (args->first_logical_block + nblocks)) {
        unsigned map_offset = block_in_file - args->first_logical_block;
        unsigned last = nblocks - map_offset;

        for (relative_block = 0; ; relative_block++) {
            if (relative_block == last) {
                clear_buffer_mapped(map_bh);
                break;
            }
            if (page_block == blocks_per_page)
                break;
            blocks[page_block] = map_bh->b_blocknr + map_offset +
                        relative_block;
            page_block++;
            block_in_file++;
        }
        bdev = map_bh->b_bdev;
    }                                                   /*          1        */

    /*
     * Then do more get_blocks calls until we are done with this page.
     */
    map_bh->b_page = page;
    while (page_block < blocks_per_page) {
        map_bh->b_state = 0;
        map_bh->b_size = 0;

        if (block_in_file < last_block) {
            map_bh->b_size = (last_block-block_in_file) << blkbits;
            if (args->get_block(inode, block_in_file, map_bh, 0)) /*          2        */
                goto confused;
            args->first_logical_block = block_in_file;
        }

        if (!buffer_mapped(map_bh)) {                           /*          3        */
            fully_mapped = 0;
            if (first_hole == blocks_per_page)
                first_hole = page_block;
            page_block++;
            block_in_file++;
            continue;
        }

        /* some filesystems will copy data into the page during
         * the get_block call, in which case we don't want to
         * read it again.  map_buffer_to_page copies the data
         * we just collected from get_block into the page's buffers
         * so readpage doesn't have to repeat the get_block call
         */
        if (buffer_uptodate(map_bh)) {                      /*          4       */
            map_buffer_to_page(page, map_bh, page_block);
            goto confused;
        }

        if (first_hole != blocks_per_page)                     /*          5        */
            goto confused;      /* hole -> non-hole */

        /* Contiguous blocks? */
        if (page_block && blocks[page_block-1] != map_bh->b_blocknr-1)   /*          6        */
            goto confused;
        nblocks = map_bh->b_size >> blkbits;
        for (relative_block = 0; ; relative_block++) {               /*          7        */
            if (relative_block == nblocks) {
                clear_buffer_mapped(map_bh);
                break;
            } else if (page_block == blocks_per_page)
                break;
            blocks[page_block] = map_bh->b_blocknr+relative_block;
            page_block++;
            block_in_file++;
        }
        bdev = map_bh->b_bdev;
    }

    if (first_hole != blocks_per_page) {                        /*          8        */
        zero_user_segment(page, first_hole << blkbits, PAGE_SIZE);
        if (first_hole == 0) {
            SetPageUptodate(page);                        
            unlock_page(page);
            goto out;
        }
    } else if (fully_mapped) {
        SetPageMappedToDisk(page);
    }

    if (fully_mapped && blocks_per_page == 1 && !PageUptodate(page) &&
        cleancache_get_page(page) == 0) {
        SetPageUptodate(page);
        goto confused;
    }

    /*
     * This page will go to BIO.  Do we need to send this BIO off first?
     */
    if (args->bio && (args->last_block_in_bio != blocks[0] - 1))  /*          9        */
        args->bio = mpage_bio_submit(REQ_OP_READ, op_flags, args->bio);

alloc_new:
    if (args->bio == NULL) {                                /*          10        */
        if (first_hole == blocks_per_page) {
            if (!bdev_read_page(bdev, blocks[0] << (blkbits - 9),
                                page))
                goto out;
        }
        args->bio = mpage_alloc(bdev, blocks[0] << (blkbits - 9),
                    min_t(int, args->nr_pages,
                          BIO_MAX_PAGES),
                    gfp);
        if (args->bio == NULL)
            goto confused;
    }

    length = first_hole << blkbits;
    if (bio_add_page(args->bio, page, length, 0) < length) {     /*          11        */  
        args->bio = mpage_bio_submit(REQ_OP_READ, op_flags, args->bio);
        goto alloc_new;
    }

    relative_block = block_in_file - args->first_logical_block;
    nblocks = map_bh->b_size >> blkbits;
    if ((buffer_boundary(map_bh) && relative_block == nblocks) ||
        (first_hole != blocks_per_page))
        args->bio = mpage_bio_submit(REQ_OP_READ, op_flags, args->bio);
    else
        args->last_block_in_bio = blocks[blocks_per_page - 1];
out:
    return args->bio;

confused:                                    /*          12        */
    if (args->bio)
        args->bio = mpage_bio_submit(REQ_OP_READ, op_flags, args->bio);
    if (!PageUptodate(page))
        block_read_full_page(page, args->get_block);
    else
        unlock_page(page);
    goto out;
}

先对这个函数中涉及到的变量做一些解释:

blocks_per_page: 一个page中包含的block数(一般来说是8, 因为一个page是4k, 一个block是512B)

blocksize: 一个block大小, 一般是512B

block_in_file: 文件中的逻辑sector号

blocks: 映射数组, 每个元素表示这个页面给定的逻辑sector对应的物理sector

page_block: page 中第几个sector

first_hole: 第一个文件空洞的位置(如果有空洞的话)

在这个函数调用之前, 可能已经有了其他page的bio(args->bio), 可以和即将创建的bio合成一个更大的bio下发。

(1) 这段if逻辑处理调用这个函数之前, 对应page的内容已经被部分map的情况。 对于这种情况,把对应部门的内容记录来。赋值给blocks[page_block] (2) 接来下处理page中剩余的部分。调用get_block 映射block_in_file对应的文件sector, 结果存在map_bh中。 (3) 如果没有被映射, 说明对应的sector是一个文件洞。 这种情况下, 用first_hole 记录当前的位置, 然后继续循环。

(4)某些文件系统会在 get block 函数进行映射时复制数据到页面中,这就没必要再次从磁盘读取了。调用map_buffer_to_page复制我们收集的数据到页面缓冲区中,然后跳转到标号 confused 处继续执行,这样readpage 不需要重复调用 get block。

(5) 程序继续运行,那一定是缓冲头己经有映射的情况。如果这时 first hole 已经设置,说明这个页面在经过空洞后又重新被映射,没有办法用bio 方式来提交,跳转到 confused 标号处。

(6)如果这个逻镇块不是页面的第一个,判断它是否和前一个逻辑块在磁盘上也是连续的。如果不是,也没有办法用bio 方式来提交,跳转到 confused 标号处

(7) 根据缓冲头的映射信息,记录页面每个逻辑块在磁盘上对应的块编号。如果映射信息用光,清除缓冲头的 mapped 标志,退出循环;如果这个页面的所有sector都己经处理完,也退出循环,如果此时映射信息可能还没有用光,则留给下一个页面。

(8) 页面的所有逻辑块都经过上面的处理后,可以采用 bio 方式提交的情况就清楚了。只可能会是三种情况。页面的逻辑块被映射到磁盘上连续的逻辑块,这时设置页面的映射标志;页面只有前面一些逻辑块被映射到磁盘,这时清零没有被映射部分的数据:控个页面都没有被映射,清除整个页面,并设置最新标志。

(9) 如果传入了一个bio, 需要判断本页面第一个逻街块和传入bio 的最后一个逻辑块在磁盘上是否连续,也就是看是否可以将本页面作为一个请求段加入传入的 bio 中。如果不连续,那么mpage_bio_submit提交前面的bio, 这个函数恒返回 NULL

(10) 如果传入的bio 为 NULL, 或者传入的bio 已提交,那么,我们需要调用 mpage_alloc 重新分配一个bio。

(11) 设置该bio的参数, 并将该bio返回

(12) 对于无法用bio处理的情况, 先将已有的bio提交, 然后调用block_read_full_page 通过buffer_head的方式进行读取

从文件块编号推导物理块编号

下面以exFAT文件系统为例, 讲解一下get_block这个函数的具体实现。

fs/exfat/inode.c
static int exfat_get_block(struct inode *inode, sector_t iblock,
        struct buffer_head *bh_result, int create)
{
    struct exfat_inode_info *ei = EXFAT_I(inode);
    struct super_block *sb = inode->i_sb;
    struct exfat_sb_info *sbi = EXFAT_SB(sb);
    unsigned long max_blocks = bh_result->b_size >> inode->i_blkbits;
    int err = 0;
    unsigned long mapped_blocks = 0;
    unsigned int cluster, sec_offset;
    sector_t last_block;
    sector_t phys = 0;
    loff_t pos;

    mutex_lock(&sbi->s_lock);
    last_block = EXFAT_B_TO_BLK_ROUND_UP(i_size_read(inode), sb);
    if (iblock >= last_block && !create)
        goto done;

    /* Is this block already allocated? */            
    err = exfat_map_cluster(inode, iblock >> sbi->sect_per_clus_bits,           /*        1        */
            &cluster, create);
    if (err) {
        if (err != -ENOSPC)
            exfat_fs_error_ratelimit(sb,
                "failed to bmap (inode : %p iblock : %llu, err : %d)",
                inode, (unsigned long long)iblock, err);
        goto unlock_ret;
    }

    if (cluster == EXFAT_EOF_CLUSTER)
        goto done;

    /* sector offset in cluster */
    sec_offset = iblock & (sbi->sect_per_clus - 1);

    phys = exfat_cluster_to_sector(sbi, cluster) + sec_offset;
    mapped_blocks = sbi->sect_per_clus - sec_offset;
    max_blocks = min(mapped_blocks, max_blocks);

    /* Treat newly added block / cluster */
    if (iblock < last_block)
        create = 0;

    if (create || buffer_delay(bh_result)) {
        pos = EXFAT_BLK_TO_B((iblock + 1), sb);
        if (ei->i_size_ondisk < pos)
            ei->i_size_ondisk = pos;
    }

    if (create) {
        err = exfat_map_new_buffer(ei, bh_result, pos);
        if (err) {
            exfat_fs_error(sb,
                    "requested for bmap out of range(pos : (%llu) > i_size_aligned(%llu)\n",
                    pos, ei->i_size_aligned);
            goto unlock_ret;
        }
    }

    if (buffer_delay(bh_result))
        clear_buffer_delay(bh_result);
    map_bh(bh_result, sb, phys);                             /*        2        */
done:
    bh_result->b_size = EXFAT_BLK_TO_B(max_blocks, sb);
unlock_ret:
    mutex_unlock(&sbi->s_lock);
    return err;
}

get_block 这个函数用于查找inode对应的文件中第iblock位置的sector在物理上对应那个sector, 并将结果保存在bh_result, create表示如果没有找到对应的sector, 是否进行创建。

(1) 调用exfat_map_cluster 去查找某个文件中的iblock 对应的实际的cluster。 这里面就涉及到文件系统的具体操作。

(2) 将bh_result映射到phys对应的位置

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
目录 1 虚拟文件系统概述 5 1.1 通用文件模型 7 1.2 VFS所处理的系统调用 9 2 虚拟文件系统架构 11 2.1 VFS对象数据结构 11 2.1.1 超级块对象 11 2.1.2 索引节点对象 15 2.1.3 文件对象 18 2.1.4 目录项对象 22 2.2 把Linux中的VFS对象串联起来 24 2.2.1 与进程相关的文件 25 2.2.2 索引节点高速缓存 29 2.2.3 目录项高速缓存 30 2.2.4 VFS对象的具体实现 32 2.3 文件系统的注册与安装 38 2.3.1 文件系统类型注册 38 2.3.2 文件系统安装数据结构 41 2.3.3 安装普通文件系统 52 2.3.4 分配超级块对象 58 2.3.5 安装根文件系统 60 2.3.6 卸载文件系统 65 2.4 路径名的查找 66 2.4.1 查找路径名的一般流程 67 2.4.2 父路径名查找 82 2.4.3 符号链接的查找 84 2.5 VFS系统调用的实现 88 2.5.1 open()系统调用 88 2.5.2 read()和write()系统调用 96 2.5.3 close()系统调用 97 3 第二扩展文件系统 99 3.1 Ext2磁盘数据结构 101 3.1.1 磁盘超级块 102 3.1.2 组描述符和位图 105 3.1.3 磁盘索引节点表 105 3.2 VFS接口数据结构 110 3.2.1 Ext2 超级块对象 110 3.2.2 Ext2 的索引节点对象 121 3.2.3 创建Ext2文件系统 124 3.2.4 Ext2的方法总结 126 3.3 Ext2索引节点分配 129 3.3.1 创建索引节点 130 3.3.2 删除索引节点 143 3.4 Ext2数据块分配 144 3.4.1 数据块寻址 145 3.4.2 文件的洞 147 3.4.3 分配数据块 148 4 页面高速缓存 160 4.1 页高速缓存数据结构 160 4.1.1 address_space对象 161 4.1.2 基树 164 4.2 高速缓存底层处理函数 166 4.2.1 查找页 166 4.2.2 增加页 168 4.2.3 删除页 173 4.3 文件系统与高速缓存 175 4.3.1 缓冲头数据结构 175 4.3.2 分配块设备缓冲区页 178 4.3.3 释放块设备缓冲区页 184 4.4 在页高速缓存中搜索块 185 4.4.1 __find_get_block()函数 185 4.4.2 __getblk()函数 188 4.4.3 __bread()函数 190 4.5 把脏页写入磁盘 191 4.5.1 pdflush内核线程 192 4.5.2 搜索要刷新的脏页 193 4.5.3 回写陈旧的脏页 196 5 文件读写 199 5.1 系统调用VFS层的处理 200 5.2 第二扩展文件系统Ext2层的处理 201 5.2.1 Ext2的磁盘布局 202 5.2.2 Ext2的超级块对象 206 5.2.3 Ext2索引节点对象的创建 210 5.2.4 Ext2索引节点对象的读取 218 5.2.5 Ext2层读文件入口函数 225 5.3 页高速缓存层的处理 237 5.3.1 创建一个bio请求 238 5.3.2 得到文件的逻辑块号 244 5.3.3 普通文件readpage方法 251 5.3.4 块设备文件readpage方法 252 5.3.5 文件的预读 260 5.4 通用块层的处理 264 5.4.1 块设备的基础知识 265 5.4.2 通用块层相关数据结构 269 5.4.3 提交I/O传输请求 271 5.4.4 请求队列描述符 273 5.5 块设备I/O调度层的处理 281 5.5.1 块设备的初始化 284 5.5.2 建立块设备驱动环境 288 5.5.3 关联block_device结构 295 5.5.4 为设备建立请求队列 306 5.5.5 块设备I/O调度程序 311 5.5.6 真实的I/O调度层处理 321 5.6 块设备驱动层的处理 330 5.6.1 scsi总线驱动的初始化 330 5.6.2 scsi设备驱动体系架构 342 5.6.3 scsi块设备驱动层处理 347 5.6.4 scsi命令的执行 369 5.6.5 scsi命令的第一次转变 372 5.6.6 scsi命令的第二次转变 380 5.7 写文件 384 5.7.1 generic file_write函数 384 5.7.2 普通文件的prepare_write方法 386 5.7.3 块设备文件的prepare_write方法 387 5.7.4 将脏页写到磁盘 388 6 直接I/O与异步I/O 391 6.1 直接I/O 391 6.2 异步I/O 393 6.2.1 Linux 2.6中的异步I/O 394 6.2.2 异步I/O环境 394 6.2.3 提交异步I/O操作 395
-Linux内核启动-内核的生成...........................................2 读核感悟-Linux内核启动-从hello world说起...................................3 读核感悟-Linux内核启动-BIOS.................................................5 读核感悟-Linux内核启动-setup辅助程序........................................6 读核感悟-Linux内核启动-内核解压缩...........................................8 读核感悟-Linux内核启动-开启页面映射.........................................9 读核感悟-Linux内核启动-链接脚本............................................11 读核感悟-伪装现场-系统调用参数.............................................13 读核感悟-伪装现场-fork()系统调用...........................................15 读核感悟-伪装现场-内核线程:...............................................17 读核感悟-伪装现场-信号通信.................................................19 读核感悟-kbuild系统-内核模块的编译.........................................22 读核感悟-kbuild系统-编译到内核和编译成模块的区别...........................24 读核感悟-kbuild系统-make bzImage的过程.....................................26 读核感悟-kbuild系统-make menuconfig........................................31 读核感悟-文件系统-用C来实现面向对象........................................32 读核感悟-设计模式-用C来实现虚函数表和多态..................................32 读核感悟-设计模式-用C来实现继承和模板......................................33 读核感悟-设计模式-文件系统和设备的继承和接口...............................34 读核感悟-设计模式-文件系统与抽象工厂.......................................36 读核感悟-阅读源代码技巧-查找定义...........................................37 读核感悟-阅读源代码技巧-变量命名规则.......................................42 读核感悟-内存管理-内核中的页表映射总结.....................................43 读核感悟-健壮的代码-exception table-内核中的刑事档案.......................44 读核感悟-定时器-巧妙的定时器算法...........................................45 读核感悟-内存管理-page fault处理流程.......................................45 读核感悟-文件读写-select实现原理...........................................47 读核感悟-文件读写-poll的实现原理
深入分析Linux内核源码 前言 第一章 走进linux 1.1 GNU与Linux的成长 1.2 Linux的开发模式和运作机制 1.3走进Linux内核 1.3.1 Linux内核的特征 1.3.2 Linux内核版本的变化 1.4 分析Linux内核的意义 1.4.1 开发适合自己的操作系统 1.4.2 开发高水平软件 1.4.3 有助于计算机科学的教学和科研 1.5 Linux内核结构 1.5.1 Linux内核在整个操系统中的位置 1.5.2 Linux内核的作用 1.5.3 Linux内核的抽象结构 1.6 Linux内核源代码 1.6.1 多版本的内核源代码 1.6.2 Linux内核源代码的结构 1.6.3 从何处开始阅读源代码 1.7 Linux内核源代码分析工具 1.7.1 Linux超文本交叉代码检索工具 1.7.2 Windows平台下的源代码阅读工具Source Insight 第二章 Linux运行的硬件基础 2.1 i386的寄存器 2.1.1通用寄存器 2.1.2段寄存器 2.1.3状态和控制寄存器 2.1.4 系统地址寄存器 2.1.5 调试寄存器和测试寄存器 2.2 内存地址 2.3 段机制和描述符 2.3.1 段机制 2.3.2 描述符的概念 2.3.3系统段描述符 2.3.4 描述符表 2.3.5 选择符与描述符表寄存器 2.3.6 描述符投影寄存器 2.3.7 Linux中的段 2.4 分页机制 2.4.1 分页机构 2.4.2页面高速缓存 2.5 Linux中的分页机制 2.5.1 与页相关的数据结构及宏的定义 2.5.2 对页目录及页表的处理 2.6 Linux中的汇编语言 2.6.1 AT&T与Intel汇编语言的比较 2.6.2 AT&T汇编语言的相关知识 2.6.3 Gcc嵌入式汇编 2.6.4 Intel386汇编指令摘要 第三章中断机制 3.1 中断基本知识 3.1.1 中断向量 3.1.2 外设可屏蔽中断 3.1.3异常及非屏蔽中断 3.1.4中断描述符表 3.1.5 相关汇编指令 3.2中断描述符表的初始化 3.2. 1 外部中断向量的设置 3.2.2中断描述符表IDT的预初始化 3.2.3 中断向量表的最终初始化 3.3异常处理 3.3.1 在内核栈中保存寄存器的值 3.3.2 中断请求队列的初始化 3.3.3中断请求队列的数据结构 3.4 中断处理 3.4.1中断和异常处理的硬件处理 3.4.2 Linux对异常和中断的处理 3.4.3 与堆栈有关的常量、数据结构及宏 3.4.4 中断处理程序的执行 3.4.5 从中断返回 3.5中断的后半部分处理机制 3.5.1 为什么把中断分为两部分来处理 3.5.2 实现机制 3.5.3数据结构的定义 3.5.4 软中断、bh及tasklet的初始化 3.5.5后半部分的执行 3.5.6 把bh移植到tasklet 第四章 进程描述 4.1 进程和程序(Process and Program) 4.2 Linux中的进程概述 4.3 task_struct结构描述 4.4 task_struct结构在内存中的存放 4.4.1 进程内核栈 4.4.2 当前进程(current宏) 4.5 进程组织的方式 4.5.1哈希表 4.5.2双向循环链表 4.5.3 运行队列 4.5.4 等待队列 4.6 内核线程 4.7 进程的权能 4.8 内核同步 4.8.1信号量 4.8.2原子操作 4.8.3 自旋锁、读写自旋锁和大读者自旋锁 4.9 本章小节 第五章进程调度 5.1 Linux时间系统 5.1.1 时钟硬件 5.1.2 时钟运作机制 5.1.3 Linux时间基准 5.1.4 Linux的时间系统 5.2 时钟中断 5.2.1 时钟中断的产生 5.2.2.Linux实现时钟中断的全过程 5.3 Linux的调度程序-Schedule( ) 5.3.1 基本原理 5.3.2 Linux进程调度时机 5.3.3 进程调度的依据 5.3.4 进程可运行程度的衡量 5.3.5 进程调度的实现 5.4 进程切换 5.4.1 硬件支持 5.4.2 进程切换 第六章 Linux内存管理 6.1 Linux的内存管理概述 6.1.1 Linux虚拟内存的实现结构 6.1.2 内核空间和用户空间 6.1.3 虚拟内存实现机制间的关系 6.2 Linux内存管理的初始化 6.2.1 启用分页机制 6.2.2 物理内存的探测 6.2.3 物理内存的描述 6.2.4 页面管理机制的初步建立 6.2.5页表的建立 6.2.6内存管理区 6.3 内存的分配和回收 6.3.1 伙伴算法 6.3.2 物理页面的分配和释放 6.3.3 Slab分配机制 6.4 地址映射机制 6.4.1 描述虚拟空间的数据结构 6.4.2 进程的虚拟空间 6.4.3 内存映射 6.5 请页机制 6.5.1 页故障的产生 6.5.2 页错误的定位 6.5.3 进程地址空间中的缺页异常处理 6.5.4 请求调页 6.5.5 写时复制 6.6 交换机制 6.6.1 交换的基本原理 6.6.2 页面交换守护进程kswapd 6.6.3 交换空间的数据结构 6.6.4 交换空间的应用 6.7 缓存和刷新机制 6.7.1 Linux使用的缓存 6.7.2 缓冲区高速缓存 6.7.3 翻译后援存储器(TLB) 6.7.4 刷新机制 6.8 进程的创建和执行 6.8.1 进程的创建 6.8.2 程序执行 6.8.3 执行函数 第七章 进程间通信 7.1 管道 7.1.1 Linux管道的实现机制 7.1.2 管道的应用 7.1.3 命名管道(FIFO) 7.2 信号(signal) 7.2.1 信号种类 7.2.2 信号掩码 7.2.3 系统调用 7.2.4 典型系统调用的实现 7.2.5 进程与信号的关系 7.2.6 信号举例 7.3 System V 的IPC机制 7.3.1 信号量 7.3.2 消息队列 7.3.3 共享内存 第八章 虚拟文件系统 8.1 概述 8.2 VFS中的数据结构 8.2.1 超级块 8.2.2 VFS的索引节点 8.2.3 目录项对象 8.2.4 与进程相关的文件结构 8.2.5 主要数据结构间的关系 8.2.6 有关操作的数据结构 8.3 高速缓存 8.3.1 块高速缓存 8.3.2 索引节点高速缓存 8.3.3 目录高速缓存 8.4 文件系统的注册、安装与拆卸 8.4.1 文件系统的注册 8.4.2 文件系统的安装 8.4.3 文件系统的卸载 8.5 限额机制 8.6 具体文件系统举例 8.6.1 管道文件系统pipefs 8.6.2 磁盘文件系统BFS 8.7 文件系统的系统调用 8.7.1 open 系统调用 8.7.2 read 系统调用 8.7.3 fcntl 系统调用 8 .8 Linux2.4文件系统的移植问题 第九章 Ext2文件系统 9.1 基本概念 9.2 Ext2的磁盘布局和数据结构 9.2.1 Ext2的磁盘布局 9.2.2 Ext2的超级块 9.2.3 Ext2的索引节点 9.2.4 组描述符 9.2.5 位图 9.2.6 索引节点表及实例分析 9.2.7 Ext2的目录项及文件的定位 9.3 文件的访问权限和安全 9.4 链接文件 9.5 分配策略 9.5.1 数据块寻址 9.5.2 文件的洞 9.5.3 分配一个数据块 第十章 模块机制 10.1 概述 10.1.1 什么是模块 10.1.2 为什么要使用模块? 10.2 实现机制 10.2.1 数据结构 10.2.2 实现机制的分析 10.3 模块的装入和卸载 10.3.1 实现机制 10.3.2 如何插入和卸载模块 10.4 内核版本 10.4.1 内核版本与模块版本的兼容性 10.4.2 从版本2.0到2.2内核API的变化 10.4.3 把内核2.2移植到内核2.4 10.5 编写内核模块 10.5.1 简单内核模块的编写 10.5.2 内核模块的Makefiles文件 10.5.3 内核模块的多个文件 第十一章 设备驱动程序 11.1 概述 11.1.1 I/O软件 11.1.2 设备驱动程序 11.2 设备驱动基础 11.2.1 I/O端口 11.2.2 I/O接口及设备控制器 11.2.3 设备文件 11.2.4 VFS对设备文件的处理 11.2.5 中断处理 11.2.6 驱动DMA工作 11.2.7 I/O 空间的映射 11.2.8 设备驱动程序框架 11.3 块设备驱动程序 11.3.1 块设备驱动程序的注册 11.3.2 块设备基于缓冲区的数据交换 11.3.3 块设备驱动程序的几个函数 11.3.4 RAM 盘驱动程序的实现 11.3.5 硬盘驱动程序的实现 11.4 字符设备驱动程序 11.4.1 简单字符设备驱动程序 11.4.2 字符设备驱动程序的注册 11.4.3 一个字符设备驱动程序的实例 11.4.4 驱动程序的编译与装载 第十二章 网络 12.1 概述 12.2 网络协议 12.2.1 网络参考模型 12.2.2 TCP/IP 协议工作原理及数据流 12.2.3 Internet 协议 12.2.4 TCP协议 12.3 套接字(socket) 12.3.1 套接字在网络中的地位和作用 12.3.2 套接字接口的种类 12.3.3 套接字的工作原理 12.3.4 socket 的通信过程 12.3.5 socket为用户提供的系统调用 12.4 套接字缓冲区(sk_buff) 12.4.1 套接字缓冲区的特点 12.4.2 套接字缓冲区操作基本原理 12.4.3 sk_buff数据结构的核心内容 12.4.4 套接字缓冲区提供的函数 12.4.5 套接字缓冲区的上层支持例程 12.5 网络设备接口 12.5.1 基本结构 12.5.2 命名规则 12.5.3 设备注册 12.5.4 网络设备数据结构 12.5.5 支持函数 第十三章 启动系统 13.1 初始化流程 13.1.1 系统加电或复位 13.1.2 BIOS启动 13.1.3 Boot Loader 13.1.4 操作系统的初始化 13.2 初始化的任务 13.2.1 处理器对初始化的影响 13.2.2 其他硬件设备对处理器的影响 13.3 Linux 的Boot Loarder 13.3.1 软盘的结构 13.3.2 硬盘的结构 13.3.3 Boot Loader 13.3.4 LILO 13.3.5 LILO的运行分析 13.4 进入操作系统 13.4.1 Setup.S 13.4.2 Head.S 13.5 main.c中的初始化 13.6 建立init进程 13.6.1 init进程的建立 13.6.2 启动所需的Shell脚本文件 附录: 1 Linux 2.4内核API 2.1 驱动程序的基本函数 2.2 双向循环链表的操作 2.3 基本C库函数 2.4 Linux内存管理中Slab缓冲区 2.5 Linux中的VFS 2.6 Linux的连网 2.7 网络设备支持 2.8 模块支持 2.9 硬件接口 2.10 块设备 2.11 USB 设备 2 参考文献
[14本经典Android开发教程] 8 Linux内核阅读心得体会 读核感悟 2 读核感悟 Linux内核启动 内核的生成 2 读核感悟 Linux内核启动 从hello world说起 3 读核感悟 Linux内核启动 BIOS 5 读核感悟 Linux内核启动 setup辅助程序 6 读核感悟 Linux内核启动 内核解压缩 8 读核感悟 Linux内核启动 开启页面映射 9 读核感悟 Linux内核启动 链接脚本 11 读核感悟 伪装现场 系统调用参数 13 读核感悟 伪装现场 fork 系统调用 15 读核感悟 伪装现场 内核线程: 17 读核感悟 伪装现场 信号通信 19 读核感悟 kbuild系统 内核模块的编译 22 读核感悟 kbuild系统 编译到内核和编译成模块的区别 24 读核感悟 kbuild系统 make bzImage的过程 26 读核感悟 kbuild系统 make menuconfig 31 读核感悟 文件系统 用C来实现面向对象 32 读核感悟 设计模式 用C来实现虚函数表和多态 32 读核感悟 设计模式 用C来实现继承和模板 33 读核感悟 设计模式 文件系统和设备的继承和接口 34 读核感悟 设计模式 文件系统与抽象工厂 36 读核感悟 阅读源代码技巧 查找定义 37 读核感悟 阅读源代码技巧 变量命名规则 42 读核感悟 内存管理 内核中的页表映射总结 43 读核感悟 健壮的代码 exception table 内核中的刑事档案 44 读核感悟 定时器 巧妙的定时器算法 45 读核感悟 内存管理 page fault处理流程 45 读核感悟 文件读写 select实现原理 47 读核感悟 文件读写 poll的实现原理 49 1 功能介绍: 49 2 关键的结构体: 49 3 poll的实现 49 4 性能分析: 50 读核感悟 文件读写 epoll的实现原理 50 1 功能介绍 50 2 关键结构体: 51 3 epoll create的实现 53 4 epoll ctl的实现 53 5 epoll wait的实现 54 6 性能分析 54 读核感悟 同步问题 同步问题概述 55 1 同步问题的产生背景 55 2 内核态与用户态的区别 55 读核感悟 同步问题 内核态自旋锁的实现 56 1自旋锁的总述 56 2非抢占式的自旋锁 56 3 锁的释放 57 4 与用户态的自旋锁的比较 57 5 总结 58 读核感悟 内存管理 free命令详解 58 读核感悟 文件读写 2 6 9内核中的AIO 59 1 AIO概述 59 2 内核态AIO的使用 61 读核感悟 文件读写 内核态AIO相关结构体 61 1 内核态AIO操作相关信息 61 2 AIO上下文: 63 3 AIO ring 63 4 异步I O事件的返回信息 64 读核感悟 文件读写 内核态AIO创建和提交操作 65 1 AIO上下文的创建 io setup 65 2 AIO请求的提交:io submit实现机制 66 读核感悟 文件操作 AIO操作的执行 66 1 在提交时执行AIO 66 2 在工作队列中执行AIO 66 3 负责AIO执行的核心函数aio run iocb 67 4 AIO操作的完成 67 读核感悟 文件读写 内核态是否支持非direct I O方式的AIO 67 已上传7本: [14本经典Android开发教程] 1 Android开发从入门到精通 http: download csdn net detail cleopard 8355245 [14本经典Android开发教程] 2 Android开发手册 API函数详解 http: download csdn net detail cleopard 8374487 [14本经典Android开发教程] 3 Android SDK 中文开发文档 http: download csdn net detail cleopard 8380429 [14本经典Android开发教程] 4 Android应用程序开发36技 http: download csdn net detail cleopard 8380495 [14本经典Android开发教程] 5 linux Android基础知识总结 http: download csdn net detail cleopard 8380529 [14本经典Android开发教程] 6 Android驱动开发入门及手机案例开发分析教程 http: download csdn net detail cleopard 8388019 [14本经典Android开发教程] 7 Android编程入门教程 http: download csdn net detail cleopard 8388043 剩余8本稍后上传 @或直接从这里寻找@ http: download csdn net user cleopard album @更多@ http: cleopard download csdn net 福利 http: xuemeilaile com 17份软件测试文档 http: download csdn net album detail 1425 13份WPF经典开发教程 http: download csdn net album detail 1115 C#资料合辑二[C#桌面编程入门篇] http: download csdn net album detail 957 C#资料合辑一[C#入门篇] http: download csdn net album detail 669 [Csharp高级编程 第6版 ] 共8压缩卷 http: download csdn net album detail 667 10个[精品资源]Java学习资料合辑[一] http: download csdn net album detail 663 10个C#Socket编程代码示例 http: download csdn net album detail 631 6份GDI+程序设计资源整合[全零分] http: download csdn net album detail 625 2014年移动游戏行业数据分析 http: download csdn net detail cleopard 8340331 一文读懂2014年全球互联网广告新生态 http: download csdn net detail cleopard 8340303">[14本经典Android开发教程] 8 Linux内核阅读心得体会 读核感悟 2 读核感悟 Linux内核启动 内核的生成 2 读核 [更多]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

简说Linux内核

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值