??xml version="1.0" encoding="utf-8" standalone="yes"?>手机看片久久高清国产日韩,久久无码专区国产精品发布 ,久久青青国产http://www.shnenglu.com/mydriverc/category/4812.html如果想飞得高Q就该把地^U忘?/description>zh-cnMon, 19 May 2008 13:37:45 GMTMon, 19 May 2008 13:37:45 GMT6080x86保护模式之实模式与保护模式切换实?/title><link>http://www.shnenglu.com/mydriverc/articles/30720.html</link><dc:creator>旅?/dc:creator><author>旅?/author><pubDate>Thu, 23 Aug 2007 16:55:00 GMT</pubDate><guid>http://www.shnenglu.com/mydriverc/articles/30720.html</guid><wfw:comment>http://www.shnenglu.com/mydriverc/comments/30720.html</wfw:comment><comments>http://www.shnenglu.com/mydriverc/articles/30720.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.shnenglu.com/mydriverc/comments/commentRss/30720.html</wfw:commentRss><trackback:ping>http://www.shnenglu.com/mydriverc/services/trackbacks/30720.html</trackback:ping><description><![CDATA[Error convertoring HTML to XHTML: System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown. at System.String.GetStringForStringBuilder(String value, Int32 startIndex, Int32 length, Int32 capacity) at System.Text.StringBuilder.GetNewString(String currentString, Int32 requiredLength) at System.Text.StringBuilder.Append(String value) at System.IO.StringWriter.Write(String value) at System.Xml.XmlTextWriter.WriteCData(String text) at System.Xml.XmlWriter.WriteNode(XmlReader reader, Boolean defattr) at FreeTextBoxControls.Support.Formatter.HtmlToXhtml(String input)<img src ="http://www.shnenglu.com/mydriverc/aggbug/30720.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.shnenglu.com/mydriverc/" target="_blank">旅?/a> 2007-08-24 00:55 <a href="http://www.shnenglu.com/mydriverc/articles/30720.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>实模式与保护模式http://www.shnenglu.com/mydriverc/articles/30719.html旅?/dc:creator>旅?/author>Thu, 23 Aug 2007 16:52:00 GMThttp://www.shnenglu.com/mydriverc/articles/30719.htmlhttp://www.shnenglu.com/mydriverc/comments/30719.htmlhttp://www.shnenglu.com/mydriverc/articles/30719.html#Feedback0http://www.shnenglu.com/mydriverc/comments/commentRss/30719.htmlhttp://www.shnenglu.com/mydriverc/services/trackbacks/30719.html

实模式: Q即实地址讉K模式Q它?/span> Intel公司80286及以后的x86(80386,80486?/span>80586{?/span>)兼容处理器(CPUQ的一U操作模式。实模式被特D定义ؓ20位地址内存可访问空间上Q这意味着它的定w?/span>2?/span>20ơ幂Q?/span>1MQ的可访问内存空_物理内存?/span>BIOS-ROMQ,软g可通过q些地址直接讉KBIOSE序和外围硬件。实模式下处理器没有gU的内存保护概念和多道Q务的工作模式。但是ؓ了向下兼容,所?/span>80286及以后的x86pd兼容处理器仍然是开机启动时工作在实模式下?/span>80186和早期的处理器仅有一U操作模式,是后来我们所定义的实模式。实模式虽然能访问到1M的地址I间Q但是由?/span>BIOS的映作用(?/span>BIOS占用了部分空间地址资源Q,所以真正能使用的物理内存空_内存条)Q也是?/span>640k?/span>924k之间?/span>1M 地址I间l成是由 16位的D地址?/span>16位的D内偏移地址l成的。用公式表示为:物理地址=左移4位的D地址+偏移地址?/span>

 

286处理器体pȝ构引入了地址保护模式的概念,处理器能够对内存及一些其他外围设备做gU的保护讄Q保护设|实质上是屏蔽一些地址的访问)。用这些新的特性,然而必不可一些额外的?/span>80186及以前处理器没有的操作规E。自从最初的x86微处理器规格以后Q它对程序开发完全向下兼容,80286芯片被制作成启动时承了以前版本芯片的特性,工作在实模式下,在这U模式下实际上是关闭了新的保护功能特性,因此能以往的Y件l工作在新的芯片下。直C天,甚至最新的x86处理器都是在计算机加电启动时都是工作在实模式下,它能q行Z前处理器芯片写的E序.

 

DOS操作pȝQ例?/font> MS-DOS,DR-DOSQ工作在实模式下Q微?/span>Windows早期的版本(它本质上是运行在DOS上的囑Ş用户界面应用E序Q实际上本nq不是一个操作系l)也是q行在实模式下,直到Windows3.0Q它q行期间既有实模式又有保护模式,所以说它是一U؜合模式工作。它的保护模式运行有两种不同意义(因ؓ80286q没有完全地实现80386及以后的保护模式功能)Q?/span>

 

1〉“标准保护模式”:q就是程序运行在保护模式下;

2〉“虚拟保护模式(实质上还是实模式Q是实模式上模拟的保护模式)”:它也使用32位地址d方式?/span>Windows3.1d删除了对实模式的支持。在80286处理器芯片以后,Windows3.1成ؓL操作pȝQ?/span>Windows/80286不是L产品Q。目前差不多所有的X86pd处理器操作系l(LinuxQ?/span>Windows95 and laterQ?/span>OS/2{)都是在启动时q行处理器设|而进入保护模式的?/span>

 

实模式工作机理:

1> 对于8086/8088来说计算实际地址是用l对地址?/span>1M求模?/span>8086的地址U的物理l构Q?/span>20根,也就是它可以物理d的内存范围ؓ2^20个字节,?/span>1 MI间Q但׃8086/8088所使用的寄存器都是16位,能够表示的地址范围只有0-64KQ这?/span>1M地址I间来比较也太小了,所以ؓ了在8086/8088下能够访?/span>1M内存Q?/span>Intel采取了分D寻址的模式:16位段基地址:16位偏U?/span>EA。其l对地址计算Ҏ为:16位基地址左移4?/span>+16位偏U?/span>=20位地址?/span>  比如Q?/span>DS=1000H EA=FFFFH 那么l对地址׃ؓQ?/span>10000H +
0FFFFH = 1FFFFH 地址单元
。通过q种Ҏ来实C?/span>16位寄存器讉K1M的地址I间Q这U技术是处理器内部实现的Q通过上述分段技术模式,能够表示的最大内存ؓQ?/span>
FFFFh: FFFFh=FFFF0h+FFFFh=10FFEFh=1M+64K-16BytesQ?/span>1M多余出来的部分被U做高端内存?/span>HMAQ?span style="color: black;">。但8086/8088只有20位地址U,只能够访?/span>1M地址范围的数据,所以如果访?/span>100000h~10FFEFh之间的内存(大于1MI间Q,则必LW?/span>21根地址U来参与dQ?/span>8086/8088没有Q。因此,当程序员l出过1MQ?/span>100000H-10FFEFHQ的地址Ӟ因ؓ逻辑上正常,pȝq不认ؓ其访问越界而生异常,而是自动?/span>0开始计,也就是说pȝ计算实际地址的时候是按照?/span>1M求模的方式进行的Q这U技术被UCؓwrap-around?/span>

 

 

2> 对于80286或以上的CPU通过A20 GATE来控?/span>A20地址U?/span> ?/span> 技术发展到?/span> 80286Q虽然系l的地址ȝ由原来的20根发展ؓ24根,q样能够讉K的内存可以达?/span>2^24=16M,但是Intel在设?/span>80286时提出的目标是向下兼?/span>,所以在实模式下Q系l所表现的行为应该和8086/8088所表现的完全一P也就是说Q在实模式下Q?/span>80386以及后箋pd应该?/span>8086/8088完全兼容仍然使用A20地址Uѝ所以说80286芯片存在一?/span>BUGQ它开?/span>A20地址Uѝ如果程序员讉K100000H-10FFEFH之间的内存,pȝ实际访问这块内存(没有wrap-around技术)Q而不是象8086/8088一样从0开始。我们来看一副图Q?/span>


Z解决上述兼容性问题,IBM使用键盘控制器上剩余的一些输出线来管理第21根地址U(?/span>0开始数是第20根) 的有效性,被称?/span>A20 GateQ?/span>

1> 如果A20 Gate被打开Q则当程序员l出100000H-10FFEFH之间的地址的时候,pȝ真正访问这块内存区域;

2 如果A20 Gate被禁止,则当E序员给?/span>100000H-10FFEFH之间的地址的时候,pȝ仍然使用8086/8088的方式即取模方式Q?/span>8086仿真Q。绝大多?/span>IBM PC兼容机默认的A20 Gate是被止的。现在许多新?/span>PC上存在直接通过BIOS功能调用来控?/span>A20 Gate的功能?/span>

上面所q的内存讉K模式都是实模式,?/span>80286以及更高pd?/span>PC中,即A20 Gate被打开Q在实模式下所能够讉K的内存最大也只能?/span>10FFEFHQ尽它们的地址ȝ所能够讉K的能力都大大过q个限制。ؓ了能够访?/span>10FFEFH以上的内存,则必进入保护模式?/span>

保护模式Q?/strong> l常~写?/span> p-mode,?/font> Intel iAPX 286E序员参考手册中Q?/span>iAPX
286?/span>Intel 80286的另一U叫法) 它又被称作ؓ虚拟地址保护模式。经在Intel 80286手册中已l提Z虚地址保护模式Q但实际上它只是一个指引,真正?/span>32位地址出现?/span>Intel 80386上。保护模式本w是80286及以后兼容处理器序列之后产成的一U操作模式,它具有许多特性设计ؓ提高pȝ的多道Q务和pȝ的稳定性。例如内存的保护Q分|制和g虚拟存储的支持。现代多数的x86处理器操作系l都q行在保护模式下Q包?/span>Linux, Free BSD, ?/span>Windows
3.0Q它也运行在实模式下Qؓ了和Windows 2.x应用E序兼容Q及以后的版本?/span>

 

80286及以后的处理器另一U工作模式是实模式(仅当pȝ启动的一瞬间Q,本着向下兼容的原则屏蔽保护模式特性,从而容许老的软g能够q行在新的芯片上。作Z个设计规范,所有的x86pd处理器,除嵌入式Intel80387之外Q都是系l启动工作在实模式下Q确保遗留下的操作系l向下兼宏V它们都必须被启动程序(操作pȝE序最初运行代码)重新讄而相应进入保护模式的Q在q之前Q何的保护模式Ҏ都是无效的。在C计算ZQ这U匹配进入保护模式是操作pȝ启动时最前沿的动作之一?/span>

 

在被调停的多道Q务程序中Q它可以从新工作在实模式下是相当可能的。保护模式的Ҏ是L被其他Q务或pȝ内核破坏已经不健全的E序的运行,保护模式也有对硬件的支持Q例如中断运行程序,Udq行q程文档到另一个进E和|空多Q务的保护功能?/span>

 

386及以后系列处理器不仅h保护模式又具?/span>32位寄存器Q结果导致了处理功能的؜乱,因ؓ80286虽然支持保护模式Q但是它的寄存器都是16位的Q它是通过自nE序讑֮而模拟出?/span>32位,q32位寄存器处理。归咎于q种混ؕ现象Q它促Windows/386 及以后的版本d抛弃80286的虚拟保护模式,以后保护模式的操作系l都是运行在80386以上Q不再运行在80286Q尽?/span>80286模式支持保护模式Q,所以说80286是一个过渡芯片,它是一个过渡品?/span>

 

286?/span>386处理器能够实C护模式和兼容以前的版本,但是内存?/span>1M以上I间q是不易存取Q由于内存地址的回l,IBM PC XT Q现以废弃)设计一U模拟系l,它能q欺骗手D访问到1M以上的地址I间Q就是开通了A20地址Uѝ在保护模式里,?/span>32个中断ؓ处理器异帔R留,例如Q中?/span>0DQ十q制13Q常规保护故障和中断00是除Cؓ零异常?/span>

 

如果要访问更多的内存Q则必须q入保护模式Q那么,在保护模式下Q?/font> A20
Gate对于内存讉K有什么媄响呢Q?/font>

Z搞清楚这一点,我们先来看一?/span>A20的工作原理?/span>A20Q从它的名字可以看出来Q其实它是对于A20Q从0开始数Q的Ҏ处理(也就是对W?/span>21根地址U的处理)。如?/span>A20 Gate被禁止,对于80286来说Q其地址?/span>24根地址U,其地址表示?/span>EFFFFFQ对?/span>80386极其随后?/span>32根地址U芯片来_其地址表示?/span>FFEFFFFF。这U表C的意思是Q?/span>

1> 如果A20
Gate被禁止。则其第A20?/span>CPU做地址讉K的时候是无效的,永远只能被作?/span>0。所以,在保护模式下Q如?/span>A20
Gate被禁止,则可以访问的内存只能是奇?/span>1MD,?/span>1M,3M,5M?span lang="ZH-CN">Q也是00000-FFFFF, 200000-2FFFFF,300000-3FFFFF?/font>

2如果A20 Gate被打开。则其第20-bit是有效的Q其值既可以?/span>0Q又可以?/span>1。那么就可以?/span>A20U传递实际的地址信号。如?/span>A20 Gate被打开Q则可以讉K的内存则是连l的?/span>

实模式和保护模式的区别: 从表面上看,保护模式和实模式q没有太大的区别Q二者都使用了内存段、中断和讑֤驱动来处理硬Ӟ但二者有很多不同之处。我们知道,在实模式中内存被划分成段Q每个段的大ؓ 64KB Q而这LD地址可以?/span> 16 位来表示。内存段的处理是通过和段寄存器相兌的内部机制来处理的,q些D寄存器Q?/span> CS ?/span> DS ?/span> SS ?/span> ES Q的内容形成了物理地址的一部分。具体来_最l的物理地址是由 16 位的D地址?/span> 16 位的D内偏移地址l成的。用公式表示为:物理地址 = 左移 4 位的D地址 + 偏移地址?/span>

在保护模式下Q段是通过一pd被称之ؓ ?/span> 描述W表 ?/span> 的表所定义的。段寄存器存储的是指向这些表的指针。用于定义内存段的表有两U:全局描述W表 (GDT) 和局部描q符?/span> (LDT) ?/span> GDT 是一个段描述W数l,其中包含所有应用程序都可以使用的基本描q符。在实模式中Q段长是固定?/span> ( ?/span> 64KB) Q而在保护模式中,D长是可变的Q其最大可?/span> 4GB ?/span> LDT 也是D|q符的一个数l。与 GDT 不同Q?/span> LDT 是一个段Q其中存攄是局部的、不需要全局׃n的段描述W。每一个操作系l都必须定义一?/span> GDT Q而每一个正在运行的d都会有一个相应的 LDT 。每一个描q符的长度是 8 个字节,格式如图 3 所C。当D寄存器被加载的时候,D基地址׃从相应的表入口获得。描q符的内容会被存储在一个程序员不可见的影像寄存?/span> (shadow register) 之中Q以便下一ơ同一个段可以使用该信息而不用每ơ都到表中提取。物理地址?/span> 16 位或?/span> 32 位的偏移加上影像寄存器中的基址l成。实模式和保护模式的不同可以从下囑־清楚地看出来?/span>

实模式下d方式

 

保护模式下寻址方式



]]>
Boot Sectorl构、系l启动过E简?/title><link>http://www.shnenglu.com/mydriverc/articles/30640.html</link><dc:creator>旅?/dc:creator><author>旅?/author><pubDate>Wed, 22 Aug 2007 16:36:00 GMT</pubDate><guid>http://www.shnenglu.com/mydriverc/articles/30640.html</guid><wfw:comment>http://www.shnenglu.com/mydriverc/comments/30640.html</wfw:comment><comments>http://www.shnenglu.com/mydriverc/articles/30640.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.shnenglu.com/mydriverc/comments/commentRss/30640.html</wfw:commentRss><trackback:ping>http://www.shnenglu.com/mydriverc/services/trackbacks/30640.html</trackback:ping><description><![CDATA[ <font size="2">解决困扰已久的系l启动过E,可以帮助使多pȝ共存Q而不影响各自启动Q?/font> <p style="margin: 0cm 0cm 0pt;" class="MsoNormal"> <font size="2"> <b style="">Boot Sector</b> <b style="">l构、系l启动过E简?/b> <br /> <br />一. Boot Sector 的组?<br /><br />   Boot Sector 也就是硬盘的W一个扇区(?font face="Times New Roman">1</font>Q?font face="Times New Roman">0</font>柱面Q?font face="Times New Roman">0</font>道Q?font face="Times New Roman">1</font>扇区Q?Q注<font face="Times New Roman">2</font>Q?font face="Times New Roman"> 1</font>道<font face="Times New Roman">=16</font>扇区Q?font face="Times New Roman">1</font>扇区<font face="Times New Roman">=512</font>字节Q? 它由 MBR (Master Boot Record), DPT (Disk Partition Table) ?Boot Record ID  三部分组? <br />   MBR 又称作主引导记录占用 Boot Sector 的前 446 个字?( 0 to 0x1BD ), <br />存放pȝd导程?(它负责检查硬盘分、寻扑֏引导分区q负责将可引导分区的引导扇区QDBRQ装入内?. <br />   DPT 即主分区表占?64 个字?(0x1BE to 0x1FD), 记录了磁盘的基本分区 <br />信息. d分ؓ四个分区? 每项 16 字节, 分别记录了每个主分区的信?<br />(因此最多可以有四个d?. <br />   Boot Record ID 卛_导区标记占用两个字节 <b>(0x1FE and 0x1FF), 对于合法 <br /> 引导? 它等?</b><b>0xAA55,</b> q是判别引导区是否合法的标志. <br />   Boot Sector 的具体结构如下图所C? <br />     0000  |------------------------------------------------| <br />           |                                                | <br />           |                                                | <br />           |             Master Boot Record                 | <br />           |                                                | <br />           |                                                | <br />           |             dD?446字节)                | <br />           |                                                | <br />           |                                                | <br />           |                                                | <br />     01BD  |                                                | <br />     01BE  |------------------------------------------------| <br />           |                                                | <br />     01CD  |             分区信息  1(16字节)                | <br />     01CE  |------------------------------------------------| <br />           |                                                | <br />     01DD  |             分区信息  2(16字节)                | <br />     01DE  |------------------------------------------------| <br />           |                                                | <br />     01ED  |             分区信息  3(16字节)                | <br />     01EE  |------------------------------------------------| <br />           |                                                | <br />     01FD  |             分区信息  4(16字节)                | <br />           |------------------------------------------------| <br />           | 01FE                | 01FF                     | <br />           |         55          |           AA             | <br />           |------------------------------------------------|</font> </p> <p style="margin: 0cm 0cm 0pt;" class="MsoNormal"> <font size="2">? pȝ启动q程?<br /><br />   pȝ启动q程主要׃下几步组?以硬盘启动ؓ?: <br /><br />   1. 开?br />   2. BIOS 加电自检 ( Power On Self Test -- POST ) <br />      内存地址?0ffff:0000 <br />   3. 硬盘第一个扇?(0??扇区, 也就是Boot Sector) <br />      d<b>内存地址 0000:<chmetcnv unitname="C" sourcevalue="7" hasspace="False" negative="False" numbertype="1" tcsc="0" w:st="on">7c</chmetcnv>00</b> ?Q注<font face="Times New Roman">3</font>Q遇到最后两个字?5 AAd内存执行Q?<br />   4. ?(WORD) 0000:7dfe 是否{于 0xaa55, 若不{于 <br />      则{d试其他启动介? 如果没有其他启动介质则显C?<br />      "No ROM BASIC" 然后L. <br />   5. 跌{?0000:<chmetcnv unitname="C" sourcevalue="7" hasspace="False" negative="False" numbertype="1" tcsc="0" w:st="on">7c</chmetcnv>00 处执?MBR 中的E序. <br />   6. MBR 首先自己复制到 0000:0600 ? 然后l箋执行. <br />   7. 在主分区表中搜烦标志为活动的分区. 如果发现没有zd <br />      分区或有不止一个活动分? 则{停止. <br />   8. 活动分区的W一个扇入内存地址 0000:<chmetcnv unitname="C" sourcevalue="7" hasspace="False" negative="False" numbertype="1" tcsc="0" w:st="on">7c</chmetcnv>00 ?</font> </p> <p style="margin: 0cm 0cm 0pt;" class="MsoNormal"> <font size="2">Q注4Q在分区表的四个记录中,一般来说有且只有一个记录的标记是活动的QMBRQ主要负责从zd分区中装载ƈq行pȝ引导E序Q会L到这个分录,Ҏ记录的v始扇区加载该分区的逻辑 0 扇区Qv始扇区)的内容到 0x<chmetcnv unitname="C" sourcevalue="7" hasspace="False" negative="False" numbertype="1" tcsc="0" w:st="on">07C</chmetcnv>0:0000Qƈ且执?JUMP 0x<chmetcnv unitname="C" sourcevalue="7" hasspace="False" negative="False" numbertype="1" tcsc="0" w:st="on">07C</chmetcnv>0:0000Q按照规范,BOOT RECORD 也应该从 0x<chmetcnv unitname="C" sourcevalue="7" hasspace="False" negative="False" numbertype="1" tcsc="0" w:st="on">07C</chmetcnv>0:0000 处开始执行,所?MBR 通常都要先将自己搬移Q以腑և位置d?BOOT RECORDQ。控制权切换?BOOT RECORD。BOOT RECORD(包括boot loader如grub或lilo {??linux ZQ它会读?linux 内核镜像到地址 0x9000:0000Q然后开始切换到 0x9000:0000 l箋q行??MS-DOS ZQ则它会d文gpȝ根目录下?IO.SYS ?MSDOS.SYS 两个文g然后加蝲到内存中l箋q行。)<br />   9. ?(WORD) 0000:7dfe 是否{于 0xaa55, 若不{于?<br />      昄 "Missing Operating System" 然后停止, 或尝?<br />      软盘启动. <br />   10. 跌{?0000:<chmetcnv unitname="C" sourcevalue="7" hasspace="False" negative="False" numbertype="1" tcsc="0" w:st="on">7c</chmetcnv>00 处l执行特定系l的启动E序. <br />   11. 启动pȝ ... <br /><br />   以上步骤?2,3,4,5 步是?<b>BIOS 的引导程?/b>完成. 6,7,8,9,10 <br />步由<b>MBR中的引导E序</b>完成. <br /><br />   一般多pȝ引导E序 (?SmartFDISK, BootStar, PQBoot {? <br />都是标准主引导记录替换成自q引导E序, 在运行系l启动程?<br />之前让用户选择要启动的分区. <br />   而某些系l自带的多系l引导程?(?lilo, NT Loader {? <br />则可以将自己的引导程序放在系l所处分区的W一个扇Z, ?Linux <br />中即?SuperBlock (其实 SuperBlock 是两个扇?. <br />   ? 以上各步骤中使用的是标准 MBR, 其他多系l引导程序的引导q程与此不同.</font> </p> <font size="2">?Q一些早期的引导型病毒,以及某些 bootloaderQ还有些盘加密卡,他们会修?MBRQ做个“钩子”出来?/font> <img src ="http://www.shnenglu.com/mydriverc/aggbug/30640.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.shnenglu.com/mydriverc/" target="_blank">旅?/a> 2007-08-23 00:36 <a href="http://www.shnenglu.com/mydriverc/articles/30640.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>?Linux 2.4内核中的轻量q程实现http://www.shnenglu.com/mydriverc/articles/29745.html旅?/dc:creator>旅?/author>Fri, 10 Aug 2007 17:38:00 GMThttp://www.shnenglu.com/mydriverc/articles/29745.htmlhttp://www.shnenglu.com/mydriverc/comments/29745.htmlhttp://www.shnenglu.com/mydriverc/articles/29745.html#Feedback0http://www.shnenglu.com/mydriverc/comments/commentRss/29745.htmlhttp://www.shnenglu.com/mydriverc/services/trackbacks/29745.html最初的q程定义都包含程序、资源及其执行三部分Q其中程序通常指代码,资源在操作系l层面上通常包括内存资源、IO资源、信号处理等部分Q而程序的 执行通常理解为执行上下文Q包括对cpu的占用,后来发展为线E。在U程概念出现以前Qؓ了减进E切换的开销Q操作系l设计者逐渐修正q程的概念,逐渐 允许进E所占有的资源从其主体剥d来,允许某些q程׃n一部分资源Q例如文件、信P数据内存Q甚至代码,q就发展量进E的概念。Linux内核 ?.0.x版本已l实C轻量q程Q应用程序可以通过一个统一的clone()pȝ调用接口Q用不同的参数指定创量进E还是普通进E。在内核中, clone()调用l过参数传递和解释后会调用do_fork()Q这个核内函数同时也是fork()、vfork()pȝ调用的最l实玎ͼ

<linux-2.4.20/kernel/fork.c>
int do_fork(unsigned long clone_flags, unsigned long stack_start,
struct pt_regs *regs, unsigned long stack_size)

其中的clone_flags取自以下宏的"?|

<linux-2.4.20/include/linux/sched.h>
#define CSIGNAL 0x000000ff /* signal mask to be sent at exit */
#define CLONE_VM 0x00000100 /* set if VM shared between processes */
#define CLONE_FS 0x00000200 /* set if fs info shared between processes */
#define CLONE_FILES 0x00000400 /* set if open files shared between processes */
#define CLONE_SIGHAND 0x00000800 /* set if signal handlers and blocked signals shared */
#define CLONE_PID 0x00001000 /* set if pid shared */
#define CLONE_PTRACE 0x00002000 /* set if we want to let tracing continue on the child too */
#define CLONE_VFORK 0x00004000 /* set if the parent wants the child to wake it up on mm_release */
#define CLONE_PARENT 0x00008000 /* set if we want to have the same parent as the cloner */
#define CLONE_THREAD 0x00010000 /* Same thread group? */
#define CLONE_NEWNS 0x00020000 /* New namespace group? */
#define CLONE_SIGNAL (CLONE_SIGHAND | CLONE_THREAD)

在do_fork()中,不同? clone_flags导致不同的行ؓQ对于LinuxThreadsQ它使用QCLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHANDQ参数来调用clone()创徏"U程"Q表C共享内存、共享文件系l访问计数、共享文件描q符表,以及׃n信号处理方式。本 节就针对q几个参敎ͼ看看Linux内核是如何实现这些资源的׃n的?/p>

1.CLONE_VM

do_fork ()需要调用copy_mm()来设|task_struct中的mm和active_mm,q两个mm_struct数据与进E所兌的内存空间相? 应。如果do_fork()时指定了CLONE_VM开养Icopy_mm()把新的task_struct中的mm和active_mm讄成与 current的相同,同时提高该mm_struct的用者数目(mm_struct::mm_usersQ。也是_轻量U进E与父进E共享内存地 址I间Q由下图C意可以看出mm_struct在进E中的地位:



2.CLONE_FS

task_struct 中利用fsQstruct fs_struct *Q记录了q程所在文件系l的根目录和当前目录信息Qdo_fork()时调用copy_fs()复制了这个结构;而对于轻量q程则仅增加fs- >count计数Q与父进E共享相同的fs_struct。也是_轻量U进E没有独立的文gpȝ相关的信息,q程中Q何一个线E改变当前目录? 根目录等信息都将直接影响到其他线E?/p>

3.CLONE_FILES

一 个进E可能打开了一些文Ӟ在进E结构task_struct中利用filesQstruct files_struct *Q来保存q程打开的文件结构(struct fileQ信息,do_fork()中调用了copy_files()来处理这个进E属性;轻量U进E与父进E是׃n该结构的Qcopy_files() 时仅增加files->count计数。这一׃n使得MU程都能讉Kq程所l护的打开文gQ对它们的操作会直接反映到进E中的其他线E?/p>

4.CLONE_SIGHAND

? 一个Linuxq程都可以自行定义对信号的处理方式,在task_struct中的sigQstruct signal_structQ中使用一个struct k_sigactionl构的数l来保存q个配置信息Qdo_fork()中的copy_sighand()负责复制该信息;轻量U进E不q行复制Q而仅 仅增加signal_struct::count计数Q与父进E共享该l构。也是_子进E与父进E的信号处理方式完全相同Q而且可以怺更改?/p>

do_fork()中所做的工作很多Q在此不详细描述。对于SMPpȝQ所有的q程fork出来后,都被分配C父进E相同的cpu上,一直到该进E被调度时才会进行cpu选择?/p>

? Linux支持轻量U进E,但ƈ不能说它支持核心U程Q因为Linux?U程"?q程"实际上处于一个调度层ơ,׃n一个进E标识符I间Q这U? 限制使得不可能在Linux上实现完全意义上的POSIXU程机制Q因此众多的LinuxU程库实现尝试都只能可能实现POSIX的绝大部分语义,q在 功能上尽可能D?/p>



回页?/b>


?LinuxThread的线E机?/span>

LinuxThreads 是目前Linuxq_上用最为广泛的U程库,由Xavier Leroy (Xavier.Leroy@inria.fr)负责开发完成,q已l定在GLIBC中发行。它所实现的就是基于核心轻量q程?一对一"U程模型Q一 个线E实体对应一个核心轻量q程Q而线E之间的理在核外函数库中实现?/p>

1.U程描述数据l构及实现限?/span>

LinuxThreads 定义了一个struct _pthread_descr_struct数据l构来描q线E,q用全局数组变量__pthread_handles来描q和引用q程所辖线E。在 __pthread_handles中的前两,LinuxThreads定义了两个全局的系l线E:__pthread_initial_thread 和__pthread_manager_threadQƈ用__pthread_main_thread表征 __pthread_manager_thread的父U程Q初始ؓ__pthread_initial_threadQ?/p>

struct _pthread_descr_struct是一个双环链表结构,__pthread_manager_thread所在的链表仅包括它一个元素,实际 上,__pthread_manager_thread是一个特D线E,LinuxThreads仅用了其中的errno、p_pid? p_priority{三个域。而__pthread_main_thread所在的铑ֈ进E中所有用LE串在了一赗经q一pd pthread_create()之后形成的__pthread_handles数组如下图所C:


? __pthread_handles数组l构

新创建的U程首先在__pthread_handles数组中占据一,然后通过数据l构中的链指针连入以__pthread_main_thread为首指针的链表中。这个链表的使用在介l线E的创徏和释攄时候将提到?/p>

LinuxThreads 遵@POSIX1003.1c标准Q其中对U程库的实现q行了一些范围限Ӟ比如q程最大线E数Q线E私有数据区大小{等。在LinuxThreads? 实现中,基本遵@q些限制Q但也进行了一定的改动Q改动的势是放松或者说扩大q些限制Qɾ~程更加方便。这些限定宏主要集中? sysdeps/unix/sysv/linux/bits/local_lim.hQ不同^C用的文g位置不同Q中Q包括如下几个:

? q程的私有数据key敎ͼPOSIX定义_POSIX_THREAD_KEYS_MAX?28QLinuxThreads使用 PTHREAD_KEYS_MAXQ?024Q私有数据释放时允许执行的操作数QLinuxThreads与POSIX一_定义 PTHREAD_DESTRUCTOR_ITERATIONS?Q每q程的线E数QPOSIX定义?4QLinuxThreads增大?024 QPTHREAD_THREADS_MAXQ;U程q行栈最空间大,POSIX未指定,LinuxThreads使用 PTHREAD_STACK_MINQ?6384Q字节)?/p>

2.理U程

" 一对一"模型的好处之一是线E的调度由核心完成了Q而其他诸如线E取消、线E间的同步等工作Q都是在核外U程库中完成的。在LinuxThreads中, 专门为每一个进E构造了一个管理线E,负责处理U程相关的管理工作。当q程W一ơ调用pthread_create()创徏一个线E的时候就会创? Q__clone()Qƈ启动理U程?/p>

在一个进E空间内Q管理线E与其他U程之间通过一?理道 Qmanager_pipe[2]Q?来通讯Q该道在创建管理线E之前创建,在成功启动了理U程之后Q管理管道的ȝ和写端分别赋l两个全局变量 __pthread_manager_reader和__pthread_manager_requestQ之后,每个用户U程都通过 __pthread_manager_request向管理线E发hQ但理U程本nq没有直接? __pthread_manager_readerQ管道的ȝQmanager_pipe[0]Q是作ؓ__clone()的参C一传给理U程的, 理U程的工作主要就是监听管道读端,q对从中取出的请求作出反应?/p>

创徏理U程的流E如下所C:
Q全局变量pthread_manager_request初gؓ-1Q?


? 创徏理U程的流E? src=

? 始化l束后,在__pthread_manager_thread中记录了轻量U进E号以及核外分配和管理的U程idQ? 2*PTHREAD_THREADS_MAX+1q个数g会与M常规用户U程id冲突。管理线E作为pthread_create()的调用者线E的 子线E运行,而pthread_create()所创徏的那个用LE则是由理U程来调用clone()创徏Q因此实际上是管理线E的子线E。(此处? U程的概念应该当作子q程来理解。)

__pthread_manager()是理U程的主循环所在,在进行一pd初始 化工作后Q进入while(1)循环。在循环中,U程?Uؓtimeout查询Q__poll()Q管理管道的ȝ。在处理h前,查其父线E(也就 是创建manager的主U程Q是否已退出,如果已退出就退出整个进E。如果有退出的子线E需要清理,则调用pthread_reap_children ()清理?/p>

然后才是d道中的hQ根据请求类型执行相应操作(switch-caseQ。具体的h处理Q源码中比较清楚Q这里就不赘qC?/p>

3.U程?/span>

在LinuxThreads中,理U程的栈和用LE的栈是分离的,理U程在进E堆中通过malloc()分配一个THREAD_MANAGER_STACK_SIZE字节的区域作qq行栈?/p>

? LE的栈分配办法随着体系l构的不同而不同,主要Ҏ两个宏定义来区分Q一个是NEED_SEPARATE_REGISTER_STACKQ这个属性仅 在IA64q_上用;另一个是FLOATING_STACK宏,在i386{少数^C使用Q此时用LE栈ql决定具体位|ƈ提供保护。与此同Ӟ 用户q可以通过U程属性结构来指定使用用户自定义的栈。因幅所限,q里只能分析i386q_所使用的两U栈l织方式QFLOATING_STACK方式 和用戯定义方式?/p>

在FLOATING_STACK方式下,LinuxThreads利用mmap()从内核空间中分配 8MBI间Qi386pȝ~省的最大栈I间大小Q如果有q行限制QrlimitQ,则按照运行限制设|)Q用mprotect()讄其中W一ؓ非访 问区。该8MI间的功能分配如下图Q?/p>
? 栈结构示? src=

低地址被保护的面用来监测栈溢出?/p>

对于用户指定的栈Q在按照指针对界后,讄U程栈顶Qƈ计算出栈底,不做保护Q正性由用户自己保证?/p>

不论哪种l织方式Q线E描q结构L位于栈顶紧邻堆栈的位|?/p>

4.U程id和进Eid

每个LinuxThreadsU程都同时具有线Eid和进EidQ其中进Eid是内核所l护的进E号Q而线Eid则由LinuxThreads分配和维护?/p>

__pthread_initial_thread 的线Eid为PTHREAD_THREADS_MAXQ__pthread_manager_thread的是 2*PTHREAD_THREADS_MAX+1Q第一个用LE的U程id为PTHREAD_THREADS_MAX+2Q此后第n个用LE的U程 id遵@以下公式Q?/p>
	tid=n*PTHREAD_THREADS_MAX+n+1

q种分配方式保证了进E中所有的U程Q包括已l退出)都不会有相同的线EidQ而线Eid的类型pthread_t定义为无W号长整型(unsigned long intQ,也保证了有理qq行旉内线Eid不会重复?/p>

从线Eid查找U程数据l构是在pthread_handle()函数中完成的Q实际上只是线E号按PTHREAD_THREADS_MAX取模Q得到的是该线E在__pthread_handles中的索引?/p>

5.U程的创?/span>

在pthread_create ()向管理线E发送REQ_CREATEh之后Q管理线E即调用pthread_handle_create()创徏新线E。分配栈、设|thread 属性后Q以pthread_start_thread()为函数入口调用__clone()创徏q启动新U程。pthread_start_thread ()d自n的进Eid号存入线E描q结构中QƈҎ其中记录的调度方法配|调度。一切准备就l后Q再调用真正的线E执行函敎ͼq在此函数返回后调用 pthread_exit()清理现场?/p>

6.LinuxThreads的不?/span>

׃Linux内核的限制以及实现难度等{原因,LinuxThreadsq不是完全POSIX兼容的,在它的发行README中有说明?/p>

1)q程id问题

q个不是最关键的不I引v的原因牵涉到LinuxThreads?一对一"模型?/p>

Linux 内核q不支持真正意义上的U程QLinuxThreads是用与普通进E具有同样内核调度视囄轻量U进E来实现U程支持的。这些轻量q程拥有独立的进 EidQ在q程调度、信号处理、IO{方面n有与普通进E一L能力。在源码阅读者看来,是Linux内核的clone()没有实现? CLONE_PID参数的支持?/p>

在内核do_fork()中对CLONE_PID的处理是q样的:

          if (clone_flags & CLONE_PID) {
if (current->pid)
goto fork_out;
}

q段代码表明Q目前的Linux内核仅在pid?的时候认可CLONE_PID参数Q实际上Q仅在SMP初始化,手工创徏q程的时候才会用CLONE_PID参数?/p>

按照POSIX定义Q同一q程的所有线E应该共享一个进Eid和父q程idQ这在目前的"一对一"模型下是无法实现的?/p>

2)信号处理问题

׃异步信号是内总q程为单位分发的Q而LinuxThreads的每个线E对内核来说都是一个进E,且没有实?U程l?Q因此,某些语义不符合POSIX标准Q比如没有实现向q程中所有线E发送信PREADMEҎ作了说明?/p>

? 果核心不提供实时信号QLinuxThreads用SIGUSR1和SIGUSR2作ؓ内部使用的restart和cancel信号Q这样应用程序就 不能使用q两个原本ؓ用户保留的信号了。在Linux kernel 2.1.60以后的版本都支持扩展的实时信P从_SIGRTMIN到_SIGRTMAXQ,因此不存在这个问题?/p>

某些信号的缺省动作难以在现行体系上实玎ͼ比如SIGSTOP和SIGCONTQLinuxThreads只能一个线E挂P而无法挂h个进E?/p>

3)U程L问题

LinuxThreads每个进E的U程最大数目定义ؓ1024Q但实际上这个数D受到整个pȝ的总进E数限制Q这又是׃U程其实是核心进E?/p>

在kernel 2.4.x中,采用一套全新的总进E数计算ҎQ得总进E数基本上仅受限于物理内存的大小Q计公式在kernel/fork.c的fork_init()函数中:

	max_threads = mempages / (THREAD_SIZE/PAGE_SIZE) / 8

在i386 上,THREAD_SIZE=2*PAGE_SIZEQPAGE_SIZE=2^12Q?KBQ,mempages=物理内存大小/PAGE_SIZEQ? 对于256M的内存的机器Qmempages=256*2^20/2^12=256*2^8Q此时最大线E数?096?/p>

但ؓ了保证每个用P除了rootQ的q程L不至于占用一半以上物理内存,fork_init()中l指定:

    init_task.rlim[RLIMIT_NPROC].rlim_cur = max_threads/2;
init_task.rlim[RLIMIT_NPROC].rlim_max = max_threads/2;

q些q程数目的检查都在do_fork()中进行,因此Q对于LinuxThreads来说Q线EL同时受这三个因素的限制?/p>

4)理U程问题

理U程Ҏ成ؓ瓉Q这是这U结构的通病Q同Ӟ理U程又负责用LE的清理工作Q因此,管理U程已经屏蔽了大部分的信P但一旦管理线E死亡,用户U程׃得不手工清理了,而且用户U程q不知道理U程的状态,之后的线E创建等h无人处理?/p>

5)同步问题

LinuxThreads中的U程同步很大E度上是建立在信号基上的Q这U通过内核复杂的信号处理机制的同步方式Q效率一直是个问题?/p>

6Q其他POSIX兼容性问?/b>

Linux中很多系l调用,按照语义都是与进E相关的Q比如nice、setuid、setrlimit{,在目前的LinuxThreads中,q些调用都仅仅媄响调用者线E?/p>

7Q实时性问?/b>

U程的引入有一定的实时性考虑Q但LinuxThreads暂时不支持,比如调度选项Q目前还没有实现。不仅LinuxThreads如此Q标准的Linux在实时性上考虑都很?/p>



回页?/b>


?其他的线E实现机?/span>

LinuxThreads 的问题,特别是兼Ҏ上的问题,严重ȝ了Linux上的跨^台应用(如ApacheQ采用多U程设计Q从而得Linux上的U程应用一直保持在比较? 的水q뀂在LinuxC֌中,已经有很多h在ؓ改进U程性能而努力,其中既包括用LU程库,也包括核心和用L配合改进的线E库。目前最Zh看好的有 两个目Q一个是RedHat公司牵头研发的NPTLQNative Posix Thread LibraryQ,另一个则是IBM投资开发的NGPTQNext Generation Posix ThreadingQ,二者都是围l完全兼容POSIX 1003.1cQ同时在核内和核外做工作以而实现多对多U程模型。这两种模型都在一定程度上弥补了LinuxThreads的缺点,且都是重L灶全新设 计的?/p>

1.NPTL

NPTL的设计目标归U_归纳Z下几点:

  • POSIX兼容?/li>
  • SMPl构的利?/li>
  • 低启动开销
  • 低链接开销Q即不用线E的E序不应当受U程库的影响Q?/li>
  • 与LinuxThreads应用的二q制兼容?/li>
  • 软硬件的可扩展能?/li>
  • 多体pȝ构支?/li>
  • NUMA支持
  • 与C++集成

? 技术实CQNPTL仍然采用1:1的线E模型,q合glibc和最新的Linux Kernel2.5.x开发版在信号处理、线E同步、存储管理等多方面进行了优化。和LinuxThreads不同QNPTL没有使用理U程Q核心线E? 的管理直接放在核内进行,q也带了性能的优化?/p>

主要是因为核心的问题QNPTL仍然不是100%POSIX兼容的,但就性能而言相对LinuxThreads已经有很大程度上的改q了?/p>

2.NGPT

IBM的开放源码项目NGPT?003q??0日推ZE_?.2.0版,但相关的文档工作q差很多。就目前所知,NGPT是基于GNU PthQGNU Portable ThreadsQ项目而实现的M:N模型Q而GNU Pth是一个经典的用户U线E库实现?/p>

按照2003q?月NGPT官方|站上的通知QNGPT考虑到NPTL日益q泛Cؓ人所接受Qؓ避免不同的线E库版本引v的؜乱,今后不再进行进一步开发,而今q行支持性的l护工作。也是_NGPT已经攑ּ与NPTL竞争下一代Linux POSIXU程库标准?/p>

3.其他高效U程机制

? 处不能不提到Scheduler Activations。这?991q在ACM上发表的多线E内核结构媄响了很多多线E内核的设计Q其中包括Mach3.0、NetBSD和商业版? Digital UnixQ现在叫Compaq True64 UnixQ。它的实质是在用用LU程调度的同Ӟ可能地减少用户U对核心的系l调用请求,而后者往往是运行开销的重要来源。采用这U结构的U程? Ӟ实际上是l合了用LU程的灵z高效和核心U线E的实用性,因此Q包括Linux、FreeBSD在内的多个开放源码操作系l设计社区都在进行相关研 IӞ力图在本pȝ中实现Scheduler Activations?/p>

参考资?

  • [Linus TorvaldsQ?002] Linux内核源码v2.4.20

  • [GNUQ?002] Glibc源码v2.2.2Q内含LinuxThreads v0.9Q?br />
  • [Thomas E. TerrillQ?997] An Introduction to Threads Using The LinuxThreads Interface

  • [Ulrich DrepperQIngo MolnarQ?003] The Native POSIX Thread Library for Linux

  • http://www.ibm.com/developerworks/oss/pthreads/QNGPT官方|站

  • [Ralf S. EngelschallQ?000] Portable Multithreading

  • [Thomas E. Anderson, Brian N. Bershad, Edward D. Lazowska, Henry M. LevyQ?992] Scheduler Activations: Effective Kernel Support for the User-Level Management of Parallelism

  • [pcjockey@21cn.com] LinuxU程初探


关于作?/span>


杨沙zԌ目前在国防科技大学计算机学院攻读Y件方向博士学位?/p>



]]>
Linux U程实现机制分析http://www.shnenglu.com/mydriverc/articles/29744.html旅?/dc:creator>旅?/author>Fri, 10 Aug 2007 17:37:00 GMThttp://www.shnenglu.com/mydriverc/articles/29744.htmlhttp://www.shnenglu.com/mydriverc/comments/29744.htmlhttp://www.shnenglu.com/mydriverc/articles/29744.html#Feedback0http://www.shnenglu.com/mydriverc/comments/commentRss/29744.htmlhttp://www.shnenglu.com/mydriverc/services/trackbacks/29744.html自从多线E编E的概念出现?Linux 中以来,Linux 多线应用的发展L与两个问题脱不开q系Q兼Ҏ、效率。本文从U程模型入手Q通过分析目前 Linux q_上最行?LinuxThreads U程库的实现及其不Q描qC Linux C֌是如何看待和解决兼容性和效率q两个问题的?/blockquote>

一.基础知识Q线E和q程

按照教科书上的定义,q程是资源管理的最单位,U程是程序执行的最单位。在操作pȝ设计上,从进E演化出U程Q最主要的目的就是更好的支持SMP以及减小Q进E?U程Q上下文切换开销?/b>

? 论按照怎样的分法,一个进E至需要一个线E作为它的指令执行体Q进E管理着资源Q比如cpu、内存、文件等{)Q而将U程分配到某个cpu上执行。一? q程当然可以拥有多个U程Q此Ӟ如果q程q行在SMP机器上,它就可以同时使用多个cpu来执行各个线E,辑ֈ最大程度的q行Q以提高效率Q同Ӟ即 是在单cpu的机器上Q采用多U程模型来设计程序,正如当年采用多进E模型代替单q程模型一P使设计更z、功能更完备Q程序的执行效率也更高,例如? 用多个线E响应多个输入,而此时多U程模型所实现的功能实际上也可以用多进E模型来实现Q而与后者相比,U程的上下文切换开销比q程要小多了Q从语义? 来说Q同时响应多个输入这L功能Q实际上是׃n了除cpu以外的所有资源的?/p>

针对U程模型的两大意义,分别开发出了核 心U程和用LU程两种U程模型Q分cȝ标准主要是线E的调度者在核内q是在核外。前者更利于q发使用多处理器的资源,而后者则更多考虑的是上下文切? 开销?/b>在目前的商用pȝ中,通常都将两者结合v来用,既提供核心线E以满smppȝ的需要,也支持用U程库的方式在用h实现另一套线E机Ӟ此时一 个核心线E同时成为多个用h线E的调度者。正如很多技术一P"混合"通常都能带来更高的效率,但同时也带来更大的实现难度,Z"?的设计思\Q? Linux从一开始就没有实现混合模型的计划,但它在实C采用了另一U思\?混合"?/p>

在线E机制的具体实现上,可以? 操作pȝ内核上实现线E,也可以在核外实现Q后者显然要求核内至实Cq程Q而前者则一般要求在核内同时也支持进E。核心U程模型昄要求前者的? 持,而用LU程模型则不一定基于后者实现。这U差异,正如前所qͼ是两U分cL式的标准不同带来的?/p>

当核内既支持q程? 支持U程Ӟ可以实现线E?q程?多对?模型Q即一个进E的某个U程由核内调度,而同时它也可以作为用LU程池的调度者,选择合适的用户U线E在 其空间中q行。这是前面提到?混合"U程模型Q既可满_处理机系l的需要,也可以最大限度的减小调度开销。绝大多数商业操作系l(如Digital Unix、Solaris、IrixQ都采用的这U能够完全实现POSIX1003.1c标准的线E模型。在核外实现的线E又可以分ؓ"一对一"?多对 一"两种模型Q前者用一个核心进E(也许是轻量进E)对应一个线E,线E调度等同于q程调度Q交l核心完成,而后者则完全在核外实现多U程Q调度也在用 h完成。后者就是前面提到的单纯的用LU程模型的实现方式,昄Q这U核外的U程调度器实际上只需要完成线E运行栈的切换,调度开销非常,但同时因 为核心信P无论是同步的q是异步的)都是以进Eؓ单位的,因而无法定位到U程Q所以这U实现方式不能用于多处理器系l,而这个需求正变得来大Q因 此,在现实中Q纯用户U线E的实现Q除法研究目的以外Q几乎已l消׃?/p>

Linux内核只提供了轻量q程的支持,限制? 更高效的U程模型的实玎ͼ但Linux着重优化了q程的调度开销Q一定程度上也I补了q一~陷。目前最行的线E机制LinuxThreads所采用的就 是线E?q程"一对一"模型Q调度交l核心,而在用户U实C个包括信号处理在内的U程理机制。Linux-LinuxThreads的运行机制正是本 文的描述重点?/p>

]]>
þþۺϾɫۺϾ| 97Ʒ91þþþþ| ˾þۺߴý| þùƷ99Ʒ987| 2021þþƷ| aѹۿþav| Ʒ԰״̼þþ| þˬˬƬAV| һɫþۺϺݺƪ| ھƷþþþþ99| þҹɫ˾ƷС˵| ŷ޳ҹƷþ| Ӱһþҹײ | 69Ʒþþþ9999APGF| þþƷAVӰ| ޾Ʒþþþþ| Ʒþþ| ˾þô߽avһ| þþƷ99þ㽶| þþþƷһ | þþùƵ| Ʒþþþ| þþþþþþþþþþþ| þþþavëƬ| ŷ þ| ȫþվ| ˾Ʒ׽þ69| þþþþ˸߳ӰԺ| þAAAƬ69| þøԴƷ999| þ99Ʒþþþþ | þ99Ʒþþþþ | þþܳ| 2021˾Ʒþ| ޹˾þþƷӰ| ձþþþƷĻ| þֻоƷþ| þѹƵ| þþþùƷ鶹ARӰԺ| ĻƷѾþ| þþþþþƷͼƬ|