﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-sin的博客-随笔分类-Linux编程</title><link>http://www.cppblog.com/sixinquan/category/14267.html</link><description>时间悄悄地流过，今天你做了什么</description><language>zh-cn</language><lastBuildDate>Sun, 03 Nov 2013 05:45:07 GMT</lastBuildDate><pubDate>Sun, 03 Nov 2013 05:45:07 GMT</pubDate><ttl>60</ttl><item><title>Linux自旋锁和互斥锁的实现</title><link>http://www.cppblog.com/sixinquan/archive/2013/10/29/203981.html</link><dc:creator>sin</dc:creator><author>sin</author><pubDate>Tue, 29 Oct 2013 14:40:00 GMT</pubDate><guid>http://www.cppblog.com/sixinquan/archive/2013/10/29/203981.html</guid><wfw:comment>http://www.cppblog.com/sixinquan/comments/203981.html</wfw:comment><comments>http://www.cppblog.com/sixinquan/archive/2013/10/29/203981.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/sixinquan/comments/commentRss/203981.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/sixinquan/services/trackbacks/203981.html</trackback:ping><description><![CDATA[<div><p>自旋锁（spin  lock）应用在多处理器环境中。如果内核控制路径发现自旋锁&#8220;开着&#8221;，就获取锁并继续自己的执行。相反，如果内核控制路径发现锁由运行在另一个CPU上的内核控制路径&#8220;锁 着&#8221;，就在周围&#8220;旋转&#8221;，反复执行一条紧凑的循环指令，直到锁被释放。自旋锁的循环指令表示&#8220;忙等&#8221;。即使等待的内核控制路径无事可做（除了浪费时间），它也在CPU上保持运行。不过，自旋锁通常非常方便，因为很多内核资源只锁1毫秒的时间片段；所以说，等待自旋锁的释放不会消耗太多CPU的时间。<br /></p><p>一般来说，由自旋锁所保护的每个临界区都是禁止内核抢占的。在单处理器系统上，这种锁本身并不起锁的作用，自旋锁技术仅仅是用来禁止或启用内核抢 占。请注意，在自旋锁忙等期间，因为并没有进入临界区，所以内核抢占还是有效的，因此，等待自旋锁释放的进程有可能被更高优先级的所取代。这种设计是合理 的，因为不能因为占用CPU太久而使系统死锁。</p></div><br />互斥锁（mutex  lock）的实现，实际上就是一把锁维护了一个等待队列和一个引用计数器，当获取锁之前，先对引用计数器减1操作，如果为非负，则可以获取锁进入临界区。否则将该任务设为不可中断状态（uninterruptible），挂在该等待对列上。获取锁的任务从临界区退出后，计数器加1操作，唤醒（wake up）等待队列上的被挂起进程。<img src ="http://www.cppblog.com/sixinquan/aggbug/203981.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/sixinquan/" target="_blank">sin</a> 2013-10-29 22:40 <a href="http://www.cppblog.com/sixinquan/archive/2013/10/29/203981.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux内存管理之三 页的分配和释放</title><link>http://www.cppblog.com/sixinquan/archive/2012/07/29/185545.html</link><dc:creator>sin</dc:creator><author>sin</author><pubDate>Sun, 29 Jul 2012 06:44:00 GMT</pubDate><guid>http://www.cppblog.com/sixinquan/archive/2012/07/29/185545.html</guid><wfw:comment>http://www.cppblog.com/sixinquan/comments/185545.html</wfw:comment><comments>http://www.cppblog.com/sixinquan/archive/2012/07/29/185545.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/sixinquan/comments/commentRss/185545.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/sixinquan/services/trackbacks/185545.html</trackback:ping><description><![CDATA[Linux对内存区内的页框的分配和释放，采用的算法是伙伴系统。<br /><img alt="" src="http://www.cppblog.com/images/cppblog_com/sixinquan/伙伴系统.png" height="233" width="562" /><br /><br />如上图，Linux分配页时，只能分配2^n个页。内核维护MAX_ORDER个链表，每个链表记录着连续的空闲页。第一个链表中的每一项为1个空闲页，第二个链表中的每一项为2个空闲页，第三个链表中的每一项为4个空闲页。。。，依次类推。分配页时，从对应的链表上摘除空闲页；释放页时，将对应的页归还到对应的链表。分配释放页的过程中，可能伴随着内存页的拆分和合并。比如要分配16个空闲页，但是对应的链表为空，这时如果32个空闲页对应的链表如果不为空，则从链表中摘除32个空闲页，并将其一分为二，其中16个页用于内存分配，剩余16个页则插入到16个页对应的链表中。<br /><br />尽管页的分配算法是简单的，但是实际过程却非常复杂。这是因为分配页式必须考虑一下几点：<br />1 备用内存区。当从一个内存区无法得到内存时，系统会从同一内存节点的其它内存区或者从另一个内存节点中的内存区中获取内存。<br />2 页的换入和换出，在没有足够多的空闲页时，可能需要将页换出以获取空闲内存。<br />3 页的回收，对一些缓冲区的不再使用的页进行回收，以获取空闲页。<br />4 系统中必须保持一定&#8220;水位&#8221;的空闲页，以应付对内存的紧急分配。如果系统将页分配完，在急需内存时，再进行页的回收或换出，无疑是非常糟糕的设计。系统中必须保持一定量的内存页。<br />5 不同的分配策略。不同的分配策略可能采用的方法有区别。<br />总之，页的分配和释放需要考虑许多因素，尽量满足内存分配的同时，要保证系统的稳定性和健壮性。<img src ="http://www.cppblog.com/sixinquan/aggbug/185545.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/sixinquan/" target="_blank">sin</a> 2012-07-29 14:44 <a href="http://www.cppblog.com/sixinquan/archive/2012/07/29/185545.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux内存管理之二 内存节点和内存分区</title><link>http://www.cppblog.com/sixinquan/archive/2012/07/29/185090.html</link><dc:creator>sin</dc:creator><author>sin</author><pubDate>Sun, 29 Jul 2012 01:38:00 GMT</pubDate><guid>http://www.cppblog.com/sixinquan/archive/2012/07/29/185090.html</guid><wfw:comment>http://www.cppblog.com/sixinquan/comments/185090.html</wfw:comment><comments>http://www.cppblog.com/sixinquan/archive/2012/07/29/185090.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/sixinquan/comments/commentRss/185090.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/sixinquan/services/trackbacks/185090.html</trackback:ping><description><![CDATA[UMA和NUMA：<br />UMA(Uniform Memory Access)，即一致性内存访问。这种情况下，CPU访问内存的任何位置，代价都是一样的。<br />NUMA)(Non Uniform Memory Access)，即非一致性内存访问。这种情况下，CPU访问不同位置的内存，代价是不一样的。在多CPU情况下，对每个CPU来说有本地内存和远端内存，访问本地内存的代价比访问远端内存的代价小。确保CPU访问内存代价最小，是非常重要的一点。<br /><br />Linux支持多种硬件体系结构，因此Linux必须采用通用的方法来描述内存，以方便对内存进行管理。为此，Linux有了内存节点、内存区、页框的概念，这些概念也是一目了然的。<br />内存节点：主要依据CPU访问代价的不同而划分。多CPU下环境下，本地内存和远端内存就是不同的节点。即使在单CPU环境下，访问所有内存的代价都是一样的，Linux内核依然存在内存节点的概念，只不过只有一个内存节点而已。内核以struct&nbsp; pg_data_t来描述内存分区。<br />内存分区：Linux对内存节点再进行划分，分为不同的分区。内核以struct zone来描述内存分区。通常一个节点分为DMA、Normal和High Memory内存区，具体下面再介绍。<br />页框：Linux采用页式内存管理，页是物理内存管理的基本单位，每个内存分区又由大量的页框组成。内核以struct page来描述页框。页框有很多属性，这些属性描述了这个页框的状态、用途等，例如是否被分配。<br /><img alt="" src="http://www.cppblog.com/images/cppblog_com/sixinquan/node.JPG" height="502" width="857" /><br /><br />上图中的zone_mem_map是一个页框的数组，它记录了一个内存分区的所有页框的使用情况。<br /><div><br />DMA内存区：即直接内存访问分区，通常为物理内存的起始16M。主要是供一些外设使用，外设和内存直接访问数据访问，而无需系统CPU的参与。<br />Normal内存区：从16M到896M内存区。<br />HighMemory内存区：896M以后的内存区。<br /><br />为什么高端内存的边界是896M？这是因为，32位Linux虚拟内存空间为0-4G，其中0-3G用于用户态，3G-4G用于内核态。这意味着内核只有1G的虚拟地址空间，如果物理内存超过1G，内核就无法映射了。Linux采取的策略是，内核地址空间的前896M采用固定映射，映射方法是：虚拟地址-3G = 物理地址，只能映射到物理地址的前896M。也就是说内核虚拟地址空间的3G到3G+896M这部分，页表的映射是固定的，系统初始化时就建立起来。而虚拟地址空间的最后128M，也就是3G+896M到4G部分采用动态映射，也就是说页表映射的物理地址可变的。在系统运行过程中，通过更新页表，就可以映射到不同的物理地址，当然也包括高端物理内存。<br /><br />这主要解决了两个问题：第一，这可以使内核地址空间映射到高端物理内存；第二，虚拟地址空间的3G+896M到4G部分，连续的虚拟地址空间可以映射到非连续的物理内存，只要通过更新页表就可以做到，这和用户态的虚拟内存映射采用了同样这种方法。这在没有大段连续的空闲物理地址时，是非常重要的。<br /><br />备用内存区：<br />在一个内存区分配页时，如果这个内存区没有满足条件的内存页，则需要从其它内存区或从其它内存节点分配。Linux为每个内存区都建立了备用内存区列表，当前内存区没有满足条件的内存时，就从备用内存区分配。比如，系统中有4个内存节点A,B,C,D，每个内存节点又分为DMA、Normal、HighMemory内存区。对节点B来说，内存区分配列表可能是B(HighMemory)、B(Normal)、B(DMA)、<div>A(HighMemory)、A(Normal)、A(DMA)、</div><div>C(HighMemory)、C(Normal)、C(DMA)、</div><div>D(HighMemory)、D(Normal)、D(DMA)。<br />分配内存时，优先从本地内存节点分配，再从其它内存节点分配。对一个内存节点，优先从HighMemory分配，再从Normal或DMA分配。</div></div><img src ="http://www.cppblog.com/sixinquan/aggbug/185090.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/sixinquan/" target="_blank">sin</a> 2012-07-29 09:38 <a href="http://www.cppblog.com/sixinquan/archive/2012/07/29/185090.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux内存管理之一  分段与分页</title><link>http://www.cppblog.com/sixinquan/archive/2012/07/19/184234.html</link><dc:creator>sin</dc:creator><author>sin</author><pubDate>Thu, 19 Jul 2012 13:22:00 GMT</pubDate><guid>http://www.cppblog.com/sixinquan/archive/2012/07/19/184234.html</guid><wfw:comment>http://www.cppblog.com/sixinquan/comments/184234.html</wfw:comment><comments>http://www.cppblog.com/sixinquan/archive/2012/07/19/184234.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/sixinquan/comments/commentRss/184234.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/sixinquan/services/trackbacks/184234.html</trackback:ping><description><![CDATA[现代操作系统的内存管理机制有两种：段式管理和页式管理。<br /><br />段式内存管理，就是将内存分成段，每个段的起始地址就是段基地址。地址映射的时候，由逻辑地址加上段基地址而得到物理地址。纯粹的段式内存管理的缺点很明显，就是灵活性和效率比较差。首先是段的长度是可变的，这给内存的换入换出带来诸多不便，如何选择一个段的长度是一个棘手的问题；其次进程在运行过程中，可能会扩充地址空间，这就要增加段，从而造成进程的地址空间由很多小段构成，在进程运行过程中，访问不同的段时，就需要频繁切换段基地址；再一点，段式内存管理如果有太多的小段，在释放段的时候，会造成外部碎片。<br /><br />页式内存管理，内存分成固定长度的一个个页片。地址映射的时候，需要先建立页表，页表中的每一项都记录了这个页的基地址。通过页表，由逻辑地址的高位部分先找到逻辑地址对应的页基地址，再由页基地址偏移一定长度就得到最后的物理地址，偏移的长度由逻辑地址的低位部分决定。一般情况下，这个过程都可以由硬件完成，所以效率还是比较高的。页式内存管理的优点就是比较灵活，内存管理以较小的页为单位，方便内存换入换出和扩充地址空间。<br /><br />严格说Linux采用段页式内存管理，也就是既分段，又分页。地址映射的时候，先确定对应的段，确定段基地址；段内分页，再找到对应的页表项，确定页基地址；再由逻辑地址低位确定的页偏移量，就能找到最终的物理地址。但是，实际上Linux采用的是页式内存管理。原因是Linux中的段基地址都是0，相当于所有的段都是相同的。这样做的原因是某些体系结构的硬件限制，比如Intel的i386。作为软件的操作系统，必须要符合硬件体系。虽然所有段基地址都是0，但是段的概念在Linux内核中是确实存在的。比如常见的内核代码段、内核数据段、用户态代码段、用户态数据段等。除了符合硬件要求外，段也是有实际意义的。<br /><br />x86硬件分段单元：<br /><img alt="" src="http://www.cppblog.com/images/cppblog_com/sixinquan/分段单元.jpg" height="140" width="311" /><br /><div>逻辑地址分为两部分组成：段标识符和指定段内相对地址的偏移量。<br /><div>段描述符：用来存放段起始地址，段大小，存储权限等。<br />段描述符表：存放段描述的表项。<br />段寄存器：存放段标识符。6个段寄存器称为cs(代码段寄存器)，ss(栈段寄存器)，ds(数据段寄存器)，es，fs 和gs。<br />段基地址寄存器：指向段描述符表地址。</div></div><br />Linux分段机制：<br />Linux对分段使用非常有限。作为一个跨硬件体系的操作系统，要支持多种硬件体系，而一些硬件体系结构式不支持分段的，Linux把所有段起始地址都设为0。<br />Linux采用4个段进行寻址，用户态代码段，用户态数据段，内核态代码段，内核态数据段。<br /><br /><br />x86分页单元：<br /><img alt="" src="http://www.cppblog.com/images/cppblog_com/sixinquan/分页单元.jpg" height="128" width="377" /><br />x86采用两级页表。<br />第一级为页目录表，存储在一个4K字节的页中，每个表项包含了一个页表的物理地址。线性地址最高的10位(22-31)用来产生第一级表索引，由该索引得到的表项中的内容定位了二级表中的一个表的地址，即下级页表所在的内存块号。<br />第二级为页表，存储在一个4K字节页中，每个表项包含了一个页的物理地址。线性地址的中间10位(12-21)位进行索引，定位页表表项，获得页的物理地址。页物理地址的高20位与线性地址的低12位形成最后的物理地址。<br /><div>分页机制由CR0寄存器中的PG位启用。如PG=1，启用分页机制，把线性地址转换为物理地址。如PG=0，禁用分页机制，直接段机制产生的线性地址当作物理地址使用。<br />页目录表的物理地址存放在CR3寄存器。每个进程有它自己的页全局目录和自己的页表集。当发生进程切换时，Linux把CR3控制寄存器的内容保存在前一个执行进程的描述符中，然后把下一个要执行进程的描述符的值装入CR3寄存器中。这确保了当新进程重新开始在CPU上执行时，分页单元指向一组正确的页表。</div><br />Linux分页机制：<br />作为一个通用的操作系统，Linux需要兼容各种硬件体系，包括不同位数的CPU。对64位的CPU来说，两级页表仍然太少，一个页表会太大，这会占用太多宝贵的物理内存。Linux采用了通用的四级页表。实际采用几级页表则具体受硬件的限制。<br /><img alt="" src="http://www.cppblog.com/images/cppblog_com/sixinquan/linux4级页表.gif" height="332" width="581" /><br />四种页表分别称为： 页全局目录、页上级目录、页中间目录、页表。对于32位x86系统，两级页表已经足够了。Linux通过使&#8220;页上级目录&#8221;位和&#8220;页中间目录&#8221;位全为0，彻底取消了页上级目 录和页中间目录字段。不过，页上级目录和页中间目录在指针序列中的位置被保留，以便同样的代码在32位系统和64位系统下都能使用。<img src ="http://www.cppblog.com/sixinquan/aggbug/184234.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/sixinquan/" target="_blank">sin</a> 2012-07-19 21:22 <a href="http://www.cppblog.com/sixinquan/archive/2012/07/19/184234.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>