Ext3文件读写流程概述

Ext3文件系统在进行读写操作的时候,首先需要open相应的文件,然后再进行读写操作。在open操作时,Linux kernel会创建一个file对象描述这个文件。File对象和文件的dentry和inode对象建立联系,并且将ext3的文件操作方法、映射处理方法(address space)注册到file对象中。
Ext3文件读写过程会涉及到VFS层的page cache,并且通常的读写操作都会使用到这层page cache,目的是提高磁盘的IO性能。在Linux中后台会运行writeback线程定时同步pagecache和设备之间的数据。Page cache的方式虽然能够提高IO性能,但是也对数据的安全性带来了潜在影响。
本文的目的是分析ext3文件系统读写流程中的关键函数,对于page cache原理以及writeback机制将在后继文章中做深入分析。
关键数据结构
File数据结构是Linux用来描述文件的关键数据结构,该对象在一个文件被进程打开的时候被创建。当一个文件被关闭的时候,file对象也会被立即销毁。file数据结构不会被作为元数据信息持久化保存至设备。该数据结构定义如下:
  1. struct file {
  2. /*
  3. * fu_list becomes invalid after file_free is called and queued via
  4. * fu_rcuhead for RCU freeing
  5. */
  6. union {
  7. struct list_head    fu_list;
  8. struct rcu_head     fu_rcuhead;
  9. } f_u;
  10. struct path     f_path;     /* 文件路径,包含文件dentry目录项和vfsmount信息 */
  11. #define f_dentry    f_path.dentry
  12. #define f_vfsmnt    f_path.mnt
  13. const struct file_operations    *f_op;  /* 文件操作函数集 */
  14. /*
  15. * Protects f_ep_links, f_flags, f_pos vs i_size in lseek SEEK_CUR.
  16. * Must not be taken from IRQ context.
  17. */
  18. spinlock_t      f_lock;
  19. #ifdef CONFIG_SMP
  20. int         f_sb_list_cpu;
  21. #endif
  22. atomic_long_t       f_count;
  23. unsigned int        f_flags;
  24. fmode_t         f_mode; /* 文件操作模式 */
  25. loff_t          f_pos;
  26. struct fown_struct  f_owner;
  27. const struct cred   *f_cred;
  28. struct file_ra_state    f_ra;
  29. u64         f_version;
  30. #ifdef CONFIG_SECURITY
  31. void            *f_security;
  32. #endif
  33. /* needed for tty driver, and maybe others */
  34. void            *private_data;
  35. #ifdef CONFIG_EPOLL
  36. /* Used by fs/eventpoll.c to link all the hooks to this file */
  37. struct list_head    f_ep_links;
  38. struct list_head    f_tfile_llink;
  39. #endif /* #ifdef CONFIG_EPOLL */
  40. struct address_space    *f_mapping; /* address space映射信息,指向inode中的i_mapping */
  41. #ifdef CONFIG_DEBUG_WRITECOUNT
  42. unsigned long f_mnt_write_state;
  43. #endif
  44. };

每个文件在内存中都会对应一个inode对象。在设备上也会保存每个文件的inode元数据信息,通过inode元数据信息可以找到该文件所占用的所有文件数据块(block)。VFS定义了一个通用的inode数据结构,同时ext3定义了ext3_inode元数据结构。在创建内存inode对象时,需要采用ext3_inode元数据信息初始化inode对象。Inode数据结构定义如下:

  1. struct inode {
  2. umode_t         i_mode;
  3. unsigned short      i_opflags;
  4. uid_t           i_uid;
  5. gid_t           i_gid;
  6. unsigned int        i_flags;
  7. #ifdef CONFIG_FS_POSIX_ACL
  8. struct posix_acl    *i_acl;
  9. struct posix_acl    *i_default_acl;
  10. #endif
  11. const struct inode_operations   *i_op;  /* inode操作函数集 */
  12. struct super_block  *i_sb;      /* 指向superblock */
  13. struct address_space    *i_mapping; /* 指向当前使用的页缓存的映射信息 */
  14. #ifdef CONFIG_SECURITY
  15. void            *i_security;
  16. #endif
  17. /* Stat data, not accessed from path walking */
  18. unsigned long       i_ino;
  19. /*
  20. * Filesystems may only read i_nlink directly.  They shall use the
  21. * following functions for modification:
  22. *
  23. *    (set|clear|inc|drop)_nlink
  24. *    inode_(inc|dec)_link_count
  25. */
  26. union {
  27. const unsigned int i_nlink;
  28. unsigned int __i_nlink;
  29. };
  30. dev_t           i_rdev;     /* 设备号,major&minor */
  31. struct timespec     i_atime;
  32. struct timespec     i_mtime;
  33. struct timespec     i_ctime;
  34. spinlock_t      i_lock; /* i_blocks, i_bytes, maybe i_size */
  35. unsigned short          i_bytes;
  36. blkcnt_t        i_blocks;   /* 文件块数量 */
  37. loff_t          i_size;
  38. #ifdef __NEED_I_SIZE_ORDERED
  39. seqcount_t      i_size_seqcount;
  40. #endif
  41. /* Misc */
  42. unsigned long       i_state;
  43. struct mutex        i_mutex;
  44. unsigned long       dirtied_when;   /* jiffies of first dirtying */
  45. struct hlist_node   i_hash; /* 连接到inode Hash Table中 */
  46. struct list_head    i_wb_list;  /* backing dev IO list */
  47. struct list_head    i_lru;      /* inode LRU list */
  48. struct list_head    i_sb_list;
  49. union {
  50. struct list_head    i_dentry;
  51. struct rcu_head     i_rcu;
  52. };
  53. atomic_t        i_count;
  54. unsigned int        i_blkbits;  /* 块大小,通常磁盘块大小为512字节,因此i_blkbits为9 */
  55. u64         i_version;
  56. atomic_t        i_dio_count;
  57. atomic_t        i_writecount;
  58. const struct file_operations    *i_fop; /* former ->i_op->default_file_ops,文件操作函数集 */
  59. struct file_lock    *i_flock;
  60. struct address_space    i_data; /* 页高速缓存映射信息 */
  61. #ifdef CONFIG_QUOTA
  62. struct dquot        *i_dquot[MAXQUOTAS];
  63. #endif
  64. struct list_head    i_devices;
  65. union {
  66. struct pipe_inode_info  *i_pipe;        /* 管道设备 */
  67. struct block_device *i_bdev;    /* block device块设备 */
  68. struct cdev     *i_cdev;    /* 字符设备 */
  69. };
  70. __u32           i_generation;
  71. #ifdef CONFIG_FSNOTIFY
  72. __u32           i_fsnotify_mask; /* all events this inode cares about */
  73. struct hlist_head   i_fsnotify_marks;
  74. #endif
  75. #ifdef CONFIG_IMA
  76. atomic_t        i_readcount; /* struct files open RO */
  77. #endif
  78. void            *i_private; /* fs or device private pointer */
  79. };
读过程源码分析
Ext3文件系统读过程相对比较简单,函数调用关系如下图所示:

读过程可以分为两大类:Direct_io方式和page_cache方式。对于Direct_io方式,首先通过filemap_write_and_wait_range函数将page cache中的数据与设备同步并且无效掉page cache中的内容,然后再通过ext3提供的direct_io方法从设备读取数据。
另一种是直接从page cache中获取数据,通过do_generic_file_read函数实现该方式。该函数的主要流程说明如下:
1,通过读地址从page cache的radix树中获取相应的page页。
2,如果对应的page页不存在,那么需要创建一个page,然后再从设备读取相应的数据更新至page页。
3,当page页准备完毕之后,从页中拷贝数据至用户空间,page_cache方式的读操作完成。
写过程源码分析
Ext3的写过程主要分为direct_io写过程和page cache写过程两大类,整个写过程的函数调用关系如下图所示:

写操作的核心函数是__generic_file_aio_write,该函数实现如下:
  1. ssize_t __generic_file_aio_write(struct kiocb *iocb, const struct iovec *iov,
  2. unsigned long nr_segs, loff_t *ppos)
  3. {
  4. struct file *file = iocb->ki_filp;
  5. /* 获取address space映射信息 */
  6. struct address_space * mapping = file->f_mapping;
  7. size_t ocount;      /* original count */
  8. size_t count;       /* after file limit checks */
  9. struct inode    *inode = mapping->host; /* 获取文件inode索引节点 */
  10. loff_t      pos;
  11. ssize_t     written;
  12. ssize_t     err;
  13. ocount = 0;
  14. /* 检验数据区域是否存在问题,数据由iov数据结构管理 */
  15. err = generic_segment_checks(iov, &nr_segs, &ocount, VERIFY_READ);
  16. if (err)
  17. return err;
  18. /* ocount为可以写入的数据长度 */
  19. count = ocount;
  20. pos = *ppos;
  21. vfs_check_frozen(inode->i_sb, SB_FREEZE_WRITE);
  22. /* We can write back this queue in page reclaim */
  23. current->backing_dev_info = mapping->backing_dev_info;
  24. written = 0;
  25. /* 边界检查,需要判断写入数据是否超界、小文件边界检查以及设备是否是read-only。如果超界,那么降低写入数据长度 */
  26. err = generic_write_checks(file, &pos, &count, S_ISBLK(inode->i_mode));
  27. if (err)
  28. goto out;
  29. /* count为实际可以写入的数据长度,如果写入数据长度为0,直接结束 */
  30. if (count == 0)
  31. goto out;
  32. err = file_remove_suid(file);
  33. if (err)
  34. goto out;
  35. file_update_time(file);
  36. /* coalesce the iovecs and go direct-to-BIO for O_DIRECT */
  37. if (unlikely(file->f_flags & O_DIRECT)) {
  38. /* Direct IO操作模式,该模式会bypass Page Cache,直接将数据写入磁盘设备 */
  39. loff_t endbyte;
  40. ssize_t written_buffered;
  41. /* 将对应page cache无效掉,然后将数据直接写入磁盘 */
  42. written = generic_file_direct_write(iocb, iov, &nr_segs, pos,
  43. ppos, count, ocount);
  44. if (written < 0 || written == count)
  45. /* 所有数据已经写入磁盘,正确返回 */
  46. goto out;
  47. /*
  48. * direct-io write to a hole: fall through to buffered I/O
  49. * for completing the rest of the request.
  50. */
  51. pos += written;
  52. count -= written;
  53. /* 有些请求由于没有和块大小(通常为512字节)对齐,那么将无法正确完成direct-io操作。在__blockdev_direct_IO 函数中会检查逻辑地址是否和块大小对齐,__blockdev_direct_IO无法处理不对齐的请求。另外,在ext3逻辑地址和物理块地址映射操作函数ext3_get_block返回失败时,无法完成buffer_head的映射,那么request请求也将无法得到正确处理。所有没有得到处理的请求通过 buffer写的方式得到处理。从这点来看,direct_io并没有完全bypass page cache,在有些情况下是一种写无效模式。generic_file_buffered_write函数完成buffer写,将数据直接写入page cache */
  54. written_buffered = generic_file_buffered_write(iocb, iov,
  55. nr_segs, pos, ppos, count,
  56. written);
  57. /*
  58. * If generic_file_buffered_write() retuned a synchronous error
  59. * then we want to return the number of bytes which were
  60. * direct-written, or the error code if that was zero.  Note
  61. * that this differs from normal direct-io semantics, which
  62. * will return -EFOO even if some bytes were written.
  63. */
  64. if (written_buffered < 0) {
  65. /* 如果page cache写失败,那么返回写成功的数据长度 */
  66. err = written_buffered;
  67. goto out;
  68. }
  69. /*
  70. * We need to ensure that the page cache pages are written to
  71. * disk and invalidated to preserve the expected O_DIRECT
  72. * semantics.
  73. */
  74. endbyte = pos + written_buffered - written - 1;
  75. /* 将page cache中的数据同步到磁盘 */
  76. err = filemap_write_and_wait_range(file->f_mapping, pos, endbyte);
  77. if (err == 0) {
  78. written = written_buffered;
  79. /* 将page cache无效掉,保证下次读操作从磁盘获取数据 */
  80. invalidate_mapping_pages(mapping,
  81. pos >> PAGE_CACHE_SHIFT,
  82. endbyte >> PAGE_CACHE_SHIFT);
  83. } else {
  84. /*
  85. * We don't know how much we wrote, so just return
  86. * the number of bytes which were direct-written
  87. */
  88. }
  89. } else {
  90. /* 将数据写入page cache。绝大多数的ext3写操作都会采用page cache写方式,通过后台writeback线程将page cache同步到硬盘 */
  91. written = generic_file_buffered_write(iocb, iov, nr_segs,
  92. pos, ppos, count, written);
  93. }
  94. out:
  95. current->backing_dev_info = NULL;
  96. return written ? written : err;
  97. }

从__generic_file_aio_write函数可以看出,ext3写操作主要分为两大类:一类为direct_io;另一类为buffer_io (page cache write)。Direct IO可以bypass page cache,直接将数据写入设备。下面首先分析一下direct_io的处理流程。

如果操作地址对应的page页存在于page cache中,那么首先需要将这些page页中的数据同磁盘进行同步,然后将这些page缓存页无效掉,从而保证后继读操作能够从磁盘获取最新数据。在代码实现过程中,还需要考虑预读机制引入的page缓存页,所以在数据写入磁盘之后,需要再次查找page cache的radix树,保证写入的地址范围没有数据被缓存。
Generic_file_direct_write是处理direct_io的主要函数,该函数的实现如下:
  1. ssize_t
  2. generic_file_direct_write(struct kiocb *iocb, const struct iovec *iov,
  3. unsigned long *nr_segs, loff_t pos, loff_t *ppos,
  4. size_t count, size_t ocount)
  5. {
  6. struct file *file = iocb->ki_filp;
  7. struct address_space *mapping = file->f_mapping;
  8. struct inode    *inode = mapping->host;
  9. ssize_t     written;
  10. size_t      write_len;
  11. pgoff_t     end;
  12. if (count != ocount)
  13. *nr_segs = iov_shorten((struct iovec *)iov, *nr_segs, count);
  14. write_len = iov_length(iov, *nr_segs);
  15. end = (pos + write_len - 1) >> PAGE_CACHE_SHIFT;
  16. /* 将对应区域page cache中的新数据页刷新到设备,这个操作是同步的 */
  17. written = filemap_write_and_wait_range(mapping, pos, pos + write_len - 1);
  18. if (written)
  19. goto out;
  20. /*
  21. * After a write we want buffered reads to be sure to go to disk to get
  22. * the new data.  We invalidate clean cached page from the region we're
  23. * about to write.  We do this *before* the write so that we can return
  24. * without clobbering -EIOCBQUEUED from ->direct_IO().
  25. */
  26. /* 将page cache对应page 缓存无效掉,这样可以保证后继的读操作能从磁盘获取最新数据 */
  27. if (mapping->nrpages) {
  28. /* 无效对应的page缓存 */
  29. written = invalidate_inode_pages2_range(mapping,
  30. pos >> PAGE_CACHE_SHIFT, end);
  31. /*
  32. * If a page can not be invalidated, return 0 to fall back
  33. * to buffered write.
  34. */
  35. if (written) {
  36. if (written == -EBUSY)
  37. return 0;
  38. goto out;
  39. }
  40. }
  41. /* 调用ext3文件系统的direct io方法,将数据写入磁盘 */
  42. written = mapping->a_ops->direct_IO(WRITE, iocb, iov, pos, *nr_segs);
  43. /*
  44. * Finally, try again to invalidate clean pages which might have been
  45. * cached by non-direct readahead, or faulted in by get_user_pages()
  46. * if the source of the write was an mmap'ed region of the file
  47. * we're writing.  Either one is a pretty crazy thing to do,
  48. * so we don't support it 100%.  If this invalidation
  49. * fails, tough, the write still worked...
  50. */
  51. /* 再次无效掉由于预读操作导致的对应地址的page cache缓存页 */
  52. if (mapping->nrpages) {
  53. invalidate_inode_pages2_range(mapping,
  54. pos >> PAGE_CACHE_SHIFT, end);
  55. }
  56. if (written > 0) {
  57. pos += written;
  58. if (pos > i_size_read(inode) && !S_ISBLK(inode->i_mode)) {
  59. i_size_write(inode, pos);
  60. mark_inode_dirty(inode);
  61. }
  62. *ppos = pos;
  63. }
  64. out:
  65. return written;
  66. }

generic_file_direct_write函数中刷新page cache的函数调用关系描述如下:

filemap_write_and_wait_range à__filemap_fdatawrite_rangeà do_writepages
do_writepages函数的作用是将page页中的数据同步到设备,该函数实现如下:
  1. int do_writepages(struct address_space *mapping, struct writeback_control *wbc)
  2. {
  3. int ret;
  4. if (wbc->nr_to_write <= 0)
  5. return 0;
  6. if (mapping->a_ops->writepages)
  7. /* 如果文件系统定义了writepages方法,调用该方法刷新page cache页 */
  8. ret = mapping->a_ops->writepages(mapping, wbc);
  9. else
  10. /* ext3没有定义writepages方法,因此调用generic_writepages()函数将page cache中的脏页刷新到磁盘 */
  11. ret = generic_writepages(mapping, wbc);
  12. return ret;
  13. }

从上述分析可以看出,direct_io需要块大小对齐,否则还会调用page cache的路径。为了提高I/O性能,通常情况下ext3都会采用page cache异步写的方式。这也就是ext3的第二种写操作方式,该方式实现的关键函数是generic_file_buffered_write,其实现如下:

  1. ssize_t
  2. generic_file_buffered_write(struct kiocb *iocb, const struct iovec *iov,
  3. unsigned long nr_segs, loff_t pos, loff_t *ppos,
  4. size_t count, ssize_t written)
  5. {
  6. struct file *file = iocb->ki_filp;
  7. ssize_t status;
  8. struct iov_iter i;
  9. iov_iter_init(&i, iov, nr_segs, count, written);
  10. /* 执行page cache写操作 */
  11. status = generic_perform_write(file, &i, pos);
  12. if (likely(status >= 0)) {
  13. written += status;
  14. *ppos = pos + status;
  15. }
  16. return written ? written : status;
  17. }

generic_file_buffered_write其实是对generic_perform_write函数的封装,generic_perform_write实现了page cache写的所有流程,该函数实现如下:

  1. static ssize_t generic_perform_write(struct file *file,
  2. struct iov_iter *i, loff_t pos)
  3. {
  4. struct address_space *mapping = file->f_mapping;
  5. const struct address_space_operations *a_ops = mapping->a_ops;  /* 映射处理函数集 */
  6. long status = 0;
  7. ssize_t written = 0;
  8. unsigned int flags = 0;
  9. /*
  10. * Copies from kernel address space cannot fail (NFSD is a big user).
  11. */
  12. if (segment_eq(get_fs(), KERNEL_DS))
  13. flags |= AOP_FLAG_UNINTERRUPTIBLE;
  14. do {
  15. struct page *page;
  16. unsigned long offset;   /* Offset into pagecache page */
  17. unsigned long bytes;    /* Bytes to write to page */
  18. size_t copied;      /* Bytes copied from user */
  19. void *fsdata;
  20. offset = (pos & (PAGE_CACHE_SIZE - 1));
  21. bytes = min_t(unsigned long, PAGE_CACHE_SIZE - offset,
  22. iov_iter_count(i));
  23. again:
  24. /*
  25. * Bring in the user page that we will copy from _first_.
  26. * Otherwise there's a nasty deadlock on copying from the
  27. * same page as we're writing to, without it being marked
  28. * up-to-date.
  29. *
  30. * Not only is this an optimisation, but it is also required
  31. * to check that the address is actually valid, when atomic
  32. * usercopies are used, below.
  33. */
  34. if (unlikely(iov_iter_fault_in_readable(i, bytes))) {
  35. status = -EFAULT;
  36. break;
  37. }
  38. /* 调用ext3中的write_begin函数(inode.c中)ext3_write_begin, 如果写入的page页不存在,那么ext3_write_begin会创建一个Page页,然后从硬盘中读入相应的数据 */
  39. status = a_ops->write_begin(file, mapping, pos, bytes, flags,
  40. &page, &fsdata);
  41. if (unlikely(status))
  42. break;
  43. if (mapping_writably_mapped(mapping))
  44. flush_dcache_page(page);
  45. pagefault_disable();
  46. /* 将数据拷贝到page cache中 */
  47. copied = iov_iter_copy_from_user_atomic(page, i, offset, bytes);
  48. pagefault_enable();
  49. flush_dcache_page(page);
  50. mark_page_accessed(page);
  51. /* 调用ext3的write_end函数(inode.c中),写完数据之后会将page页标识为dirty,后台writeback线程会将dirty page刷新到设备 */
  52. status = a_ops->write_end(file, mapping, pos, bytes, copied,
  53. page, fsdata);
  54. if (unlikely(status < 0))
  55. break;
  56. copied = status;
  57. cond_resched();
  58. iov_iter_advance(i, copied);
  59. if (unlikely(copied == 0)) {
  60. /*
  61. * If we were unable to copy any data at all, we must
  62. * fall back to a single segment length write.
  63. *
  64. * If we didn't fallback here, we could livelock
  65. * because not all segments in the iov can be copied at
  66. * once without a pagefault.
  67. */
  68. bytes = min_t(unsigned long, PAGE_CACHE_SIZE - offset,
  69. iov_iter_single_seg_count(i));
  70. goto again;
  71. }
  72. pos += copied;
  73. written += copied;
  74. balance_dirty_pages_ratelimited(mapping);
  75. if (fatal_signal_pending(current)) {
  76. status = -EINTR;
  77. break;
  78. }
  79. } while (iov_iter_count(i));
  80. return written ? written : status;
  81. }

本文出自 “存储之道” 博客,请务必保留此出处http://alanwu.blog.51cto.com/3652632/1106506

转载于:https://www.cnblogs.com/CosyAndStone/p/3261114.html

Ext3文件读写流程概述相关推荐

  1. HDFS文件读写流程

    1.HDFS文件读取流程: 2.HDFS写入文件流程 转载于:https://www.cnblogs.com/shijiaoyun/p/5790344.html

  2. linux存储--从内核文件系统看文件读写过程(四)

    系统调用 操作系统的主要功能是为管理硬件资源和为应用程序开发人员提供良好的环境,但是计算机系统的各种硬件资源是有限的,因此为了保证每一个进程都能安全的执行.处理器设有两种模式:"用户模式&q ...

  3. 转 从内核文件系统看文件读写过程

    系统调用 操作系统的主要功能是为管理硬件资源和为应用程序开发人员提供良好的环境,但是计算机系统的各种硬件资源是有限的,因此为了保证每一个进程都能安全的执行.处理器设有两种模式:"用户模式&q ...

  4. 从内核文件系统看文件读写过程

    阅读目录 系统调用 虚拟文件系统 I/O 缓冲区 Page Cache Address Space 文件读写基本流程 系统调用 操作系统的主要功能是为管理硬件资源和为应用程序开发人员提供良好的环境,但 ...

  5. Linux文件读写机制及优化方式

    本文只讨论Linux下文件的读写机制,不涉及不同读取方式如read,fread,cin等的对比,这些读取方式本质上都是调用系统api read,只是做了不同封装.以下所有测试均使用open, read ...

  6. HDFS详解(架构设计、副本放置策略、读写流程、进程、常用命令等)

    前言:当数据集的大小超过一台独立的物理计算机的存储能力时,就有必要对它进行分区(Partition)并存储到若干台单独的计算机上.管理网络中跨多台计算机存储的文件系统成为分布式文件系统(distrib ...

  7. linux 文件缓存大小设置,Linux文件读写机制及优化方式

    导读 Linux是一个可控性强的,安全高效的操作系统.本文只讨论Linux下文件的读写机制,不涉及不同读取方式如read,fread,cin等的对比,这些读取方式本质上都是调用系统api read,只 ...

  8. 【ceph】CEPH源码解析:读写流程

    相同过程 Ceph的读/写操作采用Primary-Replica模型,客户端只向Object所对应OSD set的Primary OSD发起读/写请求,这保证了数据的强一致性.当Primary OSD ...

  9. python数据库文件读写(超详解)

    目录 一:文件读写流程 二:函数open()中参数mode最常见的6种模式 三:读取文件三大方法的区别 四:超大文件高效处理 思维导图: 1.文件读写流程:                 (1):第 ...

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

    F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 二.文件数据的存储以及读写 F2FS文件数据组织方式 一般文件写流程 一般文件读流程 目录文件读流程(未完成) 目录文件写流程(未完成 ...

最新文章

  1. Cell二连发 | 广东CDC/耶鲁大学利用纳米孔测序揭示中/美新冠病毒基因组流行病学传播规律...
  2. 为什么一些机器学习模型需要对数据进行归一化?
  3. boost::container模块实现内存资源记录器的程序
  4. Java的值传递解析
  5. 汇编学习笔记(3)-80x86指令集
  6. linux动态库ppt,LINUX系统中动态链接库创建与使用补充_区块链白皮书代写|市场计划书项目PPT设计_Tbleg...
  7. Redis与Zookeeper实现分布式锁的区别
  8. android寻找手机,寻找那些“干净”的Android手机
  9. 决策树——排序算法的理论下界
  10. Web文件管理原码.rar
  11. 海康服务器协议,国标流媒体服务器GB28181协议和海康设备的交互过程记录
  12. 转:有了这些网站,英文论文再也不难写了(15个英文论文写作辅助网站介绍和使用技巧)
  13. 2015-5-23PDF的下载链接
  14. 关于百度移动端搜索中结果聚合的几个常见案例分析
  15. scratch编程一款节奏小游戏
  16. JAVA代码Review
  17. android 网络诊断工具,网络诊断工具MTR
  18. 小程序--时间处理(显示几分钟前,,几小时前,,几天前...)
  19. CouchDB系列 - 安装CouchDB
  20. 大型PUA骗局翻车现场!

热门文章

  1. mysql 参数化分页_LR12 DataWizard从Mysql数据取参数化数据
  2. 【渝粤教育】国家开放大学2018年秋季 1301T病理生理学 参考试题
  3. 【Python实例第15讲】分类概率图
  4. 使用 Eclipse 平台进行调试
  5. BD_source code for problem 1555
  6. 23种设计模式(十八)状态变化之备忘录
  7. 强化学习中价值迭代和策略迭代各有什么优缺点?
  8. flask + apidoc 生成接口文档(附加一个坑)
  9. [poj3252]Round Numbers_数位dp
  10. 【锁】redis加锁的几种方法