0.12 linux内核剖析
2009-05-15 22:43:00 couberlee 阅读数 1386

赵炯大哥新出版的关于linux的书籍,基于0.12版内核的,昨天刚在图书馆借来看了看,总体来说还可以吧,也比较详细,本人推荐这本书(可不是做广告啊,没收到一份钱)。

这本书所有的源代码均可在这个网站找到:http://oldlinux.org/oldlinux/viewthread.php?tid=290

或者我强烈推荐一个有关linux的网站:http://oldlinux.org/Linux.old/

希望有心的人好好利用这两个网站。

 

2019-03-09 23:18:34 weixin_31916083 阅读数 21

        打算从2019年开始深入学习操作系统,从2018年下半年开始又深入地回顾了数字电路、计算机组成原理、汇编语言《王爽老师版》,X86汇编语言-从实模式到保护模式《李忠老师版》;经过了2018年下半年的深入学习,对计算机硬件组织有了更加深入的认识,为2019年深入学习操作系统奠定了坚实的基础。

        2019年刚开始学习操作系统的时候有点不知所措,主要是不知道从哪里下手。起初的想法是先把三本经典的操作系统理论书籍《现代操作系统》,《操作系统概念》,《操作系统精髓与设计原理》并行着看完,再去深入研究Linux。而深入研究Linux则从《Linux内核设计与实现》,《深入理解Linux内核》入手研读,动手部分则按照南京大学版《Linux操作系统实验教程》来进行。计划就照着这样子来进行。

        三本操作系统经典理论书籍的第一章操作系统绪论部分由于自己已经有了比较深入的硬件理论基础,所以看起来不是很费力。在讲操作系统历史的时候也看的津津有味,对操作系统有了大概的了解。阅读三本书的时候对这三本书也有了更深刻的认识,《现代操作系统》讲的有点太抽象,感觉讲的有点不着边际。《操作系统概念》涉猎的面虽然广,但是感觉好多东西讲的都是蜻蜓点水,比如当我看到并发那一章节的时候,书上讲在进入临近区的前面有一段进入区代码,进入区代码要保证原子性。书上只是淡淡地说单CPU靠关中断来保证原子性,多CPU则靠硬件机制来保证,而怎么保证书上只是说要看计算机体系结构相关的书籍。所以到这里我就有点看不下去了,想把这个怎么保证原子性的办法查出来。此时我暂停下来去研究这个问题,想着去《Linux内核设计与实现》,《深入理解Linux内核》里面寻找答案。《Linux内核设计与实现》对于可能产出临界区的情况分析的比较到位,打破了我之前的一贯印象。比如我们通常只认为临界区是由多线程并发访问造成的竞争条件,也即只有不同的线程才能造成竞争条件。但其实不仅仅是多线程可以造成竞争条件,另外的比如中断,系统调用也可以产生竞争条件。比如当前线程在访问一个对象,此时该线程发起一个系统调用,内核代码也可以去访问该对象;线程正在执行的过程中硬件/CPU产生了中断,中断处理函数也可以访问该对象,所以这里让我觉得临近区真的不是仅仅只有并发会产生竞争条件。但是《Linux内核设计与实现》一书中也仅仅是说在单CPU通过禁止中断来保证原子性,而在多CPU中通过锁总线的方式来禁止其他CPU上面的线程来访问该临界区,但是总体上讲的还是太浅显。此时我又去阅读《深入理解Linux内核》,看到第二章内存寻址的时候后面有些东西讲的东西就有点理解不了了,看不下去了。

        欲速则不达,又印证了我之前的结论,之前我在看X86汇编语言-从实模式到保护模式就犯了这个毛病,导致重复看了好多遍才看懂。所以还是要从最最基本的操作系统看起。这里推荐南京大学软件学院的课程网站,上面非常好, 链接如下:http://219.219.120.71/CourseManager/showAll.jspa其中有个《Linux课程实践》课程简介:http://219.219.120.71/CourseManager/curriculumExportHtml.jspa?curriculumId=85;里面列出了Linux操作系统经典书籍,这里我根据豆瓣的评分挑选了最最浅显易懂的《Linux内核完全剖析》来看,虽然网上说毛德操老师的《Linux内核源代码情景分析》写得也挺好,但是我觉得还是要从最最基本的开始学起。同时网上说哈工大李治军老师的网易公开课《操作系统》挺好,然后我就加入了课程学习,视频讲解对照着《Linux内核完全剖析》一起学习,现在感觉效果非常好,已经学完了操作系统概论章节,这里把我感觉毛塞顿开的地方总结出来:

        一,李治军老师对于操作系统启动的讲解

               操作系统启动过程如下,计算机一加电或者一复位,硬件固定将CS:IP指向内存0xfff0处,此处是BIOS程序的顶端,此处是一条跳转指令,跳转到BIOS程序的起始端开始执行BIOS程序。BIOS是内存中的ROM区,是固化到内存中的,属于非易失性存储器。BIOS所做的主要内容如下:

               1.1,硬件检测,内存检测

               1.2,在内存0KB-1KB区间范围内建立中断向量表,包含256个中断向量;中断处理程序位于BIOS中

               1.3,将磁盘主引导扇区代码加载到内存0x7c00处,并用一条跳转指令跳转到内存0x7c00处执行;

          磁盘主引导扇区(bootsect.s)的主要工作如下:

                1.4,将磁盘2,3,4扇区的代码加载到内存中

                1.5,将内核代码加载到内存中

                1.6,用一条跳转指令跳转到磁盘2,3,4扇区的起始地址开始执行;

                这里补充一下磁盘主引导扇区源代码位于/linux/boot/bootsect.s中;磁盘2,3,4扇区的源代码位于/linux/boot/setup.s中;内核源代码位于/linux/kernel/目录中,另外/linux/boot/head.s也是内核源代码的一部分;

                 linux 0.12内核源代码链接为:http://oldlinux.org/Linux.old/Linux-0.11/sources/system/

 

           磁盘2,3,4扇区(setup.s)的主要工作如下:

                  1.7,读取机器的硬件配置参数,将内核代码system挪动到内存的适当位置处

 

           head.s的主要工作如下:   

                   1.8,被编译链接到system代码的最前面部分

                   1.9,硬件设备的探测设置

                   2.0,内存管理的初始化工作

 

        二,李治军老师对于命令行程序的讲解

               命令行可以理解为用户对于操作系统的访问,即对系统调用的访问;比如我们在命令行中输入 ./output helloworld

实际上output是一个C语言编译过后的有main函数的可执行文件,该可执行文件中调用C库函数printf来在控制台上显示输出。

而具体命令行程序是怎么做的呢?也即为啥会有提示用户一直输入,用户输入了再执行用户输入的命令。这其实就是shell程序,shell程序本质上是一个进程,该进程是由操作系统启动阶段最后的时候开启的一个进程,该进程永远不会退出。该进程本质上是一个永不停止的while循环,在while循环内部等待用户输入,用户输入之后就解释用户输入的命令,调用相应的C语言main函数执行之。                

 

        三,李治军老师对于图形用户界面/按钮的讲解

        四,Linux 0.12内核体系结构

        五,Linux 0.12源代码结构

       

2014-05-04 18:06:11 u011368821 阅读数 2138

我×。。。终于好了,大概3 4个小时吧。。。各种毛刺问题。终究还是闯过来了。。。。

ubuntu2@ubuntu:~/Downloads/linux-0.00-050613/linux-0.00$ make

ld -s -x -M head.o  -o system > System.map
dd bs=32 if=boot of=Image skip=1
16+0 records in
16+0 records out
512 bytes (512 B) copied, 0.000605945 s, 845 kB/s
dd bs=512 if=system of=Image skip=2 seek=1
14+1 records in
14+1 records out
7632 bytes (7.6 kB) copied, 0.000221138 s, 34.5 MB/s

sync


去这里下载 linux -0.00-041217.zip或者050613的zip都行

http://oldlinux.org/Linux.old/bochs/


不算最痛苦的,遇到很多问题,还好有前人帮忙,希望我的blog也能帮到别人。

编译过程中遇到的很多问题,下面的up都有遇到,并给出的详细解答。。。。好人啊。。。致敬

http://chfj007.blog.163.com/blog/static/173145044201132523034138/


 head.s:148: Error: invalid instruction suffix for `pop' head.s:149: Error: operand type mismatch  

会出现上面这种很多suffix前缀不正确的指令错误提示。解决问题的办法很简单,也就是32位汇编在64位的机子上跑要解决的问题

在相对应的汇编代码的开头加个

.code32

OK



同样,会遇到

head.s:105: Warning: using `%ebx' instead of `%bx' due to `l' suffix

这个把相应的bx寄存器改成ebx就OK

head.s:248: Error: `%al' not allowed with `movl'
head.s:257: Error: `%al' not allowed with `movl'
这个把movl改成movb,这里应该是一个错误,而不是一个平台差异照成的

因为我觉得al是8bit的movl是16bit的,即使在32位平台上也是不对的


Over。  还是很舒坦的。。。平安无事的编译成功了






时间就这样慢慢的流淌,当我们还是流动的长河,当我们变成静静的湖泊


2015-06-28 08:07:00 weixin_33935777 阅读数 6

Linux 0.12 内核管理存储器


分段,用分段的机制把进程间的虚拟地址分隔开。

每一个进程都有一张段表LDT。整个系统有一张GDT表。且整个系统仅仅有一个总页表。


 

地址翻译过程为:

程序中给出的32位地址(实际上被看做段内偏移地址),再依据代码段寄存器CS中的16位段选择子,可在GDT或LDT中查找对应的段描写叙述符。从段描写叙述符中提取段的基地址,与程序给出的32位地址相加。得到结果为线性地址

依据此线性地址查找系统页文件夹表,再查二级或是多级页表,终于得到物理地址。

 

此方式系统仅仅有一个4G的线性地址空间由各进程共享(各个进程共享一套页表)。(32位系统一个虚拟段的最大长度,理论上为4G)

Linux 0.12内核人工定义的最大任务数为64个。每一个任务的逻辑地址范围是64MB,故所有任务所使用的线性地址空间范围是64MB*64=4GB。

(即限定一张LDT表全部项的管辖范围是64MB。理论上其一项的管辖范围就是4GB)

 

内存寻址


(8086为了能寻址1M的空间,设计了称为段(Segment)的寻址技术。因为后来CPU的发展,这样的寻址技术实际上是不必要的,但为了保持兼容。80x86也用了段寻址技术。)

 

段地址部分使用16位的段选择子指定,当中14位能够选择。即16384个段。

(在保护模式下。段寄存器中的值为段表中的段选择子。在实模式下。段寄存器中的值为段表基址)

段内偏移地址使用32位的值来指定,因此段内地址能够是0~4GB。

即一个段的最大长度可达4G。

程序中由16位的段和32位的偏移构成的48位地址或长指针称为一个逻辑地址(虚拟地址)

 

80x86为段部分提供了6个存放段选择子的段寄存器:CS、DS、ES、SS、FS和GS。

当中。CS总是用于寻址代码段。而堆栈段则专门使用SS段寄存器。

CS寻址的段称为当前代码段。此时EIP寄存器中包括了当前代码段内下一条要运行指令的段内偏移地址。因此要运行指令的地址可表示成CS:[EIP]。

SS寻址的段称为当前堆栈段。栈顶由ESP寄存器内容指定。

因此堆栈顶处地址是SS:[ESP]。

另外4个段寄存器是通用段寄存器。当指令中没有指定所操作数据的段时。那么DS将是默认的数据段寄存器。

 


地址变换


CPU的内存管理给程序猿提供了这样一个抽象的内存模型:


即程序猿(不管是汇编的还是高级语言的)能够把内存分布看做是如上图所看到的,能够觉得内存中仅仅有自己的程序,自己独占CPU。

这是硬件和操作系统一起提供给程序猿的简单抽象。

(底层的实现:地址变换、任务切换等对程序猿是透明的)

 

程序(不管是汇编的还是高级语言的)中的地址是由两部分构成的逻辑地址。

这样的逻辑地址并不能直接用于訪问物理内存,而须要使用地址变换机制将它变换或映射到物理内存地址上。

内存管理机制即用于将这样的逻辑地址转换成物理内存地址。

 

80x86在从逻辑地址到物理地址变换过程中使用了分段和分页两种机制。

第一阶段使用分段机制,把程序的逻辑地址变换成处理器可寻址的内存空间(称为线性地址空间)

第二阶段使用分页机制。把线性地址转换为物理地址

第一阶段的分段变换总是使用的。而第二阶段的分页机制则是供选用的。若没有启用分页机制,那么分段机制产生的线性地址空间就直接映射到处理器的物理地址空间上。




物理地址空间定义为处理器在其地址总线上可以产生的地址范围。

(主板提供的统一编址)

 


分段机制


分段隔绝了各个代码、数据和堆栈区域的机制。为了定位指定段中的一个字节,程序必须提供一个逻辑地址。逻辑地址包含一个段选择子和一个偏移量。

(程序提供48位的逻辑地址,但指针长度仅仅有32位。在程序载入时,就已经把程序中各段的段选择子载入到了对应的段寄存器中了,程序中所用的指针值是32位的段偏移地址)

 

段选择子是一个段的唯一标识。

另外,段选择子提供了段表一个段表项的偏移量。一个段表项中含有:段大小、訪问权限、段类型及段的基地址信息。逻辑地址中的偏移量加上段基地址就形成了处理器线性地址空间中的地址

 

线性地址空间与物理地址空间具有同样的结构。相对于二维的逻辑地址空间来说,它们都是一维地址空间。

虚拟地址(逻辑地址)空间可包括最多16K个段,而每一个段最长可达4GB,使得虚拟地址空间达到64TB。

线性地址空物理地址空间都是4GB。

实际上,假设禁用分页机制,那么线性地址空间就是物理地址空间。

 


段描写叙述符表


段描写叙述符表(段表)是段描写叙述符的一个数组。段表的长度可变。最多能够包括8192个(2^13)8字节描写叙述符。

有两种段描写叙述符表:

全局描写叙述符表GDT(Global Descriptor Table)和局部描写叙述符表LDT(LocalDescriptor Table)

 

段表存储在由操作系统维护着的受保护的内存区域中,而且由CPU的内存管理硬件(MMU)来引用。(用于地址翻译)

 

虚拟地址空间被切割成大小相等的两半。整个虚拟地址空间共含有2^14个段:一半空间(即2^13个段)是由GDT映射的全局虚拟地址空间,还有一半是由LDT映射的局部虚拟地址空间。

LDT段表中的段是一个任务自己的段(代码段、数据段等)

GDT段表中的段是系统中全部任务共同拥有的(操作系统代码段等),还有系统中全部任务的LDT段表段(任务的LDT段表存储在一个段中)

当某任务在执行时,可訪问的段包含自己LDT段表中的段和GDT中操作系统的段。它们组成了此任务的虚拟地址空间。

这样,通过让每一个任务使用不同的LDT,当任务A在执行时,任务B的段不是虚拟地址空间的部分。因此任务A没有办法訪问任务B的内存。

 

GDT本身并非一个段,而是线性地址空间中的一个数据结构(故其不须要经过段机制地址翻译,GDTR中不须要段选择子)。

GDT的基线性地址和长度值必须载入进GDTR寄存器中。

LDT表存放在LDT类型的系统段中。此时GDT必须含有LDT的段描写叙述符。

 


【内存管理寄存器】


处理器提供了4个内存管理寄存器(GDTRLDTRIDTRTR)。用于指定内存分段管理所用系统表的基地址。(处理器为这些寄存器的载入和保存提供了特定的指令)




1、全局描写叙述符表寄存器GDTR

为了记录一个段,须要有下面信息:段的大小、段的基地址、段的属性。

CPU用8个字节(64位)的数据来表示这些信息。

故段表最大为8192*8=65536字节(64KB)。

GDT(global (segment) descriptor table)即全局段号记录表。此段表的起始地址和表长度放在被称为GDTR的特殊寄存器中了。

在机器刚加电或处理器复位后,基地址被默认地设置为0。而长度值被设置成0xFFFF。

在保护模式初始化过程中,必须给GDTR载入一个新值


2、局部描写叙述符表寄存器LDTR

LDT表是当前进程的段的段表。

包括LDT表的段必须在GDT表中有一个段描写叙述符项。

当进行任务切换时,处理器会把新任务LDT的段选择子和段描写叙述符自己主动地载入进LDTR中。


3、中断描写叙述符表寄存器IDTR

与GDTR的作用类似,IDTR寄存器用于存放中断记录表IDT的32位线性基地址和16位表长度值。

相同。在机器刚加电或处理器复位后,基地址被默认地设置为0,而长度值被设置成0xFFFF。在保护模式初始化过程中。必须给IDTR载入一个新值。


4、任务寄存器TR

TR用于寻址一个特殊的任务状态段(Task State Segment,TSS)。TSS中包括着当前运行任务的重要信息。TR寄存器中的段选择子用于索引GDT表中的TSS类型的段。

当运行任务切换时,CPU会把新任务的TSS的段选择子和段描写叙述符自己主动载入进任务寄存器TR中。

(LDT和TSS都存储在段中,且这些段记录在GDT段表中,故LDTR寄存器和TR寄存器仅仅有16位,用于保存段选择子)

 


段选择子


段选择子(或称段选择符)是段的一个16位标识符(可理解为段号)。段选择子并不直接指向段。而是指向段描写叙述符表中定义段的段描写叙述符。


请求特权级字段RPL提供了段保护信息。

表索引字段TI用来指出包括指定的是哪个段表。TI=0表示此为GDT表的段选择子,TI=1表示此为LDT表的段选择子。

 

相应用程序来说段选择子是作为指针变量的一部分而可见的。但选择子的值一般是由链接器或载入器进行设置或改动,而非应用程序。

 


段寄存器


处理器提供6个存放段选择子的寄存器(即段寄存器)

每一个段寄存器支持特定类型的内存引用(代码、数据或堆栈)

运行每一个程序都须要至少把有效的段选择子载入到代码段(CS)、数据段(DS)和堆栈段(SS)寄存器中

处理器还另外提供3个辅助的数据段寄存器(ES、FS、GS),以便当前运行程序可以訪问其它几个数据段。

(各段寄存器有不同的作用。用于訪问不同的段。各司其职。

 

●在32位模式下。段寄存器仍然是16位。

因为CPU设计上的原因,段寄存器的低3位不能使用。因此可以使用的段号仅仅有13位。即最大段号为8191。




对于訪问某个段的程序,必须已经把段选择子载入到一个段寄存器中。

(汇编程序是分段编写的,在其执行前各段的选择子就已经被默默地载入到了段寄存器中了,进行长跳转指令,从一个段跳转到还有一个段中。这个跳转指令会把新段的选择子载入到寄存器中)

 

每一个段寄存器都有一个“可见”部分和一个“隐藏”部分。隐藏部分用于段描写叙述符缓存(页表的缓存是TLB)

当一个段选择子被载入到一个段寄存器可见部分中时,CPU也同一时候把段选择子指向的段描写叙述符载入到段寄存器的隐藏部分中。

缓存在段寄存器(可见部分和隐藏部分)中的信息使得CPU能够在进行地址转换时不再须要花费时间从段描写叙述符中读取基地址和限长值

 


段描写叙述符


段描写叙述符是GDT和LDT表中的一个数据结构项,用于向CPU提供有关一个段的位置和大小信息以及訪问控制的状态信息。

每一个段描写叙述符的长度是8字节,含有:段基地址、段限长、段属性(段类型、訪问控制、特权级等)



 


分页机制


分段机制把逻辑地址转换成线性地址。而分页则把线性地址转换成物理地址

与分段机制不同。分页机制对固定大小的内存块(页面)进行操作。分页机制把线性和物理地址空间都划分成页面

线性地址空间中的不论什么页面能够被映射到物理地址空间的不论什么页面上。




80x86使用4K(2^12)字节固定大小的页面。

因此。线性地址的低12为页内偏移量(段内偏移量为32位)。直接作为物理地址的低12位。

分页机制可看做就是把线性地址的高20位页号 转换到相应物理地址的高20位

 


页表结构


页表(page table)可看做简单的2^20个物理地址数组,线性地址的高20位(可看做虚拟页号)构成这个数组的索引值,用于选择相应页面的物理基址。

线性地址的低12位给出了页面中的偏移量,加上页面的基地址终于形成相应的物理地址。

 

页表中每一个页表项的大小为32位。因为仅仅须要当中的20位来存放页面的物理基址,因此剩下的12位可用于存放页面属性信息。

(假设页表项中信息表明页不存在,那么当訪问相应物理页面时就会产生一个异常)

(段表项大小为64位,当中仅仅有32位为段基地址。其它为段属性)

 

1、两级页表结构

页表中含有2^20(1M)个表项。而每项占用4字节。假设作为一个表来存放的话,它们最多将占用4M内存。

为了降低内存占用量,80x86使用了两级页表。

由此。高20位线性地址到物理地址的转换也被分成两步进行。每步转换当中的10bit。

第一级表称为页文件夹(pagedirectory)。具有2^10(1K)个4字节的内存。

这些表项指向相应的二级表。

第二级表称为页表(pagetable)。

最多含有1K个4B的表项。二级页表使用线性地址中间10位作为表项索引值,以获取含有页面的20位物理基地址的表项。

这样,一个文件夹项就“管辖”1024个页。



 

2、不存在的页表

二级页表结构同意页表被分散在内存各个页面中,而不须要保存在连续的4MB内存块中。(由于每个二级页表都是4K大小,正好放在一个页中)

且。并不须要为不存在的或线性地址空间未使用部分分配二级页表。(一级页表把4G线性空间中切割的全部页都记录了)

文件夹表项中每一个表项有一个存在属性,可用于在虚拟内存中存放二级页表。这意味着仅仅有部分二级页表须要存放在主存中,其余的能够保存在磁盘上。

 


页表项格式


文件夹项和页表项的格式,例如以下图。当中位31~12含有物理地址的高20位。用于定位物理地址空间中一个页面的物理基地址。表项的低12位含有页属性信息。



P位:存在标志。

P=1表示有效,P=0表示无效。假设P=0。那么其余位可供程序自由使用,比如操作系统能够使用这些位来保存已存储在磁盘上的页面的序号。

R/W位:读/写标志

U/S位:用户/超级用户标志

A位:已訪问(Accessed)标志。

当CPU訪问页表项映射的页面时,页表表项的此标志被置1。

CPU仅仅负责设置该标志,操作系统可通过定期地复位该标志来统计页面的使用情况。

D位:已被改动(Dirty)标志。

当CPU对一个页面运行写操作时,就会置此位。

 


虚拟存储


在保护模式中,80x86同意线性地址空间直接映射到大容量的主存中,或者间接地映射到小容量的物理内存和磁盘中。此方法被称为虚拟存储

文件夹项和页表项中的存在标志P为使用分页技术的虚拟存储提供了必要的支持

页面不在物理内存中的表项其标志P=0。

假设程序中訪问主存中不存在的页面。CPU就会产生一个缺页异常。此异常中断处理程序让操作系统把对应页面载入到主存中。并设置P=1。当页面载入到主存中之后,从异常处理过程的返回操作会使得导致异常的指令被又一次运行。

 

 

 

 







2017-06-14 12:05:00 weixin_34112208 阅读数 7

我×。。

最终好了,大概3 4个小时吧。。。各种毛刺问题。终究还是闯过来了。。

。。

ubuntu2@ubuntu:~/Downloads/linux-0.00-050613/linux-0.00$ make

ld -s -x -M head.o  -o system > System.map
dd bs=32 if=boot of=Image skip=1
16+0 records in
16+0 records out
512 bytes (512 B) copied, 0.000605945 s, 845 kB/s
dd bs=512 if=system of=Image skip=2 seek=1
14+1 records in
14+1 records out
7632 bytes (7.6 kB) copied, 0.000221138 s, 34.5 MB/s

sync


去这里下载 linux -0.00-041217.zip或者050613的zip都行

http://oldlinux.org/Linux.old/bochs/


不算最痛苦的。遇到非常多问题。还好有前人帮忙,希望我的blog也能帮到别人。

编译过程中遇到的非常多问题。以下的up都有遇到,并给出的具体解答。。。。好人啊。。

。致敬

http://chfj007.blog.163.com/blog/static/173145044201132523034138/


 head.s:148: Error: invalid instruction suffix for `pop' head.s:149: Error: operand type mismatch  

会出现上面这样的非常多suffix前缀不对的指令错误提示。解决这个问题的办法非常easy。也就是32位汇编在64位的机子上跑要解决的问题

在相相应的汇编代码的开头加个

.code32

OK



相同。会遇到

head.s:105: Warning: using `%ebx' instead of `%bx' due to `l' suffix

这个把对应的bx寄存器改成ebx就OK

head.s:248: Error: `%al' not allowed with `movl'
head.s:257: Error: `%al' not allowed with `movl'
这个把movl改成movb。这里应该是一个错误。而不是一个平台差异照成的

由于我认为al是8bit的movl是16bit的。即使在32位平台上也是不正确的


Over。  还是非常舒坦的。。。平安无事的编译成功了






时间就这样慢慢的流淌,当我们还是流动的长河,当我们变成静静的湖泊


为什么80%的码农都做不了架构师?>>>...

博文 来自: weixin_33704591

P287P288P289P291P304

博文 来自: Lord_sh
没有更多推荐了,返回首页