<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title type="text">博客园_Linux系统开发专栏</title><subtitle type="text"/><id>http://feed.cnblogs.com/blog/u/76572/rss</id><updated>2012-05-28T10:38:17Z</updated><author><name>☆&amp;amp;寒 烟☆</name><uri>http://www.cnblogs.com/hanyan225/</uri></author><generator>feed.cnblogs.com</generator><link rel="alternate" type="text/html" href="http://www.cnblogs.com/hanyan225/"/><link rel="self" type="application/atom+xml" href="http://feed.cnblogs.com/blog/u/76572/rss"/><entry><id>http://www.cnblogs.com/hanyan225/archive/2012/03/02/2238454.html</id><title type="text">多核计算中多线程的退出算法</title><summary type="text">多核基本意味着多线程，那么在多线程处理中有一个比较棘手的问题：当存在一些常驻的线程访问的共享数据时，退出时必须先结束这些常驻线程才能对共享资料进行释放操作。否则，先释放这些共享资源，后面的常驻线程访问这些已经释放了的共享数据时，导致程序异常。比如下面的情况： 分析上述例子可以发现，问题出在当释放掉链表后，访问链表的线程依然存在，怎样才能在释放链表前让其他对链表有操作的线程安全的退出呢？这就要求我们设计一定的算法而不能简单地使用线程退出函数来强制某个线程的退出，而是在算法中实现让线程自己主动地退出。 要将对链表这样供多个线程操作的线程在链表前安全地退出，首先要做到的就是让这些线程知道要...</summary><published>2012-03-02T12:15:00Z</published><updated>2012-03-02T12:15:00Z</updated><author><name>☆&amp;amp;寒 烟☆</name><uri>http://www.cnblogs.com/hanyan225/</uri></author><link rel="alternate" href="http://www.cnblogs.com/hanyan225/archive/2012/03/02/2238454.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/hanyan225/archive/2012/03/02/2238454.html"/><content type="html">&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 多核基本意味着多线程，那么在多线程处理中有一个比较棘手的问题：当存在一些常驻的线程访问的共享数据时，退出时必须先结束这些常驻线程才能对共享资料进行释放操作。否则，先释放这些共享资源，后面的常驻线程访问这些已经释放了的共享数据时，导致程序异常。比如下面的情况：&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img title="QQ截图20111106215039" style="border-right: 0px; border-top: 0px; display: inline; border-left: 0px; border-bottom: 0px" height="221" alt="QQ截图20111106215039" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201111/20111106222842320.png" width="348" border="0"&gt; &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 分析上述例子可以发现，问题出在当释放掉链表后，访问链表的线程依然存在，怎样才能在释放链表前让其他对链表有操作的线程安全的退出呢？这就要求我们设计一定的算法而不能简单地使用线程退出函数来强制某个线程的退出，而是在算法中实现让线程自己主动地退出。 &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 要将对链表这样供多个线程操作的线程在链表前安全地退出，首先要做到的就是让这些线程知道要进行释放操作了，这里要分几种情况讨论： &lt;/p&gt; &lt;p&gt;&lt;strong&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1.单个子线程退出算法&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 这种情况是最简单地情况，因为只有一个线程，可以通过设置一个退出标志的方式来实现。具体地说就是再进行释放操作要进行的时候先设置一个退出一个标志，在子进程中要定期检测这个标志， 如果子进程检测到这个退出标志为真时就退出线程，在子进程具体退出过程中，也要让释放操作的线程知道操作链表的子进程已经退出，这个可以通过让子线程在退出的最后一步发送一个事件通知，释放线程就可以安全地进行释放操作了。具体的方案如下图：&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img title="QQ截图20111106220112" style="border-right: 0px; border-top: 0px; display: inline; border-left: 0px; border-bottom: 0px" height="358" alt="QQ截图20111106220112" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201111/201111062228459467.png" width="453" border="0"&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ２.多个线程退出算法&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 和1相比，麻烦在于此时有多于一个的子线程，如果每个子线程都在退出前发送一个事件通知给释放操作的线程，就可能出现第一个子线程退出就发送了事件通知，而此时还有其余的子线程还在进行对链表的访问操作，但释放操作的线程已经收到了事件通知，进行了释放。这时显然是会出现问题的。解决这个问题的关键就在于释放链表操作的线程在知道所有的子线程都退出的时候再进行释放的操作。 &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 这时问题的关键就在于如何让释放操作的线程知道所有的子线程都已经安全退出了呢？这里我提起一个新的概念----读写锁。在读操作进行过程中，要进行写操作，必须要等到所有的读操作结束后再进行。写操作的线程是通过一个计数变量来判断是否还有其他读操作线程的。当有一个读操作进行读时，会将计数器+1，一旦读操作完成，会将计数器-1，这样在计数器为0时就表示没有读操作进行了。我们可以把这个方案拿来用在这里。我们把释放操作的线程和写操作相对应，把对链表操作的线程和读操作对应。它们之间维护着一个计数器，当有操作链表的线程创建工作时就把计数器+1，一旦完成时就把计数器-1，该线程然后再检测一次计数器的值，如果计数器为0，就向释放链表操作的线程发送退出事件，否则就不发送，这样当最后一个线程对计数器作-1操作时会为0，会发送退出事件，这样释放操作的线程就可以安全的释放链表了。该算法的实现流程如下所示:  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img title="QQ截图20111106222256" style="border-right: 0px; border-top: 0px; display: inline; border-left: 0px; border-bottom: 0px" height="513" alt="QQ截图20111106222256" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201111/201111062228509561.png" width="438" border="0"&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 至于最后的源码实现，这里不给了，目前有两种方法：利用锁来完成和原子操作两种方式来完成。这里的加/解锁和原子操作只要是为了维持计数器的值，防止多线程同时对其修改的目的。&lt;/p&gt;&lt;img src="http://www.cnblogs.com/hanyan225/aggbug/2238454.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/hanyan225/archive/2012/03/02/2238454.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/hanyan225/archive/2011/08/05/2126619.html</id><title type="text">linux内核分析笔记----页高速缓存和页回写</title><summary type="text">说句真的，也缓存我听的都少，虽然看了很多。页高速缓存是linux内核实现的一种主要磁盘缓存，它主要用来减少对磁盘的IO操作，具体地讲，是通过把磁盘中的数据缓存到物理内存中，把对磁盘的访问变为对物理内存的访问。为什么要这么做呢？一，速度；二临时局部原理。有关这两个概念，相信熟悉操作系统的我们不会太陌生。页高速缓存是由RAM中的物理页组成的，缓存中的每一页都对应着磁盘中的多个块。每当内核开始执行一个页IO操作时，就先到高速缓存中找。这样就可以大大减少磁盘操作。 一个物理页可能由多个不连续的物理磁盘块组成。也正是由于页面中映射的磁盘块不一定连续，所以在页高速缓存中检测特定数据是否已被缓存就变得不那.</summary><published>2011-08-05T08:44:00Z</published><updated>2011-08-05T08:44:00Z</updated><author><name>☆&amp;amp;寒 烟☆</name><uri>http://www.cnblogs.com/hanyan225/</uri></author><link rel="alternate" href="http://www.cnblogs.com/hanyan225/archive/2011/08/05/2126619.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/hanyan225/archive/2011/08/05/2126619.html"/><content type="html">&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 说句真的，也缓存我听的都少，虽然看了很多。页高速缓存是linux内核实现的一种主要磁盘缓存，它主要用来减少对磁盘的IO操作，具体地讲，是通过把磁盘中的数据缓存到物理内存中，把对磁盘的访问变为对物理内存的访问。为什么要这么做呢？一，速度；二临时局部原理。有关这两个概念，相信熟悉操作系统的我们不会太陌生。页高速缓存是由RAM中的物理页组成的，缓存中的每一页都对应着磁盘中的多个块。每当内核开始执行一个页IO操作时，就先到高速缓存中找。这样就可以大大减少磁盘操作。&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/font&gt;&lt;font face="微软雅黑"&gt;一个物理页可能由多个不连续的物理磁盘块组成。也正是由于页面中映射的磁盘块不一定连续，所以在页高速缓存中检测特定数据是否已被缓存就变得不那么容易了。另外linux页高速缓存对被缓存页的范围定义的非常宽。缓存的目标是任何基于页的对象，这包含各种类型的文件和各种类型的内存映射。为了满足普遍性要求，linux使用定义在linux/fs.h中的结构体address_space结构体描述页高速缓存中的页面，如下：&lt;/font&gt;&lt;/p&gt;struct address_space {&lt;br/&gt;        struct inode            *host;              /* owning inode */&lt;br/&gt;        struct radix_tree_root  page_tree;          /* radix tree of all pages */&lt;br/&gt;        spinlock_t              tree_lock;          /* page_tree lock */&lt;br/&gt;        unsigned int            i_mmap_writable;    /* VM_SHARED ma count */&lt;br/&gt;        struct prio_tree_root   i_mmap;             /* list of all mappings */&lt;br/&gt;        struct list_head        i_mmap_nonlinear;   /* VM_NONLINEAR ma list */&lt;br/&gt;        spinlock_t              i_mmap_lock;        /* i_mmap lock */&lt;br/&gt;        atomic_t                truncate_count;     /* truncate re count */&lt;br/&gt;        unsigned long           nrpages;            /* total number of pages */&lt;br/&gt;        pgoff_t                 writeback_index;    /* writeback start offset */&lt;br/&gt;        struct address_space_operations   *a_ops;   /* operations table */&lt;br/&gt;        unsigned long           flags;              /* gfp_mask and error flags */&lt;br/&gt;        struct backing_dev_info *backing_dev_info;  /* read-ahead information */&lt;br/&gt;        spinlock_t              private_lock;       /* private lock */&lt;br/&gt;        struct list_head        private_list;       /* private list */&lt;br/&gt;        struct address_space    *assoc_mapping;     /* associated buffers */&lt;br/&gt;};&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 其中的i_mmap字段是一个优先搜索树，它的搜索范围包含了在address_sapce中私有的和共享的页面。nrpages反应了address_space空间的大小。address_space结构往往会和某些内核对象关联。通常情况下，会与一个索引节点(inode)关联，这时host域就会指向该索引节点。如果关联对象不是一个索引节点的话，比如address_space和swapper关联时，这是host域会被置为NULL。a_ops域指向地址空间对象中的操作函数表，这与VFS对象及其操作函数表关系类似，操作函数表定义在linux/fs.h中，由address_space_operations表示，如下：&lt;/font&gt;&lt;/p&gt;struct address_space_operations {&lt;br/&gt;        int (*writepage)(struct page *, struct writeback_control *);&lt;br/&gt;        int (*readpage) (struct file *, struct page *);&lt;br/&gt;        int (*sync_page) (struct page *);&lt;br/&gt;        int (*writepages) (struct address_space *, struct writeback_control *);&lt;br/&gt;        int (*set_page_dirty) (struct page *);&lt;br/&gt;        int (*readpages) (struct file *, struct address_space *,struct list_head *, unsigned);&lt;br/&gt;        int (*prepare_write) (struct file *, struct page *, unsigned, unsigned);&lt;br/&gt;        int (*commit_write) (struct file *, struct page *, unsigned, unsigned);&lt;br/&gt;        sector_t (*bmap)(struct address_space *, sector_t);&lt;br/&gt;        int (*invalidatepage) (struct page *, unsigned long);&lt;br/&gt;        int (*releasepage) (struct page *, int);&lt;br/&gt;        int (*direct_IO) (int, struct kiocb *, const struct iovec *,loff_t, unsigned long);&lt;br/&gt;};&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 这里面最重要的两个就是readpage()与writepage()了。对于readpage()方法而言，首先，一个address_space对象和一个偏移量会被传给该方法，这两个参数用来在页高速缓存中搜素需要的数据：&lt;/font&gt;&lt;/p&gt;page = find_get_page(mapping, index);&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; mapping是指定的地址空间，index是文件中的指定位置。如果要搜索的页并没在高速缓存中，那么内核将分配一个新页面，然后将其加入到页高速缓存中，如下：&lt;/font&gt;&lt;/p&gt;int error;&lt;br/&gt;cached_page = page_cache_alloc_cold(mapping);&lt;br/&gt;if (!cached_page)&lt;br/&gt;        /* error allocating memory */&lt;br/&gt;error = add_to_page_cache_lru(cached_page, mapping, index, GFP_KERNEL);&lt;br/&gt;if (error)&lt;br/&gt;        /* error adding page to page cache */&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 最后，需要的数据从磁盘被读入，再被加入页高速缓存，然后返回给用户：error = mapping-&amp;gt;a_ops-&amp;gt;readpage(file,page);&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 写操作和读操作有少许不同。对于文件映射来说，当页被修改了，VM仅仅需要调用：setPageDirty(page);内核晚些时候通过writepage()方法把页写出。对特定文件的写操作会比较复杂----它的代码在文件mm/filemap.c中，通常写操作路径基本上要包含一下各步：&lt;/font&gt;&lt;/p&gt;page = __grab_cache_page(mapping, index, &amp;amp;cached_page, &amp;amp;lru_pvec);&lt;br/&gt;status = a_ops-&amp;gt;prepare_write(file, page, offset, offset+bytes);&lt;br/&gt;page_fault = filemap_copy_from_user(page, offset, buf, bytes);&lt;br/&gt;status = a_ops-&amp;gt;commit_write(file, page, offset, offset+bytes);&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 首先，在页高速缓存中搜索需要的页，如果需要的页不在高速缓存中，那么内核在高速缓存中新分配一空闲项；下一步，prepare_write()方法被调用，创建一个写请求；接着数据被从用户空间拷贝到内核缓冲；最后通过commit_write()函数将数据写入磁盘。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;因为在任何页IO操作前内核都要检查页是否已经在页高速缓存中了，所以这种检查必须迅速，高效。否则得不偿失了。前边已经说过，也高速缓存通过两个参数address_space对象和一个偏移量进行搜索。每个address_space对象都有唯一的基树(radix tree),它保证在page_tree结构体中。基树是一个二叉树，只要指定了文件偏移量，就可以在基树中迅速检索到希望的数据，页高速缓存的搜索函数find_get_&lt;br&gt;page()要调用函数radix_tree_lookup(),该函数会在指定基树中搜索指定页面。基树核心代码的通用形式可以在文件lib/radix-tree.c中找到，另外想要使用基树，需要包含头文件linux/radix_tree.h.&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 在内存中累积起来的脏页必须被写回到磁盘，在一下两种情况下，脏页会被写会到磁盘：&lt;/font&gt;&lt;/p&gt;&lt;table style="margin: auto 25px; color: #000000" border="1" cellspacing="0" cellpadding="2" width="814"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td valign="top" width="812"&gt;&lt;font face="微软雅黑"&gt;1.在空闲内存低于一个特定的阈值时，内核必须将脏页写回磁盘，以便释放内存。&lt;br&gt;2.当脏页在内存中驻留超过一定的阈值时，内核必须将超时的脏页写会磁盘，以确保脏页不会无限期地驻留在内存中。&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 现在你只需知道，2.6内核中，使用pdflush后台回写例程来完成这个工作。那么具体是怎么实现的呢：&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;首先，pdflush线程在系统中的空闲内存低于一个特定的阈值时，将脏页刷新回磁盘。该后台回写例程的目的在于在可用物理内存过低时，释放脏页以重新获得内存。上面提到的特定的内存阈值可以通过dirty_background_ratio系统调用设置。一旦空闲内存比这个指小时，内核便会调用函数wakeup_bdflush() 唤醒一个pdflush线程，随后pdflush线程进一步调用函数background_writeout()开始将脏页写会到磁盘，函数background_writeout()需要一个长整型参数，该参数指定试图写回的页面数目。函数background_writeout会连续地写会数据，直到满足一下两个条件：&lt;/font&gt;&lt;/p&gt;&lt;table style="margin: auto 25px; color: #000000" border="1" cellspacing="0" cellpadding="2" width="814"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td valign="top" width="812"&gt;&lt;font face="微软雅黑"&gt;1.已经有指定的最小数目的页被写回到磁盘。&lt;br&gt;2.空闲内存页已经回升，超过了阈值dirty_background_ration.&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; pdflush线程(实现在mm/pdflush.c中，回写机制的实现代码在文件mm/page-writeback.c和fs/fs-writeback.c中)周期地被唤醒并且把超过特定期限的脏页写回磁盘。系统管理员可以在/proc/sys/vm中设置回写相关的参数，也可以通过sysctl系统调用来设置它们。下表给出了可以设置的量：&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201108/201108032030388680.png" width="475" height="215"&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/hanyan225/aggbug/2126619.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/hanyan225/archive/2011/08/05/2126619.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/hanyan225/archive/2011/08/04/2127894.html</id><title type="text">linux内核分析笔记----内核可移植性</title><summary type="text">关于linux内核的可移植性我不用多说，现在的linux操作系统，你装系统时应该很明白的知道了，很少说(至少我没见到)不兼容不能装的问题。今天就来说说这个问题： 1.字节和数据类型 能够由机器一次就完成处理的数据被称为字，字指位的数目。所以我们常听到机器是多少位的时候，就是指该机的字长。处理器通用寄存器的大小和它的字长是相同的。C语言定义的long类型总对等于机器字长。对于支持的每一种体系结构，...</summary><published>2011-08-04T12:13:00Z</published><updated>2011-08-04T12:13:00Z</updated><author><name>☆&amp;amp;寒 烟☆</name><uri>http://www.cnblogs.com/hanyan225/</uri></author><link rel="alternate" href="http://www.cnblogs.com/hanyan225/archive/2011/08/04/2127894.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/hanyan225/archive/2011/08/04/2127894.html"/><content type="html">&lt;font face="微软雅黑"&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 关于linux内核的可移植性我不用多说，现在的linux操作系统，你装系统时应该很明白的知道了，很少说(至少我没见到)不兼容不能装的问题。今天就来说说这个问题：&lt;br&gt;&lt;strong&gt;&lt;font color="#0000ff"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/font&gt;&lt;/strong&gt;&lt;/font&gt;&lt;font face="微软雅黑"&gt;&lt;strong&gt;&lt;font color="#0000ff"&gt;1.字节和数据类型&lt;br&gt;&lt;/font&gt;&lt;/strong&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/font&gt;&lt;font face="微软雅黑"&gt;能够由机器一次就完成处理的数据被称为字，字指位的数目。所以我们常听到机器是多少位的时候，就是指该机的字长。处理器通用寄存器的大小和它的字长是相同的。C语言定义的long类型总对等于机器字长。对于支持的每一种体系结构，Linux都要将&amp;lt;asm/types.h&amp;gt;中的BITS_PER_LONG定义为C long类型的长度，也就是系统的字长。不透明类型是那些通过typeder声明的类型。另外就是，我们常常需要在程序中使用长度明确的类型，内核在asm/types.h中定义了这些长度明确的类型，而该文件又被包含在文件linux/types.h中，如下表所示：&lt;/font&gt;&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201108/201108042012406294.png" width="319" height="170"&gt;&lt;/p&gt; &lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 其中带符号的变量用的比较少。接下来是char型：分为有符号(-128~127)和无符号(0~255).&lt;/font&gt;&lt;/p&gt; &lt;p&gt;&lt;font color="#0000ff" face="微软雅黑"&gt;&lt;strong&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2.数据对齐&lt;/strong&gt;&lt;/font&gt;&lt;/p&gt; &lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 如果一个变量的内存地址正好是它长度的整数倍，它就被称为自然对齐的。关于字节对齐的内容还是相当繁琐的，我这里就不细讲了，后面我会有专门的专题来说这个问题。&lt;/font&gt;&lt;/p&gt; &lt;p&gt;&lt;font color="#0000ff" face="微软雅黑"&gt;&lt;strong&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3.字节顺序&lt;/strong&gt;&lt;/font&gt;&lt;/p&gt; &lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 字节顺序是指在一个字中各个字节的顺序。处理器在对字取值时既可能将最低有效位所在字节当作第一个字节(最左边的字节)，也可能将其当作最后一个字节(最右边的字节)。如果最高有效位所在的字节放在最高字节位置上，其他字节依次放在低字节位置上，那么该字节顺序称作高位优先(big-endian)[存放左大右小]，否则就叫做little-endian[左小又大].直接举个例子，如下：&lt;/font&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 00000000 00000000 00000100 00000011&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="123" border="0" alt="123" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201108/201108042012402947.jpg" width="388" height="149"&gt;&amp;nbsp; &lt;/p&gt; &lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 下面是上述数据在两种不同字节序的排列方式：&lt;/font&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201108/201108042012409915.png" width="273" height="99"&gt;&lt;/p&gt; &lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 虽然不习惯，但确实是这样的，使用高位优先的体系结构把最高字节位存放在最小的内存地址上。下边的代码可以判定给定的机器字节对齐类型：&lt;/font&gt;&lt;/p&gt;int x = 1;&lt;br/&gt;if (*(char *)&amp;amp;x == 1)&lt;br/&gt;    /* little endian */&lt;br/&gt;else&lt;br/&gt;    /* big endian */&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 在linux内核支持的每一种体系结构，相应的内核都会根据机器使用的字节顺序在它的asm/byteorder.h中定义__BIG_ENDIAN或__LITTILE_ENDIAN中的一个。，这个头文件还从include/linux/byteo&lt;/font&gt;&lt;font face="微软雅黑"&gt;rd&lt;/font&gt; &lt;br&gt;&lt;font face="微软雅黑"&gt;er中包含了一组宏命令完成字节顺序之间的相互转换，最常用的宏命令如下：&lt;/font&gt;&lt;br&gt;u23 __cpu_to_be32(u32);    /* convert cpu's byte order to big-endian */&lt;br/&gt;u32 __cpu_to_le32(u32);    /* convert cpu's byte order to little-endian */&lt;br/&gt;u32 __be32_to_cpu(u32);    /* convert big-endian to cpu's byte order */&lt;br/&gt;u32 __le32_to_cpus(u32);   /* convert little-endian to cpu's byte order */&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;strong&gt;&lt;font color="#0000ff"&gt;4.时间&lt;/font&gt;&lt;/strong&gt;&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 关于内核的时间问题，绝对不要假定时钟中断发生的频率，也就是每秒产生的jiffies数目。相反，应该使用HZ来正确计量时间。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;strong&gt;&lt;font color="#0000ff"&gt;5.页长度&lt;/font&gt;&lt;/strong&gt;&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 当处理用页管理的内存时，绝对不要假设页的长度。不同的体系结构使用页的长度也是不一样的。当处理用页组织管理的内存时，通过PAGE_SIZE来使用以字节数来表示的页长度，而PAGE_SHIFT这个值定义了从最右端屏蔽多少位能够得到该地址对应的页的页号。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 总之，编写可移植的代码需要考虑许多问题：字长，数据类型，对齐，字节次序，页大小，处理器排序等等。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&lt;/font&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&lt;/font&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&lt;/font&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/hanyan225/aggbug/2127894.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/hanyan225/archive/2011/08/04/2127894.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/hanyan225/archive/2011/08/03/2125927.html</id><title type="text">linux内核分析笔记----进程地址空间</title><summary type="text">前边我已经说过了内核是如何管理物理内存。但事实是内核是操作系统的核心，不光管理本身的内存，还要管理进程的地址空间。linux操作系统采用虚拟内存技术，所有进程之间以虚拟方式共享内存。进程地址空间由每个进程中的线性地址区组成，而且更为重要的特点是内核允许进程使用该空间中的地址。通常情况况下，每个进程都有唯一的地址空间，而且进程地址空间之间彼此互不相干。但是进程之间也可以选择共享地址空间，这样的进程...</summary><published>2011-08-03T02:56:00Z</published><updated>2011-08-03T02:56:00Z</updated><author><name>☆&amp;amp;寒 烟☆</name><uri>http://www.cnblogs.com/hanyan225/</uri></author><link rel="alternate" href="http://www.cnblogs.com/hanyan225/archive/2011/08/03/2125927.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/hanyan225/archive/2011/08/03/2125927.html"/><content type="html">&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 前边我已经说过了内核是如何管理物理内存。但事实是内核是操作系统的核心，不光管理本身的内存，还要管理进程的地址空间。linux操作系统采用虚拟内存技术，所有进程之间以虚拟方式共享内存。进程地址空间由每个进程中的线性地址区组成，而且更为重要的特点是内核允许进程使用该空间中的地址。通常情况况下，每个进程都有唯一的地址空间，而且进程地址空间之间彼此互不相干。但是进程之间也可以选择共享地址空间，这样的进程就叫做线程。&lt;/font&gt;&lt;br&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 内核使用内存描述符结构表示进程的地址空间，由结构体mm_struct结构体表示，定义在linux/sched.h中，如下：&lt;/font&gt;&lt;br&gt;struct mm_struct {&lt;br/&gt;        struct vm_area_struct  *mmap;               /* list of memory areas */&lt;br/&gt;        struct rb_root         mm_rb;               /* red-black tree of VMAs */&lt;br/&gt;        struct vm_area_struct  *mmap_cache;         /* last used memory area */&lt;br/&gt;        unsigned long          free_area_cache;     /* 1st address space hole */&lt;br/&gt;        pgd_t                  *pgd;                /* page global directory */&lt;br/&gt;        atomic_t               mm_users;            /* address space users */&lt;br/&gt;        atomic_t               mm_count;            /* primary usage counter */&lt;br/&gt;        int                    map_count;           /* number of memory areas */&lt;br/&gt;        struct rw_semaphore    mmap_sem;            /* memory area semaphore */&lt;br/&gt;        spinlock_t             page_table_lock;     /* page table lock */&lt;br/&gt;        struct list_head       mmlist;              /* list of all mm_structs */&lt;br/&gt;        unsigned long          start_code;          /* start address of code */&lt;br/&gt;        unsigned long          end_code;            /* final address of code */&lt;br/&gt;        unsigned long          start_data;          /* start address of data */&lt;br/&gt;        unsigned long          end_data;            /* final address of data */&lt;br/&gt;        unsigned long          start_brk;           /* start address of heap */&lt;br/&gt;        unsigned long          brk;                 /* final address of heap */&lt;br/&gt;        unsigned long          start_stack;         /* start address of stack */&lt;br/&gt;        unsigned long          arg_start;           /* start of arguments */&lt;br/&gt;        unsigned long          arg_end;             /* end of arguments */&lt;br/&gt;        unsigned long          env_start;           /* start of environment */&lt;br/&gt;        unsigned long          env_end;             /* end of environment */&lt;br/&gt;        unsigned long          rss;                 /* pages allocated */&lt;br/&gt;        unsigned long          total_vm;            /* total number of pages */&lt;br/&gt;        unsigned long          locked_vm;           /* number of locked pages */&lt;br/&gt;        unsigned long          def_flags;           /* default access flags */&lt;br/&gt;        unsigned long          cpu_vm_mask;         /* lazy TLB switch mask */&lt;br/&gt;        unsigned long          swap_address;        /* last scanned address */&lt;br/&gt;        unsigned               dumpable:1;          /* can this mm core dump? */&lt;br/&gt;        int                    used_hugetlb;        /* used hugetlb pages? */&lt;br/&gt;        mm_context_t           context;             /* arch-specific data */&lt;br/&gt;        int                    core_waiters;        /* thread core dump waiters */&lt;br/&gt;        struct completion      *core_startup_done;  /* core start completion */&lt;br/&gt;        struct completion      core_done;           /* core end completion */&lt;br/&gt;        rwlock_t               ioctx_list_lock;     /* AIO I/O list lock */&lt;br/&gt;        struct kioctx          *ioctx_list;         /* AIO I/O list */&lt;br/&gt;        struct kioctx          default_kioctx;      /* AIO default I/O context */&lt;br/&gt;};&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; mm_users记录了正在使用该地址的进程数目(比如有两个进程在使用，那就为2)。mm_count是该结构的主引用计数，只要mm_users不为0，它就为1。但其为0时，后者就为0。这时也就说明再也没有指向该mm_struct结构体的引用了，这时该结构体会被销毁。内核之所以同时使用这两个计数器是为了区别主使用计数器和使用该地址空间的进程的数目。mmap和mm_rb描述的都是同一个对象：该地址空间中的全部内存区域。不同只是前者以链表，后者以红黑树的形式组织。所有的mm_struct结构体都通过自身的mmlist域连接在一个双向链表中，该链表的首元素是init_mm内存描述符，它代表init进程的地址空间。另外需要注意，操作该链表的时候需要使用mmlist_lock锁来防止并发访问，该锁定义在文件kernel/fork.c中。内存描述符的总数在mmlist_nr全局变量中，该变量也定义在文件fork.c中。&lt;/font&gt; &lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 我前边说过的进程描述符中有一个mm域，这里边存放的就是该进程使用的内存描述符，通过current-&amp;gt;mm便可以指向当前进程的内存描述符。fork函数利用copy_mm()函数就实现了复制父进程的内存描述符，而子进程中的mm_struct结构体实际是通过文件kernel/fork.c中的allocate_mm()宏从mm_cachep slab缓存中分配得到的。通常，每个进程都有唯一的mm_struct结构体。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 前边也说过，在linux中，进程和线程其实是一样的，唯一的不同点就是是否共享这里的地址空间。这个可以通过CLONE_VM标志来实现。linux内核并不区别对待它们，线程对内核来说仅仅是一个共向特定资源的进程而已。好了，如果你设置这个标志了，似乎很多问题都解决了。不再要allocate_mm函数了，前边刚说作用。而且在copy_mm()函数中将mm域指向其父进程的内存描述符就可以了，如下：&lt;/font&gt;&lt;/p&gt;if (clone_flags &amp;amp; CLONE_VM) {&lt;br/&gt;        /*&lt;br/&gt;         * current is the parent process and&lt;br/&gt;         * tsk is the child process during a fork()&lt;br/&gt;         */&lt;br/&gt;         atomic_inc(&amp;amp;current-&amp;gt;mm-&amp;gt;mm_users);&lt;br/&gt;         tsk-&amp;gt;mm = current-&amp;gt;mm;&lt;br/&gt;}&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 最后，当进程退出的时候，内核调用exit_mm()函数，这个函数调用mmput()来减少内存描述符中的mm_users用户计数。如果计数降为0，继续调用mmdrop函数，减少mm_count使用计数。如果使用计数也为0，则调用free_mm()宏通过kmem_cache_free()函数将mm_struct结构体归还到mm_cachep slab缓存中。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 但对于内核而言，内核线程没有进程地址空间，也没有相关的内存描述符，内核线程对应的进程描述符中mm域也为空。但内核线程还是需要使用一些数据的，比如页表，为了避免内核线程为内存描述符和页表浪费内存，也为了当新内核线程运行时，避免浪费处理器周期向新地址空间进行切换，内核线程将直接使用前一个进程的内存描述符。回忆一下我刚说的进程调度问题，当一个进程被调度时，进程结构体中mm域指向的地址空间会被装载到内存，进程描述符中的active_mm域会被更新，指向新的地址空间。但我们这里的内核是没有mm域(为空)，所以，当一个内核线程被调度时，内核发现它的mm域为NULL，就会保留前一个进程的地址空间，随后内核更新内核线程对应的进程描述符中的active域，使其指向前一个进程的内存描述符。所以在需要的时候，内核线程便可以使用前一个进程的页表。因为内核线程不妨问用户空间的内存，所以它们仅仅使用地址空间中和内核内存相关的信息，这些信息的含义和普通进程完全相同。&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/font&gt;&lt;font face="微软雅黑"&gt;内存区域由vm_area_struct结构体描述，定义在linux/mm.h中，内存区域在内核中也经常被称作虚拟内存区域或VMA.它描述了指定地址空间内连续区间上的一个独立内存范围。内核将每个内存区域作为一个单独的内存对象管理，每个内存区域都拥有一致的属性。结构体如下：&lt;/font&gt;&lt;/p&gt;struct vm_area_struct {&lt;br/&gt;        struct mm_struct             *vm_mm;        /* associated mm_struct */&lt;br/&gt;        unsigned long                vm_start;      /* VMA start, inclusive */&lt;br/&gt;        unsigned long                vm_end;        /* VMA end , exclusive */&lt;br/&gt;        struct vm_area_struct        *vm_next;      /* list of VMA's */&lt;br/&gt;        pgprot_t                     vm_page_prot;  /* access permissions */&lt;br/&gt;        unsigned long                vm_flags;      /* flags */&lt;br/&gt;        struct rb_node               vm_rb;         /* VMA's node in the tree */&lt;br/&gt;        union {         /* links to address_space-&amp;gt;i_mmap or i_mmap_nonlinear */&lt;br/&gt;                struct {&lt;br/&gt;                        struct list_head        list;&lt;br/&gt;                        void                    *parent;&lt;br/&gt;                        struct vm_area_struct   *head;&lt;br/&gt;                } vm_set;&lt;br/&gt;                struct prio_tree_node prio_tree_node;&lt;br/&gt;        } shared;&lt;br/&gt;        struct list_head             anon_vma_node;     /* anon_vma entry */&lt;br/&gt;        struct anon_vma              *anon_vma;         /* anonymous VMA object */&lt;br/&gt;        struct vm_operations_struct  *vm_ops;           /* associated ops */&lt;br/&gt;        unsigned long                vm_pgoff;          /* offset within file */&lt;br/&gt;        struct file                  *vm_file;          /* mapped file, if any */&lt;br/&gt;        void                         *vm_private_data;  /* private data */&lt;br/&gt;};&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 每个内存描述符都对应于地址进程空间中的唯一区间。vm_mm域指向和VMA相关的mm_struct结构体。两个独立的进程将同一个文件映射到各自的地址空间，它们分别都会有一个vm_area_struct结构体来标志自己的内存区域；但是如果两个线程共享一个地址空间，那么它们也同时共享其中的所有vm_area_struct结构体。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 在上面的vm_flags域中存放的是VMA标志，标志了内存区域所包含的页面的行为和信息，反映了内核处理页面所需要遵循的行为准则，如下表下述：&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201108/201108031055587129.png" width="448" height="462"&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 上表已经相当详细了，而且给出了说明，我就不说了。在vm_area_struct结构体中的vm_ops域指向域指定内存区域相关的操作函数表，内核使用表中的方法操作VMA。vm_area_struct作为通用对象代表了任何类型的内存区域，而操作表描述针对特定的对象实例的特定方法。操作函数表由vm_operations_struct结构体表示，定义在linux/mm.h中，如下:&lt;/font&gt;&lt;/p&gt;struct vm_operations_struct {&lt;br/&gt;        void (*open) (struct vm_area_struct *);&lt;br/&gt;        void (*close) (struct vm_area_struct *);&lt;br/&gt;        struct page * (*nopage) (struct vm_area_struct *, unsigned long, int);&lt;br/&gt;        int (*populate) (struct vm_area_struct *, unsigned long, unsigned long,pgprot_t, unsigned long, int);&lt;br/&gt;};&lt;table style="margin: auto 25px; color: #000000" border="1" cellspacing="0" cellpadding="2" width="829"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td valign="top" width="827"&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;open:当指定的内存区域被加入到一个地址空间时，该函数被调用。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;close：当指定的内存区域从地址空间删除时，该函数被调用。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;nopages:当要访问的页不在物理内存中时，该函数被页错误处理程序调用。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;populate：该函数被系统调用remap_pages调用来为将要发生的缺页中断预映射一个新映射。&lt;/font&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 记性好的你一定记得内存描述符中的mmap和mm_rb域都独立地指向与内存描述符相关的全体内存区域对象。它们包含完全相同的vm_area_struct结构体的指针，仅仅组织方式不同而已。前者以链表的方式进行组织，所有的区域按地址增长的方向排序，mmap域指向链表中第一个内存区域，链中最后一个VMA结构体指针指向空。而mm_rb域采用红--黑树连接所有的内存区域对象。它指向红--黑输的根节点。地址空间中每一个vm_area_struct结构体通过自身的vm_rb域连接到树中。关于红黑二叉树结构我就不细讲了，以后可能会详细说这个问题。内核之所以采用这两种结构来表示同一内存区域，主要是链表结构便于遍历所有节点，而红黑树结构体便于在地址空间中定位特定内存区域的节点。我么可以使用/proc文件系统和pmap工具查看给定进程的内存空间和其中所包含的内存区域。这里就不细说了。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 内核也为我们提供了对内存区域操作的API，定义在linux/mm.h中：&lt;/font&gt;&lt;/p&gt;&lt;table style="margin: auto 25px; color: #000000" border="1" cellspacing="0" cellpadding="2" width="828"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td valign="top" width="826"&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;(1)find_vma&amp;lt;定义在mm/mmap.c&amp;gt;中，该函数在指定的地址空间中搜索一个vm_end大于addr的内存区域。换句话说，该函数寻找&lt;/font&gt;&lt;font face="微软雅黑"&gt;第一个&lt;/font&gt;&lt;font face="微软雅黑"&gt;包含&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; addr或者首地址大于addr的内存区域，如果没有发现这样的区域，该函数返回NULL；否则返回指向匹配的内存区域的vm_area_struct结构&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 体指针。&lt;br&gt;&lt;/font&gt;&lt;font face="微软雅黑"&gt;(2)find_vma_prev().函数定义和声明分别在文件mm/mmap.c中和文件linux/mm.h中，它和find_vma()工作方式相同，但返回的是第一个小于&amp;nbsp; &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; addr的VMA.&lt;br&gt;(3)&lt;/font&gt;&lt;font face="微软雅黑"&gt;find_vma_intersection().定义在文件linux/mm.h中，返回第一个和指定地址区间相交的VMA,该函数是一个内敛函数。&lt;/font&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 接下来要说的两个函数就非常重要了，它们负责创建和删除地址空间。&lt;br&gt;&lt;/font&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 内核使用do_mmap()函数创建一个新的线性地址空间。但如果创建的地址区间和一个已经存在的地址区间相邻，并且它们具有相同的访问权限的话，那么两个区间将合并为一个。如果不能合并，那么就确实需要创建一个新的vma了，但无论哪种情况，do_mmap()函数都会将一个地址区间加入到进程的地址空间中。这个函数定义在linux/mm.h中，如下：&lt;/font&gt;&lt;/p&gt;unsigned long do_mmap(struct file *file, unsigned long addr, unsigned long len, unsigned long prot,unsigned long flag, unsigned long offset)&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 这个函数中由file指定文件，具体映射的是文件中从偏移offset处开始，长度为len字节的范围内的数据，如果file参数是NULL并且offset参数也是0，那么就代表这次映射没有和文件相关，该情况被称作匿名映射。如果指定了文件和偏移量，那么该映射被称为文件映射(file-backed mapping)，其中参数prot指定内存区域中页面的访问权限，这些访问权限定义在asm/mman.h中，如下：&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201108/201108031055584654.png" width="423" height="126"&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; flag参数指定了VMA标志，这些标志定义在asm/mman.h中，如下：&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201108/201108031055589670.png" width="425" height="276"&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 如果系统调用do_mmap的参数中有无效参数，那么它返回一个负值；否则，它会在虚拟内存中分配一个合适的新内存区域，如果有可能的话，将新区域和临近区域进行合并，否则内核从vm_area_cach&lt;/font&gt;&lt;br&gt;&lt;font face="微软雅黑"&gt;ep长字节缓存中分配一个vm_area_struct结构体，并且使用vma_link()函数将新分配的内存区域添加到地址空间的内存区域链表和红黑树中，随后还要更新内存描述符中的total_vm域，然后才返回新分配的地址区间的初始地址。在用户空间，我们可以通过mmap()系统调用获取内核函数do_mmap()的功能，这个在unix环境高级编程中讲的很详细，我就不好意思继续说了。我们继续往下走。&lt;br&gt;我们说既然有了创建，当然要有删除了，是不？do_mummp()函数就是干这事的。它从特定的进程地址空间中删除指定地址空间，该函数定义在文件linux/mm.h中，如下：&lt;/font&gt;&lt;/p&gt;int do_munmap(struct mm_struct *mm, unsigned long start, size_t len)&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 第一个参数指定要删除区域所在的地址空间，删除从地址start开始，长度为len字节的地址空间，如果成功，返回0，否则返回负的错误码。与之相对应的用户空间系统调用是munmap。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 下面开始最后一点内容：页表&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 我们知道应用程序操作的对象是映射到物理内存之上的虚拟内存，但是处理器直接操作的确实物理内存。所以当应用程序访问一个虚拟地址时，首先必须将虚拟地址转化为物理地址，然后处理器才能解析地址访问请求。这个转换工作需要通过查询页面才能完成，概括地讲，地址转换需要将虚拟地址分段，使每段虚地址都作为一个索引指向页表，而页表项则指向下一级别的页表或者指向最终的物理页面。linux中使用三级页表完成地址转换。多数体系结构中，搜索页表的工作由硬件完成，下表描述了虚拟地址通过页表找到物理地址的过程：&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201108/201108031055581099.png" width="544" height="201"&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 在上面这个图中，顶级页表是页全局目录(PGD)，二级页表是中间页目录(PMD).最后一级是页表(PTE),该页表结构指向物理页。上图中的页表对应的结构体定义在文件asm/page.h中。为了加快查找速度，在linux中实现了快表(TLB),其本质是一个缓冲器，作为一个将虚拟地址映射到物理地址的硬件缓存，当请求访问一个虚拟地址时，处理器将首先检查TLB中是否缓存了该虚拟地址到物理地址的映射，如果找到了，物理地址就立刻返回，否则，就需要再通过页表搜索需要的物理地址。&lt;/font&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/hanyan225/aggbug/2125927.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/hanyan225/archive/2011/08/03/2125927.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/hanyan225/archive/2011/08/02/2124553.html</id><title type="text">linux内核分析笔记----块I/O层</title><summary type="text">如果您记性好的话，应该记得我在linux设备驱动实例帖中说的最多的就是字符设备驱动程序，那么今天的块I/O层是一个和字符设备驱动相对应的设备。两者最根本的区别就是看它们能否被随机访问，换句话说就是看它们能否在访问设备时从一个位置随意地调到另外一个位置，如果可以就是块设备，否则就字符设备。 块设备中最小的可寻址单元是扇区。扇区的大小一般是2的整数倍，最常见的大小是512个字节。扇区的大小是设备的物理属性，扇区是所有块设备的基本单元，块设备无法对比它还小的单元进行寻址和操作，不过许多块设备能够一次就传输多个扇区。从软件角度来讲，最小的逻辑可寻址单元却是块，块是文件系统的一种抽象-----只能基于.</summary><published>2011-08-02T04:17:00Z</published><updated>2011-08-02T04:17:00Z</updated><author><name>☆&amp;amp;寒 烟☆</name><uri>http://www.cnblogs.com/hanyan225/</uri></author><link rel="alternate" href="http://www.cnblogs.com/hanyan225/archive/2011/08/02/2124553.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/hanyan225/archive/2011/08/02/2124553.html"/><content type="html">&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 如果您记性好的话，应该记得我在linux设备驱动实例帖中说的最多的就是字符设备驱动程序，那么今天的块I/O层是一个和字符设备驱动相对应的设备。两者最根本的区别就是看它们能否被随机访问，换句话说就是看它们能否在访问设备时从一个位置随意地调到另外一个位置，如果可以就是块设备，否则就字符设备。&lt;/font&gt;&lt;/p&gt; &lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 块设备中最小的可寻址单元是扇区。扇区的大小一般是2的整数倍，最常见的大小是512个字节。扇区的大小是设备的物理属性，扇区是所有块设备的基本单元，块设备无法对比它还小的单元进行寻址和操作，不过许多块设备能够一次就传输多个扇区。从软件角度来讲，最小的逻辑可寻址单元却是块，块是文件系统的一种抽象-----只能基于块来访问文件系统。虽然物理磁盘寻址是按照扇区级进行的，但是内核执行的所有磁盘操作都是按照块进行的。前边已经说过，扇区是设备的最小可寻址单元，所以块不能比扇区还小，只能数倍于扇区大小。另外内核还要求块大小是2的整数倍，=而且不能超过一个页的长度，所以大小的最终要求是，必须是扇区大小的2的整数倍，并且要小于页面大小。所以通常块大小是512字节，1k或4k。&lt;/font&gt;&lt;/p&gt; &lt;p&gt;&lt;font face="微软雅黑"&gt;当一个块被调入内存时，它要存储在一个缓冲区中，每个缓冲区与一个块对应，它相当于是磁盘块在内存中的表示。另外，由于内核在处理数据时需要一些相关的控制信息，所以每个缓冲区都有一个叫做buffer_head的描述符来表示，被称为缓冲区头，在linux/buffer_head.h中定义，它包含了内核操作缓冲区所需要的全部信息，如下:&lt;/font&gt;&lt;/p&gt;struct buffer_head {&lt;br/&gt;        unsigned long        b_state;          /* buffer state flags */&lt;br/&gt;        atomic_t             b_count;          /* buffer usage counter */&lt;br/&gt;        struct buffer_head   *b_this_page;     /* buffers using this page */&lt;br/&gt;        struct page          *b_page;          /* page storing this buffer */&lt;br/&gt;        sector_t             b_blocknr;        /* logical block number */&lt;br/&gt;        u32                  b_size;           /* block size (in bytes) */&lt;br/&gt;        char                 *b_data;          /* buffer in the page */&lt;br/&gt;        struct block_device  *b_bdev;          /* device where block resides */&lt;br/&gt;        bh_end_io_t          *b_end_io;        /* I/O completion method */&lt;br/&gt;        void                 *b_private;       /* data for completion method */&lt;br/&gt;        struct list_head     b_assoc_buffers;  /* list of associated mappings */&lt;br/&gt;};&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 其中的b_state域表示缓冲区的状态，下表给出一种标志或多种标志的组合，在linux/buffer_head.h中定义了所有合法标志的bh_state_bite列表，如下所示：&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201108/201108020934224496.png" width="524" height="300"&gt; &lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; bh_state_bits列表包含了一个特殊标志----BH_PrivateStart,该标志不是可用状态标志，使用它是为了指明可能其它代码使用的起始位。块I/O层不会使用BH_PrivateStart或更高的位，那么某个驱动程序希望通过b_state域存储信息时就可以安全地使用这些位。驱动程序可以在这些位中定义自己的状态标志，只要保证自定义的状态标志不会与块IO层的专用位发生冲突就可以了。b_count域表示缓冲区的使用计数，可通过两个定义在文件linux/buffer_head.h中的内联函数对此域进行增减：&lt;/font&gt;&lt;/p&gt;static inline void get_bh(struct buffer_head *bh)&lt;br/&gt;{&lt;br/&gt;        atomic_inc(&amp;amp;bh-&amp;gt;b_count);&lt;br/&gt;}&lt;br/&gt;static inline void put_bh(struct buffer_head *bh)&lt;br/&gt;{&lt;br/&gt;        atomic_dec(&amp;amp;bh-&amp;gt;b_count);&lt;br/&gt;}&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 在操作缓冲区头之前，应该先使用get_bh()函数增加缓冲区头的引用计数，确保缓冲区头不会再被分配出去，当完成对缓冲区头的操作之后，还必须使用put_bh()函数减少引用计数。与缓冲区对应的磁盘物理块由b_blocknr域索引，该值是b_bdev域指明的块设备中的逻辑块号。与缓冲区对应的内存物理页由b_page域表示，另外，b_data域直接指向相应的块(它位于b_page域所指明的页面的某个位置上)，块的大小由b_size域表示，所以块在内存中的起始位置在b_data处，结束位置在(b_data+b_size)处。缓冲区头的目的在于描述磁盘块和物理内存缓冲区(在特定页面上的字节序列)之间的映射关系。这个结构体在内核中扮演一个描述符的角色，说明从缓冲区到块的映射关系。使用缓冲区头作为I/O操作有它的弊端，这里不细说，你明白就好。我们只需知道现在的内核采用了一种新型，灵活而且轻量级的容器---bio结构体。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; bio结构体定义在linux/bio.h中，该结构体代表了正在现场的(活动)以片断(segment)链表形式组织的块I/O操作。一个片断是一小块连续的内存缓冲区。这样的话，就不需要保证单个缓冲区一定要连续，所有通过片断来描述缓冲区，即使一个缓冲区分散在内存的多个位置上，bio结构体也能保证I/O操作的执行。下面给出bio结构体和各个域的描述，如下：&lt;/font&gt;&lt;/p&gt;struct bio {&lt;br/&gt;        sector_t             bi_sector;         /* associated sector on disk */&lt;br/&gt;        struct bio           *bi_next;          /* list of requests */&lt;br/&gt;        struct block_device  *bi_bdev;          /* associated block device */&lt;br/&gt;        unsigned long        bi_flags;          /* status and command flags */&lt;br/&gt;        unsigned long        bi_rw;             /* read or write? */&lt;br/&gt;        unsigned short       bi_vcnt;           /* number of bio_vecs off */&lt;br/&gt;        unsigned short       bi_idx;            /* current index in bi_io_vec */&lt;br/&gt;        unsigned short       bi_phys_segments;  /* number of segments after coalescing */&lt;br/&gt;        unsigned short       bi_hw_segments;    /* number of segments after remapping */&lt;br/&gt;        unsigned int         bi_size;           /* I/O count */&lt;br/&gt;        unsigned int         bi_hw_front_size;  /* size of the first mergeable segment */&lt;br/&gt;        unsigned int         bi_hw_back_size;   /* size of the last mergeable segment */&lt;br/&gt;        unsigned int         bi_max_vecs;       /* maximum bio_vecs possible */&lt;br/&gt;        struct bio_vec       *bi_io_vec;        /* bio_vec list */&lt;br/&gt;        bio_end_io_t         *bi_end_io;        /* I/O completion method */&lt;br/&gt;        atomic_t             bi_cnt;            /* usage counter */&lt;br/&gt;        void                 *bi_private;       /* owner-private method */&lt;br/&gt;        bio_destructor_t     *bi_destructor;    /* destructor method */&lt;br/&gt;};&lt;br/&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 使用bio结构体的目的主要是代表正在现场执行的I/O操作，所有该结构体中的主要域都是用来管理相关信息的。其中最重要的几个域是bi_io_vecs,bi_vcnt和bi_idx.它们之间的关系如下图所示：&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201108/201108020934259706.png" width="390" height="238"&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 我在前边已经给出了struct bio的结构体，下面给出struct bio_vec的描述：&lt;/font&gt;&lt;/p&gt;struct bio_vec {&lt;br/&gt;        struct page     *bv_page;   /* pointer to the physical page on which this buffer resides */ &lt;br/&gt;        unsigned int    bv_len;     /* the length in bytes of this buffer */ &lt;br/&gt;        unsigned int    bv_offset;   /* the byte offset within the page where the buffer resides */ &lt;br/&gt;};&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 下面来分析以上上面的那个图，我们说：每一个块I/O请求都通过一个bio结构体表示。每个请求包含一个或多个块，这些块存储在bio_vec结构体数组中，这些结构体描述了每个片断在物理页中的实际位置，并且像向量一样地组织在一起，IO操作的第一个片断由b_io_vec结构体所指向，其他的片断在其后依次放置，共有bi_vcnt个片断。当块IO开始执行请求，需要使用各个片段时，bi_idx域会不断更新，从而总指向当前片断。bi_idx域指向数组中的当前bio_vec片段，块IO层通过它跟踪块IO操作的完成进度。但该域更重要的作用是分割bio结构体。bi_cnt域记录bio结构体的使用计数，如果为0，则应该销毁该bio结构体，并释放它占用的内存。通过下面两个函数管理使用计数：&lt;/font&gt;&lt;/p&gt;void bio_get(struct bio *bio);&lt;br/&gt;void bio_put(struct bio *bio);&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 最后一个域是bi_private域，这是一个属于拥有者的私有域，谁创建了bio结构，谁就可以读写该域。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 块设备将它们挂起的块IO请求保存在请求队列中，该队列有request_queue结构体体表示，定义在文件linux/blkdev.h中，包含一个双向请求链表以及相关控制信息。通过内核中想文件系统这样高层的代码将请求加入到队列中。请求队列只要不为空，队列对应的块设备驱动程序就会从队列头获取请求，然后将其送入对应的块设备上去请求队列表中的每一项都是一个单独的请求，有reques结构体体表示。队列中的请求由结构体request表示，定义在文件linux/blkdev.h表示。因为一个请求可能要操作多个连续的磁盘块，所有每个请求可有由多个bio结构体组成，注意，虽然磁盘上的块必须连续，但是在内存中的这些块并不一定要连续----每个bio结构都可以描述多个片段，而每个请求也可以包含多个bio结构体。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 好了，我们明白了块IO请求，下面的就是IO调度了。每次寻址的操作就是定位磁盘磁头到特定块上的某个位置，为了优化寻址操作，内核既不会简单地按请求接收次序，也不会立即将其提交给磁盘，相反，它会在提交前，先执行名为合并与排序的预操作，这种预操作可以极大地提高系统的整体性能。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; IO调度程序通过两种方法减少磁盘寻址时间：合并与排序。合并指将两个或多个请求结合成一个一个新请求。关于排序的，最有名的当然就是大名鼎鼎的电梯调度。排序就是整个请求队列将按扇区增长方向有序排列，使所有请求按磁盘上扇区的排列顺序有序排列的目的不仅是为了缩短单独一次请求的寻址时间，更重要的优化在于，通过保持磁盘头以直线方向移动，缩短了所有请求的磁盘寻址的时间。关于linux中的电梯调度程序，很多操作系统的书上都已经说的很明白，我这里给出一个大致流程：&lt;/font&gt;&lt;/p&gt;&lt;table style="margin: auto 25px; color: #000000" border="1" cellspacing="0" cellpadding="2" width="814"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td valign="top" width="812"&gt;&lt;font face="微软雅黑"&gt;1.首先，如果队列中已存在一个对相邻磁盘扇区操作的请求，那么新请求将和这个已经存在的请求合并为一个请求。&lt;br&gt;2.如果队列中存在一个驻留时间过长的请求，那么新请求将被插入到队列尾部，以防止其他旧的请求发生饥饿。&lt;br&gt;3.如果队列中以扇区方向为序存在合适的插入位置，那么新的请求将被插入到该位置，保证队列中的请求是以被访问磁盘物理位置为序进行排序的。&lt;br&gt;4.如果队列中不存在合适的请求插入位置，请求将被插入到队列尾部。&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 我前边提到过电梯调度，但是有一个问题一直没提，那就是电梯调度程序的缺点：饥饿。出于减少磁盘寻址时间的考虑，对某个磁盘区域上的繁重操作，无疑会使得磁盘其他位置上的操作得不到运行机会，实际上，一个对磁盘同一位置操作的请求流可以造成较远位置的其他请求永远得不到运行机会，这是一种很不公平的饥饿现象。更糟糕的是，普通的请求饥饿还会带来写--饥饿--读这种特殊问题。我们知道写操作通常发生在内核有空时，而读操作却必须阻塞知道读请求被满足，这对系统性能影响是非常大的。而且我们知道读请求往往相互依靠，比如要读大量的文件，每次都是针对一块很小的缓冲区进行读操作，而应用程序只有将上一个数据区域从磁盘中读取并返回之后，才能继续读取下一个数据区，所以如果每一次请求都发生饥饿现象，那么对读取文件的应用程序来说，全部延迟加起来会造成过长的等待时间。减少饥饿请求必须以降低全局吞吐量为代价。为了避免这种问题，提出了最后期限IO调度程序，既要尽量提高全局吞吐量，又要使请求得到公平处理。在最后期限IO调度程序中，每个请求都有一个超时时间。默认情况下，读请求的超时时间是500ms,写请求的超时时间是5s。最后期限IO调度请求类似与linux电梯，也以磁盘物理位置为次序维护请求队列，这个队列被称为排序队列。当一个新请求递交给排序队列时，最后期限IO调度程序类似于linux电梯，合并和插入请求，但是最后期限IO调度程序同时也会以请求类型为依据将它们插入到额外队列中。读请求按次序被插入到特定的读FIFO队列中，写请求被插入到特定的写FIFO队列中。虽然普通队列以磁盘扇区为序进行排序，但是这些队列是以FIFO形式组织的，结果新队列总是被加入到队列尾部。对于普通操作来说，最后期限IO调度将请求从排序队列的头部去下，再推入到派发队列中，派发队列然后将请求提交给磁盘驱动，从而保证了最小化的请求寻址。如果在写FIFO队列头，或是在读FIFO队列头的请求超时，那么最后期限IO调度程序便从FIFO队列中提取请求进行服务。依靠这种方法，最后期限IO调度程序试图保证不会发生有请求在明显超期的情况下仍不能得到服务的现象，如下图所示：&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201108/201108020933253538.png" width="333" height="99"&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 最后期限IO调度程序的实现在文件driver/block/deadline-iosched.c中。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 虽然最后期限IO调度程序为降低读操作响应时间做了许多工作，但同时也降低了系统吞吐量。考虑这样的情况，假设一个系统正处于很繁重的写操作期间，每次提交新请求，IO调度程序都会迅速处理读请求，这样磁盘会首先为读操作寻址，执行读操作，然后返回再寻址进行写操作，并且对每个读操作都重复这个过程。这种做法明显损害了系统全局吞吐量。这事就有了预测IO调度程序。它的基础就是最后期限IO调度程序。最主要的改进是它增加了预测启发能力。它的不同之处在于读操作提交后并不直接返回处理其他请求，而是会有意空闲片刻。这空闲的几秒钟，对应用程序来说是个提交其他读请求的好机会-----任何对相邻磁盘位置操作的请求都会立刻得到处理。在等待时间结束后，预测IO调度程序重新返回原来的位置，继续执行以前剩下的请求。要注意，如果等待可以减少读请求所带来的向后再向前(back-and-forth)寻址操作，那么完全值得花一些时间来等待更多的请求(这里的时间花在对更多请求的预测上)，如果一个相邻的IO请求在等待期带来，那么IO调度程序可以节省两次寻址操作。如果存在愈来愈多的访问同样区域的读请求到来，那么片刻等待无疑会避免大量的寻址操作。当然，不得不说，如果没有IO请求在等待期到来，那么预测IO调度程序会给系统性能带来轻微的损失，浪费掉几毫秒。预测调度程序所带来的优势在于能否正确预测应用程序和文件系统的行为。这种预测依靠一系列的启发和统计工作。预测IO调度程序的实现在文件driver/block/as-iosched.c中。块设备使用哪个IO调度程序是可以选择的。默认的IO调度程序就是预测IO调度程序。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&lt;/font&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/hanyan225/aggbug/2124553.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/hanyan225/archive/2011/08/02/2124553.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/hanyan225/archive/2011/07/29/2121192.html</id><title type="text">linux内核分析笔记----虚拟文件系统(下)</title><summary type="text">接着上次的来，我今天讲虚拟文件系统剩下的一点知识. 3.目录项对象.目录项的概念上节已经说了,我就不多说.目录项中也可包括安装点.在路径/mnt/cdrom/foo中，/,mnt,cdrom都属于目录项对象。目录项由dentry结构体表示，定义在文件linux/dcache.h中，描述如下:struct dentry { atomic_t d_count; /* usage count */ u...</summary><published>2011-07-29T08:32:00Z</published><updated>2011-07-29T08:32:00Z</updated><author><name>☆&amp;amp;寒 烟☆</name><uri>http://www.cnblogs.com/hanyan225/</uri></author><link rel="alternate" href="http://www.cnblogs.com/hanyan225/archive/2011/07/29/2121192.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/hanyan225/archive/2011/07/29/2121192.html"/><content type="html">&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 接着上次的来，我今天讲虚拟文件系统剩下的一点知识.&lt;/font&gt;&lt;/p&gt; &lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3.目录项对象.目录项的概念上节已经说了,我就不多说.目录项中也可包括安装点.在路径/mnt/cdrom/foo中，/,mnt,cdrom都属于目录项对象。目录项由dentry结构体表示，定义在文件linux/dcache.h中，描述如下:&lt;/font&gt;&lt;/p&gt;struct dentry {&lt;br/&gt;        atomic_t                 d_count;      /* usage count */&lt;br/&gt;        unsigned long            d_vfs_flags;  /* dentry cache flags */&lt;br/&gt;        spinlock_t               d_lock;       /* per-dentry lock */&lt;br/&gt;        struct inode             *d_inode;     /* associated inode */&lt;br/&gt;        struct list_head         d_lru;        /* unused list */&lt;br/&gt;        struct list_head         d_child;      /* list of dentries within */&lt;br/&gt;        struct list_head         d_subdirs;    /* subdirectories */&lt;br/&gt;        struct list_head         d_alias;      /* list of alias inodes */&lt;br/&gt;        unsigned long            d_time;       /* revalidate time */&lt;br/&gt;        struct dentry_operations *d_op;        /* dentry operations table */&lt;br/&gt;        struct super_block       *d_sb;        /* superblock of file */&lt;br/&gt;        unsigned int             d_flags;      /* dentry flags */&lt;br/&gt;        int                      d_mounted;    /* is this a mount point? */&lt;br/&gt;        void                     *d_fsdata;    /* filesystem-specific data */&lt;br/&gt;        struct rcu_head          d_rcu;        /* RCU locking */&lt;br/&gt;        struct dcookie_struct    *d_cookie;    /* cookie */&lt;br/&gt;        struct dentry            *d_parent;    /* dentry object of parent */&lt;br/&gt;        struct qstr              d_name;       /* dentry name */&lt;br/&gt;        struct hlist_node        d_hash;       /* list of hash table entries */&lt;br/&gt;        struct hlist_head        *d_bucket;    /* hash bucket */&lt;br/&gt;        unsigned char            d_iname[DNAME_INLINE_LEN_MIN]; /* short name */&lt;br/&gt;};&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 由于目录项并非真正保存在磁盘上，所有目录项没有对应的磁盘数据结构，VFS根据字符串形式的路径名现场创建它，目录项结构体也没有是否被修改的标志。目录项对象有三种状态：被使用，未被使用和负状态。一个被使用的目录项对应一个有效的索引节点(即d_inode指向相应的索引节点)并且该对象存在一个或多个使用者(即d_count为正值)。一个未被使用的目录项对应一个有效的索引节点(d_inode指向一个索引节点)，但是VFS当前并未使用它(d_count为0)。该目录项对象仍然指向一个有效对象，而且被保留在内存中以便需要时再使用它。显然这样要比重新创建要效率高些。一个负状态的目录项没有对应的有效索引节点(d_inode为NULL).因为索引节点已被删除了，或路径不再正确了，但是目录项仍然保留，以便快速解析以后的路径查询。虽然负的状态目录项有些用处，但如果需要的话话，还是可以删除的，可以销毁它。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 结构体dentry_operation指明了VFS操作目录的所有方法，如下：&lt;/font&gt;&lt;/p&gt;struct dentry_operations {&lt;br/&gt;        int (*d_revalidate) (struct dentry *, int);&lt;br/&gt;        int (*d_hash) (struct dentry *, struct qstr *);&lt;br/&gt;        int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);&lt;br/&gt;        int (*d_delete) (struct dentry *);&lt;br/&gt;        void (*d_release) (struct dentry *);&lt;br/&gt;        void (*d_iput) (struct dentry *, struct inode *);&lt;br/&gt;};&lt;p&gt;&lt;font face="微软雅黑"&gt;&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 其实，如果VFS遍历路径名中所有的元素并将它们逐个地解析成目录项对象，将是一件非常耗时的事情。所以内核将目录项对象缓存在目录项缓存(dcache)中，目录项缓存包括三个主要部分：&lt;/font&gt;&lt;/p&gt;&lt;table style="margin: auto 25px; color: #000000" border="1" cellspacing="0" cellpadding="2" width="814"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td valign="top" width="812"&gt;&lt;font face="微软雅黑"&gt;1.“被使用的”目录项链表，该链表通过索引节点对象中的i_dentry项连接相关的索引节点，因为一个给定的索引节点可能有多个链接，所以就可能有多&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 个目录项对象，因此用一个链表来连接它们。&lt;br&gt;2.“最近被使用的”双向链表。该链表包含未被使用的和负状态的目录项对象。该链表是按时间插入的。&lt;br&gt;3.&amp;nbsp; 哈希表和相应的哈希函数用来快速地将给定路径解析为相关目录项对象。&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 哈希表有数组dentry_hashtable表示，其中每一个元素都是一个指向具有相同键值的目录项对象链表的指针。数组的大小取决于系统中物理内存的大小。实际的哈希值由d_hash()计算，它是内核提供给文件系统的唯一的一个哈希函数。查找哈希表要通过d_lookup()函数，如果该函数在dcache中发现了与其相匹配的目录项对像，则匹配对象被返回；否则，返回NULL指针。dcache在一定意义上也提供了对索引节点的缓存。和目录项对象相关的索引节点对象不会被释放，因为目录项会让相关索引节点的使用计数为正，这样就可以确保索引节点留在内存中。只要目录项被缓存，其相应的索引节点也就被缓存了。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 4.文件对象:文件对象表示进程以打开的文件。文件对象仅仅在进程观点上代表已打开文件，它反过来指向目录项对象(反过来指向索引节点)，其实只有目录项对象才表示已打开的实际文件。虽然一个文件对应的文件对象不是唯一的，但对应的索引节点和目录项对象无疑是唯一的。文件对象由file结构表示，定义在文件linux/fs.h中，如下:&lt;/font&gt;&lt;/p&gt;struct file {&lt;br/&gt;        struct list_head       f_list;        /* list of file objects */&lt;br/&gt;        struct dentry          *f_dentry;     /* associated dentry object */&lt;br/&gt;        struct vfsmount        *f_vfsmnt;     /* associated mounted fs */&lt;br/&gt;        struct file_operations *f_op;         /* file operations table */&lt;br/&gt;        atomic_t               f_count;       /* file object's usage count */&lt;br/&gt;        unsigned int           f_flags;       /* flags specified on open */&lt;br/&gt;        mode_t                 f_mode;        /* file access mode */&lt;br/&gt;        loff_t                 f_pos;         /* file offset (file pointer) */&lt;br/&gt;        struct fown_struct     f_owner;       /* owner data for signals */&lt;br/&gt;        unsigned int           f_uid;         /* user's UID */&lt;br/&gt;        unsigned int           f_gid;         /* user's GID */&lt;br/&gt;        int                    f_error;       /* error code */&lt;br/&gt;        struct file_ra_state   f_ra;          /* read-ahead state */&lt;br/&gt;        unsigned long          f_version;     /* version number */&lt;br/&gt;        void                   *f_security;   /* security module */&lt;br/&gt;        void                   *private_data; /* tty driver hook */&lt;br/&gt;        struct list_head       f_ep_links;    /* list of eventpoll links */&lt;br/&gt;        spinlock_t             f_ep_lock;     /* eventpoll lock */&lt;br/&gt;        struct address_space   *f_mapping;    /* page cache mapping */&lt;br/&gt;};&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 文件对象的操作有file_operations结构表示，在linux/fs.h中，如下:&lt;/font&gt;&lt;/p&gt;struct file_operations {&lt;br/&gt;        struct module *owner;&lt;br/&gt;        loff_t (*llseek) (struct file *, loff_t, int);&lt;br/&gt;        ssize_t (*read) (struct file *, char *, size_t, loff_t *);&lt;br/&gt;        ssize_t (*aio_read) (struct kiocb *, char *, size_t, loff_t);&lt;br/&gt;        ssize_t (*write) (struct file *, const char *, size_t, loff_t *);&lt;br/&gt;        ssize_t (*aio_write) (struct kiocb *, const char *, size_t, loff_t);&lt;br/&gt;        int (*readdir) (struct file *, void *, filldir_t);&lt;br/&gt;        unsigned int (*poll) (struct file *, struct poll_table_struct *);&lt;br/&gt;        int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);&lt;br/&gt;        int (*mmap) (struct file *, struct vm_area_struct *);&lt;br/&gt;        int (*open) (struct inode *, struct file *);&lt;br/&gt;        int (*flush) (struct file *);&lt;br/&gt;        int (*release) (struct inode *, struct file *);&lt;br/&gt;        int (*fsync) (struct file *, struct dentry *, int);&lt;br/&gt;        int (*aio_fsync) (struct kiocb *, int);&lt;br/&gt;        int (*fasync) (int, struct file *, int);&lt;br/&gt;        int (*lock) (struct file *, int, struct file_lock *);&lt;br/&gt;        ssize_t (*readv) (struct file *, const struct iovec *,&lt;br/&gt;                          unsigned long, loff_t *);&lt;br/&gt;        ssize_t (*writev) (struct file *, const struct iovec *,&lt;br/&gt;                           unsigned long, loff_t *);&lt;br/&gt;        ssize_t (*sendfile) (struct file *, loff_t *, size_t,&lt;br/&gt;                             read_actor_t, void *);&lt;br/&gt;        ssize_t (*sendpage) (struct file *, struct page *, int,&lt;br/&gt;                             size_t, loff_t *, int);&lt;br/&gt;        unsigned long (*get_unmapped_area) (struct file *, unsigned long,&lt;br/&gt;                                            unsigned long, unsigned long,&lt;br/&gt;                                            unsigned long);&lt;br/&gt;        int (*check_flags) (int flags);&lt;br/&gt;        int (*dir_notify) (struct file *filp, unsigned long arg);&lt;br/&gt;        int (*flock) (struct file *filp, int cmd, struct file_lock *fl);&lt;br/&gt;};&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 最后，除了以上几种VFS基础对象外，内核还使用了另外一些数据结构来管理文件系统的其它相关数据，如下：&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1.file_system_type:因为linux支持众多的文件系统，所以内核必有由一个特殊的结构来描述每种文件系统的功能和行为：&lt;/font&gt;&lt;/p&gt;struct file_system_type {&lt;br/&gt;        const char              *name;     /* filesystem's name */&lt;br/&gt;        struct subsystem        subsys;    /* sysfs subsystem object */&lt;br/&gt;        int                     fs_flags;  /* filesystem type flags */&lt;br/&gt;        /* the following is used to read the superblock off the disk */&lt;br/&gt;        struct super_block      *(*get_sb) (struct file_system_type *, int,char *, void *);&lt;br/&gt;        /* the following is used to terminate access to the superblock */&lt;br/&gt;        void                    (*kill_sb) (struct super_block *);&lt;br/&gt;        struct module           *owner;    /* module owning the filesystem */&lt;br/&gt;        struct file_system_type *next;     /* next file_system_type in list */&lt;br/&gt;        struct list_head        fs_supers; /* list of superblock objects */&lt;br/&gt;};&lt;br/&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 其中，get_sb()函数从磁盘上读取超级块，并且在文件系统被安装时，在内存中组装超级块对象，剩余的函数描述文件系统的属性。每种文件系统，不管有多少个实力安装到系统中，还是根本就没有安装到系统中，都只有一个file_system_type结构。更有趣的是，当文件系统被实际安装时，将有一个vfsmount结构体在安装点被创建。该结构体被用来代表文件系统的实例----换句话说，代表一个安装点.&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2.vfsmount结构被定义在linux/mount.h中，下面是具体结构：&lt;/font&gt;&lt;/p&gt;struct vfsmount {&lt;br/&gt;        struct list_head   mnt_hash;        /* hash table list */&lt;br/&gt;        struct vfsmount    *mnt_parent;     /* parent filesystem */&lt;br/&gt;        struct dentry      *mnt_mountpoint; /* dentry of this mount point */&lt;br/&gt;        struct dentry      *mnt_root;       /* dentry of root of this fs */&lt;br/&gt;        struct super_block *mnt_sb;         /* superblock of this filesystem */&lt;br/&gt;        struct list_head   mnt_mounts;      /* list of children */&lt;br/&gt;        struct list_head   mnt_child;       /* list of children */&lt;br/&gt;        atomic_t           mnt_count;       /* usage count */&lt;br/&gt;        int                mnt_flags;       /* mount flags */&lt;br/&gt;        char               *mnt_devname;    /* device file name */&lt;br/&gt;        struct list_head   mnt_list;        /* list of descriptors */&lt;br/&gt;        struct list_head   mnt_fslink;      /* fs-specific expiry list */&lt;br/&gt;        struct namespace   *mnt_namespace   /* associated namespace */&lt;br/&gt;};&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; vfs中维护的各种链表是为了跟踪文件系统和所有其他安装点的关系，mnt_flags保存了安装时指定的标志信息，下表给出了标准的安装标志：&lt;/font&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201107/201107291651297480.png" width="347" height="99"&gt; &lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 安装那些管理不充分信任的移动设备时，这些标志很有用处。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 系统中每一个进程都有自己的一组打开的文件，有三个数据结构将VFS层和文件的进程紧密联系在一起，它们分别是file_struct,fs_struct和namespace.&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1.file_struct:该结构体有进程描述符中的files域指向，如下：&lt;/font&gt;&lt;/p&gt;struct files_struct {&lt;br/&gt;        atomic_t    count;              /* structure's usage count */&lt;br/&gt;        spinlock_t  file_lock;          /* lock protecting this structure */&lt;br/&gt;        int         max_fds;            /* maximum number of file objects */&lt;br/&gt;        int         max_fdset;          /* maximum number of file descriptors */&lt;br/&gt;        int         next_fd;            /* next file descriptor number */&lt;br/&gt;        struct file **fd;               /* array of all file objects */&lt;br/&gt;        fd_set      *close_on_exec;     /* file descriptors to close on exec() */&lt;br/&gt;        fd_set      *open_fds;           /* pointer to open file descriptors */&lt;br/&gt;        fd_set      close_on_exec_init; /* initial files to close on exec() */&lt;br/&gt;        fd_set      open_fds_init;      /* initial set of file descriptors */&lt;br/&gt;        struct file *fd_array[NR_OPEN_DEFAULT]; /* default array of file objects */&lt;br/&gt;};&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; fd数组指针指向以打开的文件对象链表，默认情况下，指向fd_arrar数组。NR_OPEN_DEFAULT默认是32，所以该数组可以容纳32个文件对象。如果一个进程所打开的文件对象超过32个，内核将分配一个新数组，并且将fd指针指向它。这个值也是可以调整的。&lt;/font&gt;&lt;font face="微软雅黑"&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2.第二个结构体是fs_struct：由进程描述符的fs域指向。它包含文件系统和进程相关的信息，在linux/fs_struct.h中，如下：&lt;/font&gt;struct fs_struct {&lt;br/&gt;        atomic_t        count;       /* structure usage count */&lt;br/&gt;        rwlock_t        lock;        /* lock protecting structure */&lt;br/&gt;        int             umask;       /* default file permissions*/&lt;br/&gt;        struct dentry   *root;       /* dentry of the root directory */&lt;br/&gt;        struct dentry   *pwd;        /* dentry of the current directory */&lt;br/&gt;        struct dentry   *altroot;    /* dentry of the alternative root */&lt;br/&gt;        struct vfsmount *rootmnt;    /* mount object of the root directory */&lt;br/&gt;        struct vfsmount *pwdmnt;     /* mount object of the current directory */&lt;br/&gt;        struct vfsmount *altrootmnt; /* mount object of the alternative root */&lt;br/&gt;};&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 该结构包含了当前进程的当前工作目录和根目录。&lt;/font&gt; &lt;font face="微软雅黑"&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3.最后一个是namespace：由进程描述符namespace域指向，定义在linux/namespace.h中,如下:&lt;/font&gt;struct namespace {&lt;br/&gt;        atomic_t            count; /* structure usage count */&lt;br/&gt;        struct vfsmount     *root; /* mount object of root directory */&lt;br/&gt;        struct list_head    list;  /* list of mount points */&lt;br/&gt;        struct rw_semaphore sem;   /* semaphore protecting the namespace */&lt;br/&gt;};&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; list域是连接已安装文件系统的双向链表，它包含的元素组成了全体命令空间。&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 上述这些数据结构都是通过进程描述符连接起来的。对多数进程来说，它们的描述符都指向唯一的files_struct和fs_struct结构体。但是，对于那些使用克隆标志CLONE_FILES或CLONE_FS创建的进程，会共享这两个结构体。所以多个进程描述符可能指向同一个files_struct或fs_struct结构体。每个结构体都维护一个count域作为引用计数，它防止进程正使用该结构时，该结构被销毁。而namespace却不是这样，默认情况下，所有的进程共享同样的命名空间，也就是说，它们都看到同一个文件层层结构。只有在进行clone()操作时使用CLONE_NEWS标志，才会给进程一个另外的命名空间结构体的拷贝。&lt;/font&gt;&lt;img src="http://www.cnblogs.com/hanyan225/aggbug/2121192.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/hanyan225/archive/2011/07/29/2121192.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/hanyan225/archive/2011/07/29/2120658.html</id><title type="text">linux内核分析笔记----虚拟文件系统(上)</title><summary type="text">虚拟文件系统，也不知道大家听过没有，反正我是听过了！我们知道在计算机行业，很多东西都不是一定有个官方说：朋友，我最大，你们做的东西，都要是这个样子，否则是非法的。事实上，很多东西都是靠的一种实力，通过实力来慢慢在人们心中成为既定事实。这个事实同样是没有官方的。好了，问题来了，没有官方，就没有标准，没有标准就没有统一，没有统一那就是三国时代，混战当道也！ 怎么办？特别是百花争鸣的文件系统，这时li...</summary><published>2011-07-29T02:11:00Z</published><updated>2011-07-29T02:11:00Z</updated><author><name>☆&amp;amp;寒 烟☆</name><uri>http://www.cnblogs.com/hanyan225/</uri></author><link rel="alternate" href="http://www.cnblogs.com/hanyan225/archive/2011/07/29/2120658.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/hanyan225/archive/2011/07/29/2120658.html"/><content type="html">&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 虚拟文件系统，也不知道大家听过没有，反正我是听过了！我们知道在计算机行业，很多东西都不是一定有个官方说：朋友，我最大，你们做的东西，都要是这个样子，否则是非法的。事实上，很多东西都是靠的一种实力，通过实力来慢慢在人们心中成为既定事实。这个事实同样是没有官方的。好了，问题来了，没有官方，就没有标准，没有标准就没有统一，没有统一那就是三国时代，混战当道也！&lt;/font&gt;&lt;/p&gt; &lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 怎么办？特别是百花争鸣的文件系统，这时linux的内核开发者们想到了VFS(虚拟文件系统)。VFS使得用户可以直接使用open(),read()和write()这样的系统调用而不用关注具体文件系统和实际物理介质。也许你感觉不是很新奇啊，告诉你新奇的事情：在老式操作系统上(比如DOS)，任何对非本地文件系统的访问都必须依靠特殊工具才能完成。这种实现的方式是内核在它的底层文件系统接口上建立了一个抽象层。该抽象层是linux能够支持各种文件系统，即便是它们在功能和行为上存在很大差别。为了支持文件系统，VFS提供了一个通用文件系统模型，该模型囊括了我们所能想到的文件系统的常用功能和行为。这个VFS抽象层之所以能衔接各种各样的文件系统，是因为它定义了所有文件系统都支持的基本抽象接口和数据结构，同时实际系统也将自身的诸如“如何打开文件”，“目录是什么”等概念在形式上与VFS的定义保持一致。因为实际文件系统的代码在统一的接口和数据结构隐藏了具体的实现细节，所以在VFS层和内核的其他部分看来，所有文件系统都是相同的，它们都支持像文件和目录这样的概念，同时也支持像创建和删除文件这样的操作。&lt;/font&gt;&lt;/p&gt; &lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 实际文件系统通过编程提供VFS所期望的抽象接口和数据结构，这样，内核就可以毫不费力地和任何文件系统协同工作。那么接下的问题，它们直接的关系如何呢，看下边的例子：&lt;/font&gt;&lt;/p&gt;write(f,&amp;amp;buf,len);&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 该代码不用说，应该明白。这个用户调用首先被一个通用系统调用sys_write()处理，sys_write()函数要找到f所在的文件系统实际给出的是哪个写操作，然后再执行该操作。实际文件系统的写方法是文件系统实现的一部分，数据最终通过该操作写入介质。下图给出流程：&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/font&gt;&lt;img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201107/201107291011215077.png" width="429" height="127"&gt; &lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 下面我就先从整体上对unix(linux)文件系统做个概述，然后在具体下去。&lt;/font&gt;&lt;font face="微软雅黑"&gt;Unix使用了四种和文件系统相关的传统抽象概念，如下：&lt;/font&gt;&lt;/p&gt;&lt;table style="margin: auto 29px; color: #000000" border="1" cellspacing="0" cellpadding="2" width="814"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td valign="top" width="812"&gt;&lt;font face="微软雅黑"&gt;1.文件：就是一个有序字节串。&lt;br&gt;2.目录项：文件是放在目录中，目录又可以层层嵌套，形成文件路径。路径中的每一项就叫做目录项。目录是文件，这个文件列出了该目录下的所有文件.&lt;br&gt;3.索引节点:一个文件其实是由两部分组成：相关信息和文件本身。这里的相关信息指的是访问控制权限，大小，拥有者，创建时间等。文件相关信息也叫&lt;br&gt;&amp;nbsp;&amp;nbsp; 做元数据，被存储在一个单独的数据结构中，这个结构就叫做索引点(index node,简写inode)。&lt;br&gt;4.安装点(挂载点):文件系统被安装在一个特定的安装点上，该安装点在全局层次结构中被称为命名空间，所有的已安装文件系统都作为根文件树的树叶出&lt;br&gt;&amp;nbsp;&amp;nbsp; 现在系统中。&lt;br&gt;5.超级块：是一种包含文件系统信息的数据结构，里边是文件系统的控制信息。&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 对应于上图，我们知道VFS是介于用户文件和文件系统之间的一个概念，所以如果文件系统想要穿透VFS供用户空间使用，就必须经过封装，提供一个符合这些概念的界面。上述每个元素都对应一个对象，该对象有属性结构体，描述了该对象的属性。有操作结构体，包含了自身所支持的操作，下面详细介绍：&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1.超级块对象：代表一个已安装的文件系统。由数据结构super_block结构体表示，定义在linux/fs.h中。如下所示：&lt;/font&gt;&lt;/p&gt;struct super_block {&lt;br/&gt;        struct list_head        s_list;            /* list of all superblocks */&lt;br/&gt;        dev_t                   s_dev;             /* identifier */&lt;br/&gt;        unsigned long           s_blocksize;       /* block size in bytes */&lt;br/&gt;        unsigned long           s_old_blocksize;   /* old block size in bytes */&lt;br/&gt;        unsigned char           s_blocksize_bits;  /* block size in bits */&lt;br/&gt;        unsigned char           s_dirt;            /* dirty flag */&lt;br/&gt;        unsigned long long      s_maxbytes;        /* max file size */&lt;br/&gt;        struct file_system_type s_type;            /* filesystem type */&lt;br/&gt;        struct super_operations s_op;              /* superblock methods */&lt;br/&gt;        struct dquot_operations *dq_op;            /* quota methods */&lt;br/&gt;        struct quotactl_ops     *s_qcop;           /* quota control methods */&lt;br/&gt;        struct export_operations *s_export_op;     /* export methods */&lt;br/&gt;        unsigned long            s_flags;          /* mount flags */&lt;br/&gt;        unsigned long            s_magic;          /* filesystem's magic number */&lt;br/&gt;        struct dentry            *s_root;          /* directory mount point */&lt;br/&gt;        struct rw_semaphore      s_umount;         /* unmount semaphore */&lt;br/&gt;        struct semaphore         s_lock;           /* superblock semaphore */&lt;br/&gt;        int                      s_count;          /* superblock ref count */&lt;br/&gt;        int                      s_syncing;        /* filesystem syncing flag */&lt;br/&gt;        int                      s_need_sync_fs;   /* not-yet-synced flag */&lt;br/&gt;        atomic_t                 s_active;         /* active reference count */&lt;br/&gt;        void                     *s_security;      /* security module */&lt;br/&gt;        struct list_head         s_dirty;          /* list of dirty inodes */&lt;br/&gt;        struct list_head         s_io;             /* list of writebacks */&lt;br/&gt;        struct hlist_head        s_anon;           /* anonymous dentries */&lt;br/&gt;        struct list_head         s_files;          /* list of assigned files */&lt;br/&gt;        struct block_device      *s_bdev;          /* associated block device */&lt;br/&gt;        struct list_head         s_instances;      /* instances of this fs */&lt;br/&gt;        struct quota_info        s_dquot;          /* quota-specific options */&lt;br/&gt;        char                     s_id[32];         /* text name */&lt;br/&gt;        void                     *s_fs_info;       /* filesystem-specific info */&lt;br/&gt;        struct semaphore         s_vfs_rename_sem; /* rename semaphore */&lt;br/&gt;};&lt;br/&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 创建，管理和销毁超级块对象的代码位于文件fs/super.c中，超级块对象通过alloc_super()函数创建并初始化。在文件系统安装时，内核会调用该函数以便从磁盘读取文件系统超级块，并且将其信息填充到内存中的超级块对象中。其中最重要的一个是s_op,指向超级块的操作函数表，由super_operations结构体表示，定义在linux/fs.h中，如下：&lt;/font&gt;&lt;/p&gt;struct super_operations {&lt;br/&gt;        struct inode *(*alloc_inode) (struct super_block *sb);&lt;br/&gt;        void (*destroy_inode) (struct inode *);&lt;br/&gt;        void (*read_inode) (struct inode *);&lt;br/&gt;        void (*dirty_inode) (struct inode *);&lt;br/&gt;        void (*write_inode) (struct inode *, int);&lt;br/&gt;        void (*put_inode) (struct inode *);&lt;br/&gt;        void (*drop_inode) (struct inode *);&lt;br/&gt;        void (*delete_inode) (struct inode *);&lt;br/&gt;        void (*put_super) (struct super_block *);&lt;br/&gt;        void (*write_super) (struct super_block *);&lt;br/&gt;        int (*sync_fs) (struct super_block *, int);&lt;br/&gt;        void (*write_super_lockfs) (struct super_block *);&lt;br/&gt;        void (*unlockfs) (struct super_block *);&lt;br/&gt;        int (*statfs) (struct super_block *, struct statfs *);&lt;br/&gt;        int (*remount_fs) (struct super_block *, int *, char *);&lt;br/&gt;        void (*clear_inode) (struct inode *);&lt;br/&gt;        void (*umount_begin) (struct super_block *);&lt;br/&gt;        int (*show_options) (struct seq_file *, struct vfsmount *);&lt;br/&gt;};&lt;br/&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 当文件系统需要对其超级块执行操作时，首先要在超级块对象中寻找需要的操作方法。比如一个文件系统要写自己的超级块，需要调用：sb-&amp;gt;s_op-&amp;gt;write_super(sb)这里的sb是指向文件系统超级块的指针，沿着该指针进入超级块操作函数表，并从表中取得希望得到的write_super()函数，该函数执行写入超级块的实际操作。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2.索引节点对象：由inode结构体表示，定义在linux/fs.h中，如下：&lt;/font&gt;&lt;/p&gt;struct inode {&lt;br/&gt;        struct hlist_node       i_hash;              /* hash list */&lt;br/&gt;        struct list_head        i_list;              /* list of inodes */&lt;br/&gt;        struct list_head        i_dentry;            /* list of dentries */&lt;br/&gt;        unsigned long           i_ino;               /* inode number */&lt;br/&gt;        atomic_t                i_count;             /* reference counter */&lt;br/&gt;        umode_t                 i_mode;              /* access permissions */&lt;br/&gt;        unsigned int            i_nlink;             /* number of hard links */&lt;br/&gt;        uid_t                   i_uid;               /* user id of owner */&lt;br/&gt;        gid_t                   i_gid;               /* group id of owner */&lt;br/&gt;        kdev_t                  i_rdev;              /* real device node */&lt;br/&gt;        loff_t                  i_size;              /* file size in bytes */&lt;br/&gt;        struct timespec         i_atime;             /* last access time */&lt;br/&gt;        struct timespec         i_mtime;             /* last modify time */&lt;br/&gt;        struct timespec         i_ctime;             /* last change time */&lt;br/&gt;        unsigned int            i_blkbits;           /* block size in bits */&lt;br/&gt;        unsigned long           i_blksize;           /* block size in bytes */&lt;br/&gt;        unsigned long           i_version;           /* version number */&lt;br/&gt;        unsigned long           i_blocks;            /* file size in blocks */&lt;br/&gt;        unsigned short          i_bytes;             /* bytes consumed */&lt;br/&gt;        spinlock_t              i_lock;              /* spinlock */&lt;br/&gt;        struct rw_semaphore     i_alloc_sem;         /* nests inside of i_sem */&lt;br/&gt;        struct semaphore        i_sem;               /* inode semaphore */&lt;br/&gt;        struct inode_operations *i_op;               /* inode ops table */&lt;br/&gt;        struct file_operations  *i_fop;              /* default inode ops */&lt;br/&gt;        struct super_block      *i_sb;               /* associated superblock */&lt;br/&gt;        struct file_lock        *i_flock;            /* file lock list */&lt;br/&gt;        struct address_space    *i_mapping;          /* associated mapping */&lt;br/&gt;        struct address_space    i_data;              /* mapping for device */&lt;br/&gt;        struct dquot            *i_dquot[MAXQUOTAS]; /* disk quotas for inode */&lt;br/&gt;        struct list_head        i_devices;           /* list of block devices */&lt;br/&gt;        struct pipe_inode_info  *i_pipe;             /* pipe information */&lt;br/&gt;        struct block_device     *i_bdev;             /* block device driver */&lt;br/&gt;        unsigned long           i_dnotify_mask;      /* directory notify mask */&lt;br/&gt;        struct dnotify_struct   *i_dnotify;          /* dnotify */&lt;br/&gt;        unsigned long           i_state;             /* state flags */&lt;br/&gt;        unsigned long           dirtied_when;        /* first dirtying time */&lt;br/&gt;        unsigned int            i_flags;             /* filesystem flags */&lt;br/&gt;        unsigned char           i_sock;              /* is this a socket? */&lt;br/&gt;        atomic_t                i_writecount;        /* count of writers */&lt;br/&gt;        void                    *i_security;         /* security module */&lt;br/&gt;        __u32                   i_generation;        /* inode version number */&lt;br/&gt;        union {&lt;br/&gt;                void            *generic_ip;         /* filesystem-specific info */&lt;br/&gt;        } u;&lt;br/&gt;};&lt;br/&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 有时，某些文件系统可能并不能完整的包含索引节点结构体要求的所有信息。举个例子，有的文件系统可能并不记录文件的创建时间，这时，该文件系统就可以在实现中选择任意合适的办法来解决这个问题，它可以在i_ctime中存储0，或者让i_ctime等于i_mtime,甚至任何其他值。索引节点对象中的inode_operations项存放了操作函数列表，定义在linux/fs.h中，如下：&lt;/font&gt;&lt;/p&gt;struct inode_operations {&lt;br/&gt;        int (*create) (struct inode *, struct dentry *,int);&lt;br/&gt;        struct dentry * (*lookup) (struct inode *, struct dentry *);&lt;br/&gt;        int (*link) (struct dentry *, struct inode *, struct dentry *);&lt;br/&gt;        int (*unlink) (struct inode *, struct dentry *);&lt;br/&gt;        int (*symlink) (struct inode *, struct dentry *, const char *);&lt;br/&gt;        int (*mkdir) (struct inode *, struct dentry *, int);&lt;br/&gt;        int (*rmdir) (struct inode *, struct dentry *);&lt;br/&gt;        int (*mknod) (struct inode *, struct dentry *, int, dev_t);&lt;br/&gt;        int (*rename) (struct inode *, struct dentry *, struct inode *, struct dentry *);&lt;br/&gt;        int (*readlink) (struct dentry *, char *, int);&lt;br/&gt;        int (*follow_link) (struct dentry *, struct nameidata *);&lt;br/&gt;        int (*put_link) (struct dentry *, struct nameidata *);&lt;br/&gt;        void (*truncate) (struct inode *);&lt;br/&gt;        int (*permission) (struct inode *, int);&lt;br/&gt;        int (*setattr) (struct dentry *, struct iattr *);&lt;br/&gt;        int (*getattr) (struct vfsmount *, struct dentry *, struct kstat *);&lt;br/&gt;        int (*setxattr) (struct dentry *, const char *,const void *, size_t, int);&lt;br/&gt;        ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t);&lt;br/&gt;        ssize_t (*listxattr) (struct dentry *, char *, size_t);&lt;br/&gt;        int (*removexattr) (struct dentry *, const char *);&lt;br/&gt;};&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 同样，操作调用时，用以下方式：i-&amp;gt;i_op-&amp;gt;truncate(i).&lt;/font&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 由于版面原因，我不得不分两次说了，下次继续后面有关虚拟文件系统的剩余部分.&lt;/font&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/hanyan225/aggbug/2120658.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/hanyan225/archive/2011/07/29/2120658.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/hanyan225/archive/2011/07/28/2119628.html</id><title type="text">linux内核分析笔记----内存管理</title><summary type="text">内存管理，不用多说，言简意赅。在内核里分配内存还真不是件容易的事情，根本上是因为内核不能想用户空间那样奢侈的使用内存。 先来说说内存管理。内核把物理页作为内存管理的基本单位。尽管处理器的最小可寻址单位通常是字，但是，内存管理单元MMU通常以页为单位进行处理。因此，从虚拟内存的交代来看，页就是最小单位。内核用struct page(linux/mm.h)结构表示系统中的每个物理页:struct p...</summary><published>2011-07-28T07:42:00Z</published><updated>2011-07-28T07:42:00Z</updated><author><name>☆&amp;amp;寒 烟☆</name><uri>http://www.cnblogs.com/hanyan225/</uri></author><link rel="alternate" href="http://www.cnblogs.com/hanyan225/archive/2011/07/28/2119628.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/hanyan225/archive/2011/07/28/2119628.html"/><content type="html">&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 内存管理，不用多说，言简意赅。在内核里分配内存还真不是件容易的事情，根本上是因为内核不能想用户空间那样奢侈的使用内存。&lt;/font&gt;&lt;/p&gt; &lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 先来说说内存管理。内核把物理页作为内存管理的基本单位。尽管处理器的最小可寻址单位通常是字，但是，内存管理单元MMU通常以页为单位进行处理。因此，从虚拟内存的交代来看，页就是最小单位。内核用struct&amp;nbsp; page(linux/mm.h)结构表示系统中的每个物理页:&lt;/font&gt;&lt;/p&gt;struct page {&lt;br/&gt;         unsigned long flags;                                                      &lt;br/&gt;         atomic_t count;                &lt;br/&gt;         unsigned int mapcount;          &lt;br/&gt;         unsigned long private;          &lt;br/&gt;         struct address_space *mapping;  &lt;br/&gt;         pgoff_t index;                  &lt;br/&gt;         struct list_head lru;  &lt;br/&gt; union{&lt;br/&gt; struct pte_chain;&lt;br/&gt;pte_addr_t;&lt;br/&gt; }         &lt;br/&gt;         void *virtual;                  &lt;br/&gt;};&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; flag用来存放页的状态，每一位代表一种状态，所以至少可以同时表示出32中不同的状态,这些状态定义在linux/page-flags.h中。count记录了该页被引用了多少次。mapping指向与该页相关的address_space对象。virtual是页的虚拟地址，它就是页在虚拟内存中的地址。要理解的一点是page结构与物理页相关，而并非与虚拟页相关。因此，该结构对页的描述是短暂的。内核仅仅用这个结构来描述当前时刻在相关的物理页中存放的东西。这种数据结构的目的在于描述物理内存本身，而不是描述包含在其中的数据。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 在linux中，内核也不是对所有的也都一视同仁，内核而是把页分为不同的区，使用区来对具有相似特性的页进行分组。Linux必须处理如下两种硬件存在缺陷而引起的内存寻址问题：&lt;/font&gt;&lt;/p&gt;&lt;table style="margin: auto 29px; color: #000000" border="1" cellspacing="0" cellpadding="2" width="814"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td valign="top" width="812"&gt;&lt;font face="微软雅黑"&gt;1.一些硬件只能用某些特定的内存地址来执行DMA&lt;br&gt;2.一些体系结构其内存的物理寻址范围比虚拟寻址范围大的多。这样，就有一些内存不能永久地映射在内核空间上。&lt;br&gt;为了解决这些制约条件，Linux使用了三种区：&lt;br&gt;1.ZONE_DMA:这个区包含的页用来执行DMA操作。&lt;br&gt;2.ZONE_NOMAL:这个区包含的都是能正常映射的页。&lt;br&gt;3.ZONE_HIGHEM:这个区包"高端内存"，其中的页能不永久地映射到内核地址空间。&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 区的实际使用与体系结构是相关的。linux 把系统的页划分区，形成不同的内存池，这样就可以根据用途进行分配了。需要说明的是，区的划分没有任何物理意义，只不过是内核为了管理页而采取的一种逻辑上的分组。尽管某些分配可能需要从特定的区中获得页，但这并不是说，某种用途的内存一定要从对应的区来获取，如果这种可供分配的资源不够用了，内核就会占用其他可用去的内存。下表给出每个区及其在X86上所占的列表：&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201107/201107281542128476.png" width="514" height="99"&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 每个区都用定义在linux/mmzone.h中的struct zone表示，如下：&lt;/font&gt;&lt;/p&gt;struct zone {&lt;br/&gt;         spinlock_t              lock;&lt;br/&gt;         unsigned long           free_pages;&lt;br/&gt;         unsigned long           pages_min, pages_low, pages_high;&lt;br/&gt;         unsigned long           protection[MAX_NR_ZONES];&lt;br/&gt;         spinlock_t              lru_lock;       &lt;br/&gt;         struct list_head        active_list;&lt;br/&gt;         struct list_head        inactive_list;&lt;br/&gt;         unsigned long           nr_scan_active;&lt;br/&gt;         unsigned long           nr_scan_inactive;&lt;br/&gt;         unsigned long           nr_active;&lt;br/&gt;         unsigned long           nr_inactive;&lt;br/&gt;         int                     all_unreclaimable; &lt;br/&gt;         unsigned long           pages_scanned;    &lt;br/&gt;         struct free_area        free_area[MAX_ORDER];&lt;br/&gt;         wait_queue_head_t       * wait_table;&lt;br/&gt;         unsigned long           wait_table_size;&lt;br/&gt;         unsigned long           wait_table_bits;&lt;br/&gt;         struct per_cpu_pageset  pageset[NR_CPUS];&lt;br/&gt;         struct pglist_data      *zone_pgdat;&lt;br/&gt;         struct page             *zone_mem_map;&lt;br/&gt;         unsigned long           zone_start_pfn;&lt;br/&gt;&lt;br/&gt;         char                    *name;&lt;br/&gt;         unsigned long           spanned_pages;  &lt;br/&gt;         unsigned long           present_pages;  &lt;br/&gt;};&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 其中的lock域是一个自旋锁，这个域只保护结构，而不是保护驻留在这个区中的所有页。没有特定的锁来保护单个页。free_pages域是这个区中空闲页的个数。内核尽可能的保护有pages_min个空闲页可用。name域是一个以NULL结束的字符串，表示这个区的名字。内核启动期间初始化这个值，其代码位于mm/page_alloc.h中，三个区的名字分别是"DMA","Normal","HighMem"。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;内核提供了一种请求内层的底层机制，并提供了对它进行访问的几个接口。所有这些接口都是以页为单位进行操作的。下表给出所有底层的页分配方法：&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201107/20110728154213504.png" width="526" height="233"&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 当你不再需要页时可以用下列函数释放它们，只是提醒：仅能释放属于你的页，否则可能导致系统崩溃。内核是完全信任自己的，如果有非法操作，内核会开心的把自己挂起来，停止运行。列表如下：&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201107/201107281542149881.png" width="442" height="41"&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 上面提到都是以页为单位的分配方式，那么对于常用的以字节为单位的分配来说，内核通供的函数是kmalloc(),和mallloc很像吧，其实还真是这样，只不过多了一个flags参数。用它可以获得以字节为单位的一块内核内存。如果需要的是页----尤其是在你的需求总量接近2的幂次方的时候----那么，前面讨论的页分配接口可能是更好的选择。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 接下来，注意的话，可能会发现无论是页分配接口还是kmalloc都有一个分配器标志(如GFP_KERNEL这样的)。这些标志可分为三类：行为修饰符，区修饰符及类型.下面就来讨论个问题.&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1.行为修饰符(linux/gfp.h)：表示内核应当如何分配所需的内存。在某些特定的情况下，只能使用某些特定的方法分配内存。可以同时使用这些标志，用|链接。列表如下：&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201107/201107281542155638.png" width="485" height="287"&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2.区分配符：它只关心去应当从何处分配。通常，分配可以从任何区开始。不过，内核优先从ZONE_NORMAL开始，这样可以确保其他区在需要时有足够的空闲页可以使用。区修饰符如下：&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201107/20110728154216555.png" width="484" height="73"&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 不能给_get_free_pages()指定ZONE_HIGHMEM,因为这个函数返回都是逻辑地址，而不是page结构。这两个函数分配的内存当前可能有可能还没有映射到内核的虚拟地址空间，因此，也可能根本就没有逻辑地址。只有alloc_pages()才能分配高端内存。实际上，大多数ZONE_NORMAL就已经足够了。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3.类型标志：指定所需的行为和区描述符以完成特殊类型的处理。正因为这点，内核代码趋向于使用正确的类型标志，而不是一味地指定它可能需要用到的多个描述符。下面两个表分别给出了类型标志的列表和每个类型标志与哪些修饰符相关联：&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201107/20110728154217107.png" width="591" height="326"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201107/201107281542186420.png" width="434" height="218"&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 上表中，左边是类型标志，右边是每种类型标志后隐含的修饰符列表。在编写的大多数代码中，用到的要么是GFP_KERNEL,要么是GFP_ATOMIC。下表是通常情形和所用标志的列表，不管使用那种分配类型，你都必须进行检查，并对错误进行处理：&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201107/20110728154219225.png" width="563" height="200"&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 有了kmalloc，当然就有kfree()(linux/slab.h)，释放由kmalloc()分配出来的内存块。如果想要释放的内存不是由kmalloc()分配的，或者想要释放的内存早就被释放了，在这种情况下调用这个函数会导致严重的后果。特别说明kfree(NULL)是安全的。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; vmalloc()和kmalloc是一样的作用，不同在于前者分配的内存虚拟地址是连续的，而物理地址则无需连续。这也是用户空间分配函数的工作方式，如malloc().kmalloc()可以保证在物理地址上都是连续的(当然，虚拟地址当然也是连续的)。vmalloc()函数只确保页在虚拟机地址空间内是连续的。它通过分配非联系的物理内存块，再“修正”页表，把内存映射到逻辑地址空间的连续区域中，就能做到这点。但很显然这样会降低处理性能，因为内核不得不做“拼接”的工作。所以这也是为什么不得已才使用vmalloc()的原因(比如获得大内存时)。大多数情况下，只有硬件设备需要得到物理地址连续的内存。硬件设备存在于内存管理单元以外，它根本不懂什么是虚拟地址。因此，硬件设备用到的任何内存区都必须是物理上连续的块，而不仅仅是虚地址连续的块。最后需要说明的是，vmalloc()可能睡眠，不能从中断上下文中进行调用，也不能从其他不允许阻塞的情况下进行调用。释放时必须使用vfree().&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 分配和释放数据结构是所有内核中最普遍的操作之一。为了便于数据的频繁分配和回收，常常会用到一个空间链表。它就相当于对象高速缓存以便快速存储频繁使用的对象类型。在内核中，空闲链表面临的主要问题之一是不能全局控制。当可用内存变得紧张的时候，内核无法通知每个空闲链表，让其收缩缓存的大小以便释放一些内存来。实际上，内核根本不知道有这样的空闲离岸边。为了弥补这一缺陷，也为了是代码更加稳固，linux内核提供了slab层(也就是所谓的slab分类器)，slab分类器扮演了通用数据结构缓存层的角色。slab分配器试图在如下几个原则中寻求一种平衡：&lt;/font&gt;&lt;/p&gt;&lt;table style="margin: auto 29px; color: #000000" border="1" cellspacing="0" cellpadding="2" width="814"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td valign="top" width="812"&gt;&lt;font face="微软雅黑"&gt;1.频繁使用的数据结构也会频繁分配和释放，因此应当缓存它们。&lt;br&gt;2.频繁分配和回收必然会导致内存碎片。为了避免这种情况，空闲链表的缓存会连续地存放。因为已释放的数据结构又会放回空闲链表，不会导致碎片。&lt;br&gt;3.回收的对象可以立即投入下一次分配，因此，对于频繁的分配和释放，空闲链表能够提高其性能。&lt;br&gt;4.如果让部分缓存专属于单个处理器，那么，分配和释放就可以在不加SMP锁的情况下进行。&lt;br&gt;5.对存放的对象进行着色，以防止多个对象映射到相同的高速缓存行。&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; slab层把不同的对象划分为所谓的高速缓存组，其中每个高速缓存都存放不同类型的对象，每种对象类型对应一个高速缓存。kmalloc()接口建立在slab层上，使用了一组通用高速缓存。这些缓存又被分为slabs，slab由一个或多个物理上连续的页组成，一般情况下，slab也就仅仅由一页组成。每个高速缓存可以由多个slab组成。每个slab都包含一些对象成员，这里的对象指的是被缓存的数据结构，每个slab处于三种状态之一：满，部分满，空。当内核的某一部分需要一个新的对象时，先从部分满的slab中进行分配。如果没有部分满的slab，就从空的slab中进行分配。如果没有空的slab，就要创建一个slab了。下图给出高速缓存，slab及对象之间的关系：&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201107/201107281542206779.png" width="333" height="207"&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 上图中的每个cache由kmem_cache_s结构表示，这个结构包含三个链表slabs_full,slab_partial和slabs_empty,均存放在kmem_list3结构内，这些链表包含高速缓存中的所有slab，slab描述符struct slab：&lt;/font&gt;&lt;/p&gt;struct slab {&lt;br/&gt;        struct list_head  list;       /*满，部分满或空链表*/&lt;br/&gt;        unsigned long     colouroff;  /*slab着色的偏移量*/&lt;br/&gt;        void              *s_mem;     /*在slab中的第一个对象*/&lt;br/&gt;        unsigned int      inuse;      /*已分配的对象数*/&lt;br/&gt;        kmem_bufctl_t     free;       /*第一个空闲对象*/&lt;br/&gt;};&lt;br/&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; slab描述符要么在slab之外另行分配，要么就在slab自身最开始的地方。如果slab很小或者slab内核有足够的空间容纳slab描述符，那么描述符就存放在slab里面.slab分配器创建新的slab是通过__get_free_pages()低级内存分配器进行的：&lt;/font&gt;&lt;/p&gt;static inline void * kmem_getpages(kmem_cache_t *cachep, unsigned long flags)&lt;br/&gt;{&lt;br/&gt;        void *addr;&lt;br/&gt;        flags |= cachep-&amp;gt;gfpflags;&lt;br/&gt;        addr = (void*)__get_free_pages(flags, cachep-&amp;gt;gfporder);&lt;br/&gt;        return addr;&lt;br/&gt;}&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 上面的是一个描述原理的简化版。接着，调用kmem_freepages()释放内存，而对给定的高速缓存页，kmem_freepages()最终调用的是free_pages().当然，slab层的关键就是避免频繁分配和释放页。由此可知，slab页只有当给定的高速缓存中既没有部分满也没有空的slab时候才会调用页分配函数。而只有在下列情况下才会调用释放函数：当可用内存变得紧缺时，系统试图释放出更多内存以供使用，或者当高速缓存显式地被销毁时。slab层的管理是在每个高速缓存的基础上，通过提供个整个内核一个简单的接口来完成的。通过接口就可以创建和销毁新的高速缓存，并在高速缓存内分配和释放对象。高速缓存及slab的复杂管理完全通过slab层的内部机制来处理。当创建一个高速缓存后，slab层所起的作用就像一个专用的分配器，可以为具体的对象类型进行分配。一个新的高速缓存是通过一下接口进行创建的：&lt;/font&gt;&lt;/p&gt;kmem_cache_t * kmem_cache_create(const char *name, size_t size,size_t align, unsigned long flags,                                 void (*ctor)(void*, kmem_cache_t *, unsigned long),&lt;br/&gt;                                 void (*dtor)(void*, kmem_cache_t *, unsigned long));&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 有关这个函数的说明，我就省略了，需要的网上一大堆。这个函数成功时会返回一个执行所创建高速缓存的指针，否则，返回空。这个函数由于会睡眠，因此不能在中断上下文中使用。要销毁一个高速缓存，调用：int kmem_cache_destroy(kmem_cache_t *cachep)，同样，也是不能在中断上下文中使用。调用该函数之前必须确保存在以下两个条件：&lt;/font&gt;&lt;table style="margin: auto 29px; color: #000000" border="1" cellspacing="0" cellpadding="2" width="814"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td valign="top" width="812"&gt;&lt;font face="微软雅黑"&gt;1.高速缓存中的所有slab都必须为空。&lt;br&gt;2.在调用kmem_cache_destory()期间不再访问这个高速缓存，调用者必须确保这种同步。&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 创建了高速缓存以后，就可以通过下列函数从中获取对象：void * kmem_cache_alloc(kmem_cache_t *cachep, int flags)。该函数从高速缓存cachep中返回一个指向对象的指针。如果高速缓存的所有slab中都没有空闲的对象，那么slab层必须通过kmem_getpages()获取新的页，flags的值传递给__get_free_pages().最后，释放一个对象，并把它返回给原来的slab，可以使用下面的函数：&lt;/font&gt;&lt;/p&gt;void kmem_cache_free(kmem_cache_t *cachep,void *objp)&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 这样就能把cachep中的对象objp标记为空闲了，关于slab分配器的使用实例，参考资料上有，我就不说了。相比较以前的用户空间栈而言，内核栈是非常小的。每个进程都有自己的内核栈进程在内核执行期间的整个调用链必须放在自己的内核栈上。中断处理程序也使用被它们打断的进程的堆栈。这就意味着，在最恶劣的情况下，8kB的内核栈可能会由多个函数的嵌套调用链和几个中断处理程序来共享。显然，深度的嵌套会导致溢出。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 根据定义，在高端内存中的页不能永久地映射到内核地址空间上。因此，通过alloc_pages()函数以__GFP_HIGHMEM标志获得的页不可能有逻辑地址。一旦这些页被分配，就必须映射到内核的逻辑地址空间上。要映射一个给定的page结构到内核地址空间，可以使用void *kmap(struct page *page) 这个函数在高端内存或低端内存上都能用。如果page结构对应的是低端内存中的一页，函数只会单纯地返回该页的虚拟地址，如果页位于高端内存，则会建立一个永久映射，在返回地址。这个函数可以睡眠，所以kmap()只能用在进程上下文中。当不再需要内存映射的时候，就用下列函数进行解除映射：&lt;/font&gt;&lt;/p&gt;void kunmem(struct page* page)&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 当必须创建一个映射而当前的上下文又不能睡眠时，内核提供了临时睡眠(也就是原子睡眠)。只要有一组保留的永久映射，它们就可以临时持有新创建的一个映射。内核可以原子地把高端内存中的一个页映射到某个保留的映射中。因此，临时映射可以用在不能睡眠的地方。建立临时映射：void *kmap＿atomic(struct page *page,enum km_type type).参数type是下列枚举类型之一，描述了临时映射的目的，如下：&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201107/201107281542207236.png" width="215" height="246"&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 这个函数不会阻塞，它也禁止内核抢占，通过函数void *kunmap＿atomic(void *kvaddr,enum km_type type).这个函数还是不会映射。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 最后，我们总结一下，说说分配函数的选择吧，总结如下：&lt;/font&gt;&lt;/p&gt;&lt;table style="margin: auto 29px; color: #000000" border="1" cellspacing="0" cellpadding="2" width="814"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td valign="top" width="812"&gt;&lt;font face="微软雅黑"&gt;1.如果需要连续的物理页，就可以使用某个低级页分配器或kmalloc().&lt;br&gt;2.如果想从高端内存进行分配，使用alloc_pages().&lt;br&gt;3.如果不需要物理上连续的页，而仅仅是虚拟地址上连续的页，那么就是用vmalloc&lt;br&gt;4.如果要创建和销毁很多大的数据结构，那么考虑建立slab高速缓存。&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 好了，有关内存管理的也说完了，其实也不算我说，有很多都是参考书上资料的。&lt;/font&gt;&lt;img src="http://www.cnblogs.com/hanyan225/aggbug/2119628.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/hanyan225/archive/2011/07/28/2119628.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/hanyan225/archive/2011/07/26/2117158.html</id><title type="text">linux内核分析笔记----定时器和时间管理</title><summary type="text">在这一次里，主要讲讲和时间相关的东西，这个我们都比较熟悉，我就直接如主题。 首先要明白两个概念：系统定时器和动态定时器。周期性产生的事件都是有系统定时器驱动的，这里的系统定时器是一种可编程硬件芯片，它能以固定频率产生中断。该中断就是定时器中断，它所对应的中断处理程序负责更新系统时间，也负责执行需要周期行运行的任务。系统定时器和时钟中断处理程序是Linux系统内核管理机制中的中枢。动态定时器是用来推迟执行程序的工具。内核可以动态创建或销毁动态定时器。 内核必须在硬件的帮助下才能计算和管理时间。硬件为内核提供了一个系统定时器用以计算流逝的时间，该时钟在内核中可看成是一个电子时间资源。系统定时器以.</summary><published>2011-07-26T06:07:00Z</published><updated>2011-07-26T06:07:00Z</updated><author><name>☆&amp;amp;寒 烟☆</name><uri>http://www.cnblogs.com/hanyan225/</uri></author><link rel="alternate" href="http://www.cnblogs.com/hanyan225/archive/2011/07/26/2117158.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/hanyan225/archive/2011/07/26/2117158.html"/><content type="html">&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 在这一次里，主要讲讲和时间相关的东西，这个我们都比较熟悉，我就直接如主题。&lt;/font&gt;&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;font face="微软雅黑"&gt;首先要明白两个概念：系统定时器和动态定时器。周期性产生的事件都是有系统定时器驱动的，这里的系统定时器是一种可编程硬件芯片，它能以固定频率产生中断。该中断就是定时器中断，它所对应的中断处理程序负责更新系统时间，也负责执行需要周期行运行的任务。系统定时器和时钟中断处理程序是Linux系统内核管理机制中的中枢。动态定时器是用来推迟执行程序的工具。内核可以动态创建或销毁动态定时器。&lt;/font&gt;&lt;/p&gt; &lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 内核必须在硬件的帮助下才能计算和管理时间。硬件为内核提供了一个系统定时器用以计算流逝的时间，该时钟在内核中可看成是一个电子时间资源。系统定时器以某种频率自行触发时钟中断，该频率可以通过编程预定称为节拍率(tick rate).当时钟中断发生时，内核就通过一种特殊的中断处理程序对其进行处理。系统定时器频率(节拍率)是通过静态预处理定义的，也就是HZ.在系统启动时按照HZ值对硬件进行设置。体系结构不一样，HZ的值也不同，定义在asm/param.h中。刚提到的节拍率就是这个意思。周期是1/HZ秒。最后要说明的是这个HZ值在编写内核代码时，不是固定不变的，而是可调的。当然，对于操作系统而言，也并不是一定要这个固定的时钟中断。实际上，内核可以使用动态编程定时器操作挂起事件。这里就不多说了。&lt;/font&gt;&lt;/p&gt; &lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 在linux内核里，有一个叫jiffies的变量(定义在linux/jiffies)记录了自系统启动以来产生的节拍的总数。启动时，内核将该变量初始化为0，此后每次时钟中断处理程序都会增加该变量的值。因为一秒内时钟中断的次数等于HZ,所以jiffies一秒内增加的值也就为HZ.系统运行时间以秒为单位计算，就等于jiffes/HZ.它作为在计算机表示的变量，就总存在大小，当这个变量增加到超出它的表示上限时，就要回绕到0.这个回绕看起来很简单，但实际上还是给我们编程造成了很大的麻烦，比如边界条件判断时。幸好，内核提供了四个宏来帮助比较节拍计数，这些宏定义在linux/jiffies.h可以很好的处理节拍回绕的情况：&lt;/font&gt;&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="1" border="0" alt="1" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201107/201107261407233329.png" width="473" height="79"&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;font color="#0000ff"&gt;说明:unknown参数通常是jiffies，known参数是需要对比的值。&lt;/font&gt;&lt;/font&gt;&lt;/p&gt; &lt;p&gt;&lt;font color="#000000"&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 如果改变内核中的HZ的值则会给用户空间中某些程序造成异常结果，这是因为内核是以节拍数/秒的形式给用户空间导出这个值的，在这个接口稳定了很长一段时间后，应用程序便逐渐依赖于这个特定的HZ的值了。所以如果在内核中更改了HZ的定义值，就打破了用户空间的常量关系----用户空间并不知道这个新的HZ的值。为了解决这个问题，内核必须更改所有导出的jiffies的值。内核定义了USER_HZ来代表用户空间看到的HZ值。内核可以使用宏jiffies_to_clock_t()将一个由HZ表示的节拍计数转换成一个由USER_HZ表示的节拍数。改宏的用法取决于USER_HZ是否为HZ的整数倍或相反。当是整数倍时，宏的形式相当简单：&lt;/font&gt;&lt;/font&gt;&lt;/p&gt;#define jiffies_to_clock_t(x) ((x)/(HZ/USER_HZ));&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 如果不是整数倍关系，那么该宏就得用更为复杂的算法了。同样的，如果是64位系统，内核使用函数jiffies_64_to_clock()将64位的jiffies值的单位从HZ转换为USER_HZ.&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 体系结构提供了两种设备进行计时：系统定时器和实时时钟。系统定时器提供一种周期性触发中断机制。实时时钟(RTC)是用来持久存储系统时间的设备，即便系统关闭后，它也可以靠主板上的微型电池提供的电力保护系统的计时。当系统启动时，内核通过读取RTC来初始化墙上时间，该时间存放在xtime变量中，实时时钟最主要的作用是在启动时初始化xtime变量。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 有了上面的概念基础，下面就分析时钟中断处理程序。它分为两个部分：体系结构相关部分和体系结构无关部分。相关的部分作为系统定时器的中断处理程序而注册到内核中，以便在产生时钟中断时，它能够相应地运行。执行的工作如下：&lt;/font&gt;&lt;/p&gt;&lt;table style="margin: auto 29px; color: #000000" border="1" cellspacing="0" cellpadding="2" width="814"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td valign="top" width="812"&gt;&lt;font face="微软雅黑"&gt;1.获得xtime_lock锁，以便对访问jiffies_64和墙上时间xtime进行保护。&lt;br&gt;2.需要时应答或重新设置系统时钟。&lt;br&gt;3.周期性地使用墙上时间更新实时时钟。&lt;br&gt;4.调用体系结构无关的时间例程：do_timer().&lt;br&gt;&lt;font color="#0000ff"&gt;中断服务程序主要通过调用与体系结构无关的例程do_timer()执行下面的工作：&lt;br&gt;&lt;/font&gt;1.给jiffies_64变量加1.&lt;br&gt;2.更新资源消耗的统计值，比如当前进程所消耗的系统时间和用户时间。&lt;br&gt;3.执行已经到期的动态定时器.&lt;br&gt;4.执行scheduler_tick()函数.&lt;br&gt;5.更新墙上时间，该时间存放在xtime变量中.&lt;br&gt;6.计算平均负载值.&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; do_timer看起来还是很简单的，应为它的主要工作就是完成上面的框架，具体的让其它函数做就好了：&lt;/font&gt;&lt;/p&gt;void do_timer(struct pt_regs *regs)&lt;br/&gt;{&lt;br/&gt;jiffies_64++;&lt;br/&gt;update_process_times(user_mode(regs));&lt;br/&gt;update_times();&lt;br/&gt;}&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 上述user_mode()宏查询处理器寄存器regs的状态，如果时钟中断发生在用户空间，它返回1;如果发生在内核模式，则返回0.update_process_times()函数根据时钟中断产生的位置，对用户或对系统进行相应的时间更新：&lt;/font&gt;&lt;/p&gt;void update_process_times(int user_tick)&lt;br/&gt;{&lt;br/&gt;struct task_struct *p=current;&lt;br/&gt;int cpu=smp_processor_id();&lt;br/&gt;int system=user_tick^1;&lt;br/&gt;updata_one_process(p,user_tick,system,cpu);&lt;br/&gt;run_local_timers();&lt;br/&gt;scheduler_tick(user_tick,system);&lt;br/&gt;}&lt;font face="微软雅黑"&gt;&lt;/font&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; update_one_process()函数的作用是更新进程时间。它的实现是相当细致的。但注意，因为使用了XOR操作，所以user_tick和system两个变量只要其中有一个为1，则另外一个就必须为0，updates_one_process()函数可以通过判断分支，将user_tick和system加到进程相应的计数上：&lt;/font&gt;&lt;/p&gt;p-&amp;gt;utime = user;&lt;br/&gt;p-&amp;gt;stime = system;&lt;font face="微软雅黑"&gt;&lt;/font&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 上述操作将适当的计数值增加1，而另外一个值保持不变。也许你已经发现了，这样做意味着内核对进程时间计数时，是根据中断发生时处理器所处的模式进行分类统计的，它把上一个tick全部算给进程。但是事实上进程在上一个节拍器间可能多次进入和退出内核模式，而在在上一个节拍期间，该进程也不一定是唯一一个运行进程，但是这没办法。接下来的run_lock_times() 函数标记了一个软中断去处理所有到期的定时器。最后，scheduler_tick()函数负责减少当前运行进程的时间片计数值并且在需要时设置need_resched标志，在SMP机器中中，该函数还要负责平衡每个处理器上的运行队列。当update_process_times()函数返回时，do_timer()函数接着会调用update_times()更新墙上时间。&lt;/font&gt;&lt;/p&gt;void update_times(void)&lt;br/&gt;{&lt;br/&gt;unsigned long ticks;&lt;br/&gt;if(ticks){&lt;br/&gt;wall_jiffies += ticks;&lt;br/&gt;update_wall_time(ticks);&lt;br/&gt;}&lt;br/&gt;last_time_offset = 0;&lt;br/&gt;calc_load(ticks);&lt;br/&gt;}&lt;font face="微软雅黑"&gt;&lt;/font&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 这里的ticks记录最近一次更新后新产生的节拍数。通常情况下ticks显然应该等于1.但是时钟中断也有可能丢失，因而节拍也会丢失。在中断长时间被禁止的情况下，就会出现这种现象(这种情况并不常见，往往是个BUG).wall_jiffies值随后被加上ticks----所以此刻wall_jiffies值就等于更新的墙上时间的更新值jiffies----接着调用update_wall_time()函数更新xtime，最后由calc_load()执行。do_timer()函数执行完毕后返回与体系结构相关的中断处理程序，继续执行后面的工作，释放xtime_lock锁，然后退出。以上的工作每1/HZ都要发生一次。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/font&gt;&lt;font face="微软雅黑"&gt;刚前边说的墙上时间就是我们常说的实际时间，指变量xtime，由结构体timespec定义(kernel/timer.c)，如下：&lt;/font&gt;&lt;/p&gt;structtimespec{&lt;br/&gt;time_t tv_sec;//秒,存放自1970年7月1日(UTC)以来经过的时间,1970年7月1日称为纪元&lt;br/&gt;long tv_nsec;//纳秒,记录自上一秒开始经过的纳秒数&lt;br/&gt;}       &lt;p&gt;&lt;font face="微软雅黑"&gt;读写这个xtime变量需要xtime_lock锁，该锁是一个顺序锁(seqlock).关于内核读写就不说了，注意适当加解锁就好。回到用户空间，从用户空间取得墙上时间的主要接口是gettimeofday(),在内核中对应系统调用为sys_gettimeofday():&lt;/font&gt;&lt;/p&gt;asmlinkage long sys_gettimeofday(struct timeval __user *tv, struct timezone __user *tz)&lt;br/&gt;{&lt;br/&gt;         if (likely(tv != NULL)) {&lt;br/&gt;                 struct timeval ktv;&lt;br/&gt;                 do_gettimeofday(&amp;amp;ktv);&lt;br/&gt;                 if (copy_to_user(tv, &amp;amp;ktv, sizeof(ktv)))&lt;br/&gt;                         return -EFAULT;&lt;br/&gt;         }&lt;br/&gt;         if (unlikely(tz != NULL)) {&lt;br/&gt;                 if (copy_to_user(tz, &amp;amp;sys_tz, sizeof(sys_tz)))&lt;br/&gt;                         return -EFAULT;&lt;br/&gt;         }&lt;br/&gt;         return 0;&lt;br/&gt;}&lt;br/&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 分析上面的函数发现，问题就集中在tv上。当tv非空，就调用do_gettimeofday(),它主要完成循环读取xtime的操作。如果tz参数为空，该函数将把系统时区(存放在sys_tz中)返回用户。如果给用户空间拷贝墙上时间或时区发生错误，该函数返回-EFAULT;如果成功，则返回0.另外，内核提供的time系统调用，几乎被gettimeofday()完全取代。C库函数提供的一些墙上时间相关的库调用如ftime和ctime。系统的settimeofday()是用来设置当前时间，它需要具有CAP_SYS_TIME权限。除了更新xtime时间以外，内核不会像用户空间程序那样频繁使用xtime。但也需要注意在文件系统的实现代码中存放访问时间戳时需要使用xtime。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 上面说完了有关硬时钟，下面开始新的话题，是关于定时器的(也称动态定时器或内核定时器)。定时器并不周期执行，它在超时后就自行销毁。定义器由定义在linux/timer.h中的time_list表示，如下：&lt;/font&gt;&lt;/p&gt;struct timer_list {&lt;br/&gt;         struct list_head entry;&lt;br/&gt;         unsigned long expires; &lt;br/&gt;         spinlock_t lock;&lt;br/&gt;         unsigned long magic; &lt;br/&gt;         void (*function)(unsigned long);&lt;br/&gt;         unsigned long data; &lt;br/&gt;         struct tvec_t_base_s *base;&lt;br/&gt;};&lt;br/&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 内核提供了一组与定时器相关的用来简化管理定时器的操作。所有这些接口都声明在文件linux/timer.h中，大多数接口在文件kernel/timer.c中获得实现。有了这些接口，我们要做的事情就很简单了:&lt;/font&gt;&lt;/p&gt;&lt;table style="margin: auto 25px; color: #000000" border="1" cellspacing="0" cellpadding="2" width="814"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td valign="top" width="812"&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;1.创建定时器：struct timer_list my_timer;&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;2.初始化定时器：init_timer(&amp;amp;my_timer);&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;3.根据需要，设置定时器了：&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; my_timer.expires = jiffies + delay;&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; my_timer.data = 0;&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; my_timer.function = my_function;&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;4.激活定时器：add_timer(&amp;amp;my_timer);&lt;/font&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 经过上面的几步，定时器就可以开始工作了。然而，一般来说，定时器都在超时后马上就会执行，但是也有可能被推迟到下一时钟节拍时才能运行，所以不能使用它来实现硬实时。如果修改定时器，使用mod_timer(&amp;amp;my_timer,jiffies+new_delay)来修改已经激活的定时器时间。它也可以操作那些已经初始化，但还没有被激活的定时器，如果定时器未被激活，mod_timer会激活它。如果第啊哟个定时器时未被激活，该函数返回0；否则返回1。但不论哪种情况，一旦从mod_timer函数返回，定时器都将被激活而且设置了新的定时值。当然你也可以在超市前删除定时器用：del_timer(&amp;amp;my_timer);另外需要注意的是在多处理器上定时器中断可能已经在其它机器上运行了，这是就需要等待可能在其它处理器上运行的定时器处理程序都退出后再删除该定时器。这是就要使用del_timer_sync()函数执行删除工作。这个函数参数和上面一个一样，只是不能在中断上下文中使用而已。定时器是独立与当前代码的，这意味着可能存在竞争条件，这个就要特别小心，从这个意义上讲后者删除比前者更加安全。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 内核在时钟中断发生后执行定时器，定时器作为软件中断在下半部上下文中执行。具体来说就是时钟中断处理程序会执行update_process_timers()函数，该函数随即调用run_local_timers()函数：&lt;/font&gt;&lt;/p&gt;void run_local_timers(void)&lt;br/&gt;{&lt;br/&gt;raise_softirq(TIMER_SOFTIRQ);&lt;br/&gt;}&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 这个函数处理软中断TIEMR_SOFTIRQ,从而在当前处理器上运行所有的超时定时器。所有定时器都以链表的形式组织起来，但如果单纯的链表结构显然影响性能，因为每次都要顺序的的查找调整，这个时候，内核定时器按它们的超时时间将他们分为5组，当定时器超时时间接近时，定时器将随组一起下移。采用这种方法可以减少搜素超时定时器所带来的负担。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;下一话题，内核代码(尤其是驱动程序)除了使用定时器或下半部机制以外还提供了许多延迟的方法来处理各种延迟请求。下面就来总结一下：&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;1.忙等待(也叫忙循环)：通常是最不理想的方法,因为处理器被白白占用旋转而无法做别的事情。该方法仅仅在想要延迟的时间是节拍的整数倍或者精确率要求不高时才可以使用。实现起来还是挺简单的，就是在循环中不断旋转直到希望的时钟节拍数耗尽。比如：&lt;/font&gt;&lt;/p&gt;unsigned long delay = jiffies+10;   //10个节拍&lt;br/&gt;while(time_before(jiffies,delay))&lt;br/&gt;cond_resched();&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 缺点很明显，更好的方法是在代码等待时，允许内核重新调度执行其他任务，如下：&lt;/font&gt;&lt;/p&gt;unsigned long delay = jiffies+10;   //10个节拍&lt;br/&gt;while(time_before(jiffies,delay))&lt;br/&gt;cond_resched();&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; cond_resched()函数将调度一个新程序投入运行，但它只有在设置完need_resched标志后才能生效。换句话说，就是系统中存在更重要的任务需要运行。再由于该方法需要调用调度程序，所以它不能在中断上下文中使用----只能在进程上下文中使用。事实上，所有延迟方法在进程上下文中使用，因为中断处理程序都应该尽可能快的执行。另外，延迟执行不管在哪种情况下都不应该在持有锁时或者禁止中断时发生。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 至于说那些需要很短暂的延迟(比时钟节拍还短)而且还要求延迟的时间很精确，这种情况多发生在和硬件同步时，也就是说需要短暂等待某个动作的完成----等待时间往往小于1ms，所以不可能使用像前面例子中那种基于jiffies的延迟方法。这时，就可以使用在linux/delay.h中定义的两个函数，它们不使用，这两个函数可以处理微秒和毫秒级别的延迟的时间，如下所示：&lt;/font&gt;&lt;/p&gt;void udelay(unsigned long usecs);&lt;br/&gt;void mdelay(unsigned long msecs);&lt;font face="微软雅黑"&gt;&lt;/font&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 前者是依靠执行次数循环来达到延迟效果的，而mdelay()函数又是通过udelay()函数实现的。因为内核知道处理器在一秒内能执行多少次循环，所以udelay()函数仅仅需要根据指定的延迟时间在1秒中占的比例，就能决定需要进行多少次循环就能达到需要的推迟时间。udelay()函数仅能在要求的延迟时间很短的情况下执行，而在高速机器中时间很长的延迟会造成溢出，经验表明，不要试图在延迟超过1ms的情况下使用这个函数。这两个函数其实和忙等待一样，如果不是非常必要，还是不要用了算了。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 前边说的有点害怕，那咋办呢？其实更理想的延迟执行方法是使用schedule_timeout()函数，该方法会让需要延迟执行的任务睡眠到指定的延迟时间耗尽后再重新运行。但该方法也不能保证睡眠时间正好等于指定的延迟时间----只能尽量是睡眠时间接近指定的延迟时间。当指定的时间到期后，内核唤醒被延迟的任务并将其重新放回运行队列，如下：&lt;/font&gt;&lt;/p&gt;set_current_state(TASK_INTERRUPTIBLE);&lt;br/&gt;schedule_timeout(s*HZ);&lt;font face="微软雅黑"&gt;&lt;/font&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 唯一的参数是延迟的相对时间，单位是jiffies，上例中将相应的任务推入可中断睡眠队列，睡眠s秒。在调用函数schedule_timeout之前，不要要将任务设置成可中断或不和中断的一种，否则任务不会休眠。这个函数需要调用调度程序，所以调用它的代码必须保证能够睡眠，简而言之，调用代码必须处于进程上下文中，并且不能持有锁。有关这个函数的实现细节，可以看下源码，还是相当简单的。接下来就是当定时器超时，process_timeout()函数被调用：&lt;/font&gt;&lt;/p&gt;void process_timeout(unsigned long data)&lt;br/&gt;{&lt;br/&gt;wake_up_process((task_t *)data);&lt;br/&gt;}&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 该函数将任务置为TASK_RUNNING状态，然后哦将其放入运行队列。当任务重新被调度时，将返回代码进入睡眠前的位置继续执行(正好在调用schedule()后)。如果任务提前被唤醒(比如收到信号)，那么定时器被销毁，process_timeout()函数返回剩余的时间。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 最后，在进程调度那一节我们说过，进程上下文的代码为了等待特定时间发生，可以将自己放入等待队列。但是，等待队列上的某个任务可能既在等待一个特定事件到来，又在等待一个特定时间到期----就看谁来得更快。这种情况下，代码可以简单的使用scedule_timeout()函数代替schedule()函数，这样一来，当希望指定时间到期后，任务都会被唤醒，当然，代码需要检查被唤醒的原因----有可能是被事件唤醒，也有可能是因为延迟的时间到期，还可能是因为接收到了信号----然后执行相应的操作。&lt;/font&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/hanyan225/aggbug/2117158.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/hanyan225/archive/2011/07/26/2117158.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/hanyan225/archive/2011/07/23/2114987.html</id><title type="text">linux内核分析笔记----内核同步</title><summary type="text">内核同步讲的比较多了，我也就不太啰嗦了，先说一些概念，然后就是方法。 同步就是避免并发和防止竞争条件。有关临界区的例子我就不举了，随便一本操作系统的书上都有。锁机制的提出也算解决了一些问题，我们待会再说，现在只要知道锁的使用是自愿的，非强制的。linux自身也提供了几种不同的锁机制，区别主要在于当锁被争用时，有些会简单地执行等待，而有些锁会使当前任务睡眠直到锁可用为止，这个后面细说。真正的困难在...</summary><published>2011-07-23T09:38:00Z</published><updated>2011-07-23T09:38:00Z</updated><author><name>☆&amp;amp;寒 烟☆</name><uri>http://www.cnblogs.com/hanyan225/</uri></author><link rel="alternate" href="http://www.cnblogs.com/hanyan225/archive/2011/07/23/2114987.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/hanyan225/archive/2011/07/23/2114987.html"/><content type="html">&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 内核同步讲的比较多了，我也就不太啰嗦了，先说一些概念，然后就是方法。&lt;/font&gt;&lt;/p&gt; &lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 同步就是避免并发和防止竞争条件。有关临界区的例子我就不举了，随便一本操作系统的书上都有。锁机制的提出也算解决了一些问题，我们待会再说，现在只要知道锁的使用是自愿的，非强制的。linux自身也提供了几种不同的锁机制，区别主要在于当锁被争用时，有些会简单地执行等待，而有些锁会使当前任务睡眠直到锁可用为止，这个后面细说。真正的困难在于发现并辨认出真正需要共享的数据和相应的共享区。先来说一些感性的话：大多数内核数据结构都需要加锁，如果有其他执行线程可以访问这些数据，那么就给这些数据加上某种形式的锁。如果任何其他什么东西能看到它，那么就要锁住它。简而言之，几乎访问所有的内核全局变量和共享数据都需要某种形式的同步方法。有关加锁的细节，在嵌套的锁时，要保证以相同的顺序获取锁，不要重复请求同一个锁，释放时，最好还是以获得锁的相反顺序来释放锁。加锁的粒度用来描述加锁保护的数据规模。接下来就开始讨论真正的同步方法：&lt;/font&gt;&lt;/p&gt; &lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1.原子操作。就是指执行过程不被打断的操作，是 不能够被分割的指令。关于这个linux内核提供了两组原子操作接口：原子整数操作和原子位操作。好，先来说说这个原子整数操作。针对整数的原子操作只能对atomic_t类型的数据进行处理。需要说明的是，尽管linux支持的所有机器上的整形数据都是32位的，但是使用atomic_t的代码只能将该类型的数据当作24位来用，原因就不说了。使用原子操作需要的声明在asm/atomic.h中。原子整数操作列表如下;&lt;/font&gt;&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="1" border="0" alt="1" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201107/201107231738099082.jpg" width="561" height="204"&gt; &lt;/p&gt; &lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 在编写代码的时候，能使用原子操作的时候，就尽量不要使用复杂的加锁机制，因为大多数或者100%情况下，原子操作比更复杂的同步方法相比较而言，给系统带来的开销小，对高速缓存行(cache-line)的影响也很小。&lt;/font&gt;&lt;/p&gt; &lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 对应于原子整数操作，还有一种原子操作就是原子位操作，它们是与体系结构相关的操作，定义在文件&amp;lt;asm/bitops.h&amp;gt;,它是对普通的内存地址进行操作的。它的参数是一个指针和一个位号，第0位是给定地址的最低有效位。这里没有想atomic_t一样的数据结构，只要指针指向任何希望的数据，就可以进行操作。原子位操作函数列表如下：&lt;/font&gt;&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="2" border="0" alt="2" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201107/201107231738104381.jpg" width="558" height="170"&gt; &lt;/p&gt; &lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 同时，内核还提供了一组与上述操作对应的非原子位函数，操作完全相同，不同在于不保证原子性且名字前缀多了两个下划线，例如与test_bit()对应的非原子形式是__test_bit().如果不需要原子操作，这时这些函数的执行效率可能更高。内核还提供了两个函数用来从指定的地址开始搜索第一个被设置(或未被设置)的位：&lt;/font&gt;&lt;/p&gt;int find_first_bit(unsigned long *addr,unsigned int size);&lt;br/&gt;int find_first_zero_bit(unsigned long *addr,unsigned int size);&lt;font face="微软雅黑"&gt;&lt;/font&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 其中，第一个参数是一个指针，第二个参数是要搜索的总位数。返回值分别是第一个被设置的(或没被设置的)位的位号。如果要搜素的范围仅限于一个字，使用_ffs()和__ffz()这两个函数更好，它们只需要给定一个要搜索的地址做参数。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2.自旋锁。临界区远没有我们想象那样的那样简单，有时可能跨越多个函数。自旋锁是linux内核中最常见的锁。如果一个执行线程试图获得一个被争用的(已经被持有)的自旋锁，那么这个线程就会一直进行忙循环----旋转----等待锁重新可用。同一个锁可以用在多个位置，例如，对于给定数据的访问都可以得到保护和同步。上述的自旋过程是很费时间的，所以自旋锁不应该被长时间持有。我们前边所过也可以让请求线程休眠，CPU可以执行其他代码，直到锁可用时在唤醒它，但自旋锁由于忙等待，它是占用CPU的。上面的休眠过程也会带来上下文切换带来的开销，所以持有自旋锁的时间最好小于完成两次上下文切换的时间。自旋锁的实现和体系结构密切相关，代码往往用汇编实现，这些与体系结构相关的部分定义在&amp;lt;asm/spinlock.h&amp;gt;,实际需要用到的接口定义在文件&amp;lt;linux/spinlock.h&amp;gt;中。自旋锁操作列表如下:&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="3" border="0" alt="3" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201107/201107231738102953.jpg" width="560" height="176"&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 自旋锁仅仅被当作一个设置内核抢占机制时候被启用的开关，如果禁止内核抢占，那么在编译时自旋锁会被完全剔除出内核。另外就是linux内核实现的自旋锁是不可递归的。自旋锁可是用在中断处理程序中(使用信号量，会导致睡眠)。在中断处理程序中使用自旋锁，一定要在获取锁之前禁止本地中断(当前处理器上的中断请求)。否则，中断处理程序会打断正持有锁的内核代码，有可能会试图去争用这个已经被持有的自旋锁。这样一来，中断处理程序就会自旋。但锁的持有者在这个中断处理程序执行完毕前不可能运行，这就是双重请求死锁。注意，需要关闭的只是当前处理器上中断，如果中断发生在不同的处理器上，即使中断处理程序在同一个锁上自旋，也不会妨碍锁的持有者(在不同处理器上)最终释放锁。最后两个函数spin_lock_bh()用于获取指定锁，同时它会禁止所有下半部的执行。相应的spin_unlock_bh()函数执行相反的操作。最后，提醒要注意自旋锁和下半部的关系。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3.读--写自旋锁。有时，锁的用途是可以明确分为读取和写入的。当对某个数据结构的操作可以被划分为读/写两种类别时，就可以使用这里说的读---写自旋锁。这种机制为读和写分别提供了不同的锁。一个或多个读任务可以并发的持有读者锁，而写锁只能被一个写任务持有，而且此时不能有并发的读。操作函数列表如下：&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="5" border="0" alt="5" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201107/201107231738101525.jpg" width="568" height="248"&gt;&amp;nbsp;&amp;nbsp; &lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 事实上，即使一个线程递归地获得同一读锁也是安全的。还是那句话，使用之前要能明确的分清读和写。如果在中断处理程序中只有读操作而没有写操作，那么，就可以混合使用“中断禁止”锁，使用read_lock()而不是read_lock_irqsave()对读进行保护。不过，你还是需要用write_lock_irqsave()禁止有写操作的中断，否则，中断里读操作就有可能锁死在写锁上(假如读者正在进行操作，包含写操作的中断发生了，由于读锁还没有全部被释放，所以写操作会自旋，而读操作只能在包含写操作的中断返回后才能继续，释放读锁，这时死锁就发生了)。最后需要说明的是，这种机制偏向与读锁：当读锁被持有时，写操作为了互斥访问只能等待，但是，读者却可以继续成功地占用锁。而自旋等待的写者在所有读锁释放锁之前是无法获得锁的。所以大量的读者会使挂起的写者处于饥饿状态。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 4.信号量。它是一种睡眠锁，实现和体系结构相关的，定义在&amp;lt;asm/semaphore.h&amp;gt;。如果有一个任务试图获得一个已经被占用的信号量时，信号量会将其推进一个等待队列，然后让其睡眠。这时的处理器会重获自由，从而去执行其他代码。当持有信号量的进程将信号量释放后，处于等待队列中的那个任务将被唤醒，并获得该信号量。如果需要在自旋锁和信号量中做出选择，应该根据锁被持有的时间长短做判断，如果加锁时间不长并且代码不会休眠，利用自旋锁是最佳选择。相反，如果加锁时间可能很长或者代码在持有锁有可能睡眠，那么最好使用信号量来完成加锁功能。信号量一个有用特性就是它可以同时允许任意数量的锁持有者，而自旋锁在一个时刻最多允许一个任务持有它。信号量同时允许的持有者数量可以在声明信号量时指定，当为1时，成为互斥信号量，否则成为计数信号量。操作函数列表如下：&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="4" border="0" alt="4" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201107/201107231738104872.jpg" width="566" height="180"&gt;  &lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 信号量支持pv操作，我就不说了。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 5.读-写信号量。这个和信号量的关系是和自旋锁与读写自旋锁是一样的关系，由rw_semaphore结构表示的。定义在linux/rwsem.h中。所有的读写信号量都是互斥信号量。操作函数有：&lt;/font&gt;&lt;/p&gt;DECLARE_RWSEM(name)//声明名为name的读写信号量，并初始化它。&lt;br/&gt;void init_rwsem(struct rw_semaphore *sem);//对读写信号量sem进行初始化。&lt;br/&gt;void down_read(struct rw_semaphore *sem);//读者用来获取sem，若没获得时，则调用者睡眠等待。&lt;br/&gt;void up_read(struct rw_semaphore *sem);//读者释放sem。&lt;br/&gt;int down_read_trylock(struct rw_semaphore *sem); //读者尝试获取sem，如果获得返回1，如果没有获得返回0。可在中断上下文使用。&lt;br/&gt;void down_write(struct rw_semaphore *sem);//写者用来获取sem，若没获得时，则调用者睡眠等待。&lt;br/&gt;int down_write_trylock(struct rw_semaphore *sem);//写者尝试获取sem，如果获得返回1，如果没有获得返回0。可在中断上下文使用&lt;br/&gt;void up_write(struct rw_semaphore *sem);//写者释放sem。&lt;br/&gt;void downgrade_write(struct rw_semaphore *sem);//把写者降级为读者。&lt;br/&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 其实上面的就是分了两类：信号量和自旋锁，使用时选择的比较如下：&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="6" border="0" alt="6" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201107/201107231738101492.jpg" width="553" height="117"&gt;  &lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 6.完成变量。如果内核中一个任务需要发出信号通知另外一个任务发生了某个特定事件，利用完成变量(complete variables)是使两个任务得以同步的简单方法。如果任务要执行一些工作时，另一个任务就会在完成量上等待，当这个任务完成工作后，会使用完成变量去唤醒在等待的任务，就像信号量一样，它们两者思想是一样的， 它仅仅提供了代替信号量的一个简单地解决方法。它有结构completion表示，定义在linux/completion.h中。操作接口列表如下：&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="7" border="0" alt="7" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201107/201107231738109299.jpg" width="557" height="104"&gt;  &lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 完成变量的通常用法是将完成变量作为数据结构中的一项动态创建，而完成变量的初始化工作的内核代码将调用wait_for_completion()进行等待。初始化完成后，初始化函数调用completion()唤醒在等待的内核任务。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 7.BKL(大内核锁)。是一个全局自旋锁，年代有些久远了，使用它主要是为了方便实现从linux最初的SMP过度到细粒度加锁机制，有趣的特性如下：&lt;/font&gt;&lt;/p&gt;&lt;table style="margin: auto 31px" border="1" cellspacing="0" cellpadding="2" width="811"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td valign="top" width="809"&gt;&lt;font face="微软雅黑"&gt;1.持有BKL的任务可以睡眠。因为当任务无法调度时，所加锁会自动被丢弃，所加锁会自动被丢弃；当任务被调度时，锁又会被重新获得。当然，这不是&lt;br&gt;&amp;nbsp; 说，当任务持有BKL时，睡眠是安全的，仅仅是这样做，因为睡眠不会造成任务死锁。&lt;br&gt;2.BKL是一种递归锁，一个进程可以多次请求一个锁，并不会像自旋锁那样产生死锁现象。&lt;br&gt;3.BKL可以在进程上下文中。&lt;br&gt;4.BKL是有害的。&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 不要奇怪，这种机制在内核中已经几乎不存在了，也不鼓励使用。这里提到这样的思想和接口，是为了万一遇到呢？操作接口(linux/smp_lock.h)列表如下:&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="8" border="0" alt="8" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201107/201107231738112331.jpg" width="558" height="92"&gt;  &lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; BKL在被持有的时候同样会禁止内核抢占。多数情况下，BKL更像是保护代码而不是保护数据。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 8.Seq锁。这种锁提供了一种简单机制，用于读写共享数据。实现这种锁主要依靠一个序列计数器。当数据被进行写入操作时，会得到一个锁，并且序列值会增加。在读取数据之前和之后，序列号都被读取。如果读取的序列号值相同，说明在读操作进行的过程中没有被写操作打断过。此外，如果读取的值是偶数，那么就说明写操作没有发生(要明白因为锁的初始值是0，所以写锁会使值为奇数，释放的时候变成偶数)。操作接口如下：&lt;/font&gt;&lt;/p&gt;seqlock_t mr_seq_lock = SEQLOCK_UNLOCKED;    //定义顺序锁&lt;br/&gt;write_seqlock(&amp;amp;mr_seq_lock);     //写操作代码块…&lt;br/&gt;write_sequnlock(&amp;amp;mr_seq_lock);&lt;br/&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 这和普通自旋锁类似，不同的情况发生在读时，写自旋锁有很大不同：&lt;/font&gt;&lt;/p&gt;unsigned long seq;&lt;br/&gt;do{&lt;br/&gt;seq = read_seqbegin(&amp;amp;my_seq_lock);&lt;br/&gt;}while(read_seqretry(&amp;amp;my_seq_lock,seq));&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 在多个读者和少数读者共享一把锁的时候，seq锁有助于提供一种非常轻量级和具有可扩展性的外观。但是seq锁对写者更有利。只有没有其他写者，写锁总是能够被成功获得。读者不会影响写锁，这点和读写自旋锁及信号量是一样的。另外，挂起的写者会不断地使得读操作循环，知道不再有任何写者锁持有锁为止。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 9.禁止抢占：实际情况是这样的，有时我们不需要锁，但同时又不希望进程抢占(内核抢占)的修改某些数据，这时就要关闭内核抢占。可以通过preempt_disable()禁止内核抢占。这时一个可以嵌套调用的函数，可以使用任意次。每次调用都必须有一个相应的preempt_enable()调用，当最后一次preempt_enable()被调用时，内核抢占才重新启用。内核抢占相关操作如下：&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="9" border="0" alt="9" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201107/201107231738115362.jpg" width="556" height="85"&gt;  &lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 抢占技术存放着被持有锁的数量和preempt_disable()的调用次数，当计数为0时，那么内核可以进行抢占。否则不能。为了用更简洁的方法解决每个处理器上的数据访问问题，你可以通过get_cpu()获得处理器编号(假定用这种编号来对每个处理器的数据进行索引的)。这个函数在返回当前处理器号前首先会关闭内核抢占：&lt;/font&gt;&lt;/p&gt;int cpu = get_cpu();&lt;br/&gt;...对每个处理器的数据进行操作&lt;br/&gt;put_cpu();&lt;p&gt;&lt;font face="微软雅黑"&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/font&gt;10.屏障。当处理多处理器之间或硬件设备之间的同步问题时，有时需要在程序代码中以指定的顺序发出读内存(写入)和写内存(存储)指令。在和硬件交互时，时常需要确保一个给定的读操作发生在其他读或写操作之前。另外，在多处理器上，可能需要按写数据的顺序读数据(通常确保后以同样的顺序进行读取)。但是编译器和处理器为了提高效率，可能对读和写重新排序，这样无疑是问题复杂化了。幸好，所有可能重新排序和写的处理器提供了机器指令来确保顺序要求。同样也可以指示编译器不要对给定点周围的指令序列进行重新排序。这些确保顺序的指令叫做屏障(barriers);这样的内存和编译屏障方法列表如下：&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="10" border="0" alt="10" src="http://images.cnblogs.com/cnblogs_com/hanyan225/201107/20110723173811346.jpg" width="554" height="180"&gt; &lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&lt;font face="微软雅黑"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/font&gt;最后，说明的是，对于不同的体系结构，屏障的实际效果差别很大。但应该在最坏的情况下使用恰当的内存屏障，这样代码才能在编译时执行针对体系结构的优化。&lt;/font&gt;&lt;/p&gt;&lt;p&gt;&lt;font face="微软雅黑"&gt;&lt;/font&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/hanyan225/aggbug/2114987.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/hanyan225/archive/2011/07/23/2114987.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry></feed>
