精华内容
下载资源
问答
  • !DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>星期一菜谱<...table border="1px" cellspacing="0" align="center" bgcolor="#FFFF00">...
  • 一个简单的操作系统

    万次阅读 多人点赞 2017-03-20 15:30:52
    如果一定要找出OS最重要的核心,那就是调度器,调度器本身即可以看作一个简单的操作系统,允许以周期性或单次方式来调用任务。从底层的角度看,调度器可以看作是一个由许多不同任务共享的定时器中断服务程序,因此,...

    摘 要

    如果一定要找出OS最重要的核心,那就是调度器,调度器本身即可以看作一个简单的操作系统,允许以周期性或单次方式来调用任务。从底层的角度看,调度器可以看作是一个由许多不同任务共享的定时器中断服务程序,因此,只需要初始化一个定时器,而且改变定时的时候通常只需要改变一个函数。此外,无论需要运行1个,10个还是多个不同的任务,通常都可以使用同一个调度器完成。随着计算机的发展,我们比较容易能够使用高级程序语言来实现调度器。那么使用一个低级程序语言像汇编语言能不能实现一个调度器。我们将要基于Bochs, 做一个小OS试一试。

     

    特别说明

    该项目是学习期间作为OS课程大作业而开发的,功能没有太多花样。本篇博客基于当时提交的报告更改而来。

     

    技术文档PDF版:浏览链接

     

    完整代码

    在此下载 

     

    先行知识

    1.1 虚拟计算机Bochs

    即便没有听说过虚拟计算机,你至少应该听说过磁盘映像。如果经历过DOS时代,你可以就曾经用HD-COPY把一张软盘做成一个.IMG文件,或者把一个.IMG文件恢复成一张软盘。简单来讲,它相当于运行在计算机的小计算机。在介绍Bochs及其他工具之前,需要说明一点,这些工具并不是不可或缺的,介绍它们仅仅是为了提供一些可供选择的方法,用以搭建自己的工作环境。但是,这并不代表这一章就不重要,因为得心应手的工具不但可以愉悦身心,并且可以起到让工作事半功倍的功效。下面就从Bochs开始介绍。

    我们先来看看Bochs是什么样子的,请看图1.1这一个屏幕截图。窗口的标题栏一行"Bochs x86 emulator"明白无误地告诉我们,这仅仅是个"emulator"——模拟器而已。在本文中我们把这种模拟器成为虚拟机,因为这个词使用得更广泛一些。不管是模拟还是虚拟,我们要的就是它,因为有了它我们不再需要频繁地重启计算机,即便程序有严重的问题,也丝毫伤害不到你的计算机。更加方便的是,可以用这个虚拟机来进行操作系统的调式。

     

    图1.1,Linux中的Bochs

     

    1.2 Bochs的安装

    就像大部分软件一样,在不同的操作系统里面安装Bochs的过程是不同的,在Window中,最方便的方法就是从Bochs的官方网站获取安装程序来安装(安装时不妨将"DLX Linux Demo"选中,这样你可以参考它的配置文件)。在Linux中,不同的发行版(distribution)处理方法可能不同。比如,如果你用的是Debian GNU/Linux或其近亲(Ubuntu),可以使用这样的命令:

    sudo apt-get install vgabios bochs bochs-x bximage

    敲入这样一行命令,不一会儿就装好了。

    很多Linux发行版都有自己的包管理机制,不如上面这行命令就使用了Debian的包管理命令,不过这样安装虽然省事,但有个缺点不得不说,就是默认安装的Bochs很可能是没有调式功能的,这显然不能满足我们的需要,所以最好的方法还是从源代码安装,源代码同样位于Bochs的官方网站,假设你下载的版本是2.3.5,那么安装过程差不多如下:

    tar vxzf bochs-2.3.5.tar.gz

    cd bochs-2.3.5

    ./configure –-enable-debuger –-enable-disasm

    make

    sudo make install

    注意"./configure"之后的参数便是打开调式功能的开关。在安装过程中,如果遇到任何困难,不要惊慌,其官方网站有详细的安装说明。

    1.3 Bochs的使用

    上面有提到软盘,那么软盘究竟是什么?既然计算机都可以"虚拟",软盘当然也可以。在刚刚装好的Bochs组件中,就有一个工具叫做bximage,它不但可以生成虚拟软盘,还能生成虚拟硬盘,我们也称它们为磁盘映像。创建一个软盘映像的过程如图1.2所示:

    图1.2,bximage,创建一个软盘映像

     

    在上面只有一个地方没有使用默认值,就是被问到创建硬盘还是软盘映像的时候,就输入了"fd"。

    完成这一步骤之后,当前目录下就多了一个a.img,这便是软盘映像了。所谓映像者可以理解为原始设备的逐字节复制,也就是说,软盘的第M个字节对应映像文件的第M个字节。

    现在我们已经有了"计算机",也有了"软盘",是时候将引导扇区写进软盘了。可以使用dd命令:

    dd if=boot.bin of=a.img bs=512 count=1 conv=notrunc

    注意这里多用了一个参数"conv=notrunc",如果不用它的话软盘映像文件a.img会被截断(truncated),因为boot.bin比a.img要小。

    现在一切准备就绪,只差一个Bochs的配置文件。为什么要有配置文件呢?因为我们需要告诉Bochs,希望我们的虚拟机是什么样子的。比如,内存多大、硬盘映像和软盘映像都是哪些文件等内容。图1.3就是一个Linux下的典型配置文件例子。

     

    图1.3,bochsrc示例

     

    可以看到,这个配置文件本来就不长,除去注释之后内容更少了,而且很容易理解,字面上稍微不容易理解的只有romimage和vgaromimage,它们指定的文件对应的其实就是机器的BIOS和VGA BIOS,操作时需要确保它们的路径是正确的,不然过一会儿虚拟机启动时可能会被提示"couldn't open ROM image file"。除了之外还要注意floppya一项,它指定我们使用哪个文件作为软盘映像。现在一切准备就绪,是时候启动了,输入命令:

    bochs –f bochsrc

    一个回车,结果就如图1.1所示。

     

    思路与代码

    不借助任何外部代码,不借助《30天教你从头写XXX》类书籍,不借助现有OS的工程思路,不借助网络上博客的"讲解",进行如下工作:

    1. 阅读英特尔CPU产品说明书,掌握CPU工作方式、指令的详尽功能;

    2. 阅读BIOS产品说明书,全面掌握BIOS的功能,掌握使用要点与调用方法;

    3. 阅读GAS软件产品说明书,全面掌握GAS的汇编语法与工程使用;

    4. 阅读NASM软件产品说明书,全面掌握NASM的汇编语法与工程使用;

    5. 阅读各平台ABI说明书,选择性地大致掌握主流二进制文件的结构;

    6. 编写能够实现输出的HELLOWORLD程序。

    7. 编写能对简单可重入客户程序进行调度的调度器。

    8. 编写2级加载的内核加载器。

    9. 编写能实现位置无关运行的2级内核加载器。

    10. 编写2级加载的C语言内核,并实现调度、维护,提供简单的系统调用与服务。

    11. 编写2级加载的保护模式下的调度器。

    如有雷同,绝非巧合。因为工程上的雷同大都因为框架的限制与规定。

     

    2.1 研究思路

    容易想出来的模式有两种:

    1,FIFO模式(管道调度)

    2,仲裁模式(事件调度)

    我们采用明显更为适合的仲裁模式。

    CPU上电基本环境为0x7c00开始运行,cs=0x0000 ip=0x7c00。内存结构如下图所示,共计1MB。A20地址线未打开,如果访存超1MB,则会被环回0地址。

     

    2.2 研究过程与实现详解

     

    实现打印与光标控制功能,直接覆盖到bios信息上面。首先展示我们研究过程中使用的编译方法,如下图所示。

     

     

    因为该项目对编译的需求比较复杂,总结起来包括:

    1. 调用不同的汇编编译器处理S源代码

    2. 处理并抽象汇编器的一大堆参数,保存多种汇编方案

    3. 读写镜像文件

    4. 打包与保存,简单的版本管理

    5. 处理C语言的编译生成、编译器参数

    6. 处理链接器的参数

    7. 处理ABI格式并进行重构拼接

    8. 保存、显示功能提示笔记和帮助

    第一个项目实现时,Makefile功能还很简单,只用了一小部分功能。在此实验之后的研究中,Makefile的功能逐渐被填充增加,以满足研发过程中出现过的乱七八糟的需求。

    图2.1,Hello,World的运行结果(红色)

     

    主要代码:

    首先将地址翻译的预处理指针定位到7c00位置,设置代码段与数据段相同,便于索引数据。

    VGABIOS将功能共享内存映射到显存上,主板BIOS再将显存映射到主存上,并根据VGA型号在内存中写入控制程序。这里利用的是10h号中断的2h偏移功能包装了一个DisplayString函数。

     

    2.3 可重入程序调度器

    这次编写的调度器尝试调度两个简单的打印程序,一个打印A,另一个打印B,两者交替运行。程序运行效果如图2.2所示。


    图2.2,调度两个简单的打印程序

     

    程序代码说明:

    基于上一份代码主要的更改就是"中断劫持"。我们的思路是通过时钟中断唤醒调度程序。修改中断服务程序的返回地址,使得中断结束后,返回到一个我们指定的位置。

    我们要劫持的中断号是1c,该中断是由08h中断触发的软中断,08h是由8259级联片触发的硬件中断,8259从晶体振荡钟获得时钟触发的电气脉冲。

    鉴于每个中断向量占4字节,其中低2子节为IP,高2子节为CS,我们将1ch乘4获得要修改的内存首地址,将该处内容改写为我们的调度器地址即可完成劫持。

     

    图2.3展示的是两个被调度的客户程序

    图2.3,两个被调度的客户程序

     

    之后,我们实现了调度器的逻辑,最初有两种考虑,分别为2状态调度与4状态调度,两种状态调度模式如下:

    1. 状态1:应该运行main1

    2. 状态2:应该运行main2

       

    3. 永远在1,2之间切换跳转。

    四种状态调度模式如下:

    1. 状态1:应该启动main1

    2. 状态2:main1被中断,启动main2

    3. 状态3:main2被中断,还原main1

    4. 状态4:main1被中断,还原main2

    5. 稳定在3,4之间切换跳转。

    我们最终选择了四种状态的调度逻辑,代码如下。

     

    当中断触发,进入调度器后,首先保护父过程的栈底地址ebp,将上一次的状态从内存读取到ax中。之后将ax与0-3比较,实现C语言中switch的功能。由于INTEL芯片中断的行为如下:

    1.查看int偏移值是否合法

    2.检查栈够6byte

    3.push(eflags[0:15]); 2bytes

    4.关中断,清trap,清AC

    5.push(CS) 2bytes

    6.push(IP) 2bytes

    故在状态的处理中,会将上次程序的flag、IP从栈底后16byte处读取出来,因为最初main被中断时,其地址保存在0x08中断的ebp+0 到 ebp+5的位置上,换算到第二次调用,就是6*2+size(ebp)=16。根据此地址,可以从此处取出上一次的状态,并写入想要的状态。

    比较完成后,将新的状态号写入ax,跳转到结束保存位置"savestate",该位置的代码会将ax写回内存,退出中断,返回到指定位置。

    同时,我们在这一阶段也实现了对CRT硬件的基本控制。首先是屏幕清除函数,代码如下。

     

    它是用屏幕翻滚中断来实现的,当翻滚为0时,则清屏,光标位置不变。这里我们发现用双头方式定义默认参数构造函数非常方便,算是汇编编程领悟到的一个小trick。这个Scroll功能行为其实比较不完善,因为其会破坏eax与ebp、ebx寄存器,所以必须要预先保存。之后我们从内存位置取出行列值,发送命令让int10-06写入显存。

     

    每次写入显存后,我们都要更新光标的位置,让它指向下一个字符块,如果到达行尾,则光标应该向下一行,然后回车;如果整个屏幕都已写满,则应该向上滚动屏幕,然后写在CRT最后一行,这样才能实现平时我们所熟悉的字符界面。函数cursor_deal如下。

    我们的CRT是标准设备,大小为80x25。初始光标在(0,0)位置,每次写完成后,我们都要计算光标下一个可写的坐标,并保存该坐标到内存。必要时,会滚动屏幕。

    下面的函数实现了打印单个字符到显示器的功能,通过修改参数,也可以实现打印多彩色字符。

     

     

    这里我们也使用了双头函数来包装默认功能与详细功能的不同入口。之后我们编写了光标设置函数。该函数接收行列参数来放置光标。代码如下所示。

     

     

    在实验的过程中,我们发现这个简单的调度器当陷入到我们自己编写的"系统调用"函数中时,如果被中断切出,则会因为已经使用的栈空间而造成内存泄漏。所以,我们在此确定了一个非抢断的实现方案,使得陷入API后进程不得被抢断。打印的服务函数如下。

     

     

     

    该函数在进入时关中断,离开时开中断,避免被调度而泄漏内存。

     

     

    2.4 2级位置无关的内核加载器

    因为BIOS的规定与限制,首次读盘只能加载0柱面0磁头1扇区512byte的内容,这对于一个稍微复杂一点的启动程序来说已是远远不够的,所以实现从其他存储介质中读取代码复制到内存中运行是不可避免的。因此,我们计划将bootloader写入软驱的第一个扇区,再编写一个小程序放到硬盘中等待被bootloader加载。

     

    我们曾想尝试直接从硬盘中读取第一扇区作为bootloader,读取之后的十个扇区作为mini-kernel,但是我们经历数次挫折后发现,由于bochs已知的BUG,从HD中读首扇区后再次读取时常会发生错误,而软盘则不会;另外考虑到我们应该实践练习一下对多种存储设备进行读写,所以最终选择了这种复杂的组合。

     

    这次编写的代码中,bootloader的主干逻辑如下所示。

     

     

     

    首先清空屏幕,读取mini-kernel到0x7e00位置,然后跳转到新加载的代码处运行。

     

    一般来说,编译代码时必须制定一个代码起始的默认地址,这个数值是必须指定的,用来给所有的label、call、jmp或内存访问编址。对于bootsection,我们根据BIOS说明书已知其会被复制到0x7c00位置,所以我们可以显式用org指定。但是对于客户程序或者可能被升级的自写代码来说,编写者并不知道自己的代码可能被bootloader/kernel加载到什么位置上去,所以操作系统必须支持把任意编写的代码正确运行的功能,也就是PIC/PIE(Position Independent Code/ Position Independent Executable)。

     

    我们根据阅读INTEL说明书地址翻译部分学习的知识,认为段地址+偏移值的机制很好的支持了这种特性,因为地址的翻译是基于eip计算的逻辑地址,也称偏移值。我们的设想是将代码加载到16B对齐的位置,改变CS值,使得地址翻译正确进行。这样,客户的程序只需要从0编址就可以了。

     

    这里的机制比较复杂,有一些误区,比如PIE机制可能被人误解为程序中不出现绝对的内存寻址,比如gcc的编译选项fpie。实际上,任何程序都不能避免使用绝对值来表示地址,例如C语言的函数指针,必须是关于CS的偏移值。如果CS不改变的话,翻译一定会出错。所以即使在gcc中选了fpie,也无法保证这代码就是理论上地址无关的。地址无关代码的支持是编译器与内核加载器紧密联系才能实现的特性。我们没有阅读gcc或linux的工程代码,不过我们猜想他们用的PIE机制应该和我们探索的极为相似。

     

    接下来我们将主体框架拆分讲解,首先是读取硬盘的代码。代码如下所示。

     

     

    之后,我们把打印相关的代码统统移到mini-kernel里,这使得bootloader的大小减为原先的二分之一。Mini-kernel的主干代码如下所示。

     

     

    该部分代码是依照段首0x00为准编译的,具有通用性,因为编译器生成的obj都是从0编址。代码在逻辑上首先关中断,然后不管之前的cs/ds如何。统一将代码段数据段扩展数据段都设置为代码段。然后调用mini-kernel里的清屏函数,如果位置无关工作正常的话,屏幕上的BIOS信息将被清除。紧接着调用光标设置与字符打印函数打印一个'C'字母,如果工作正常,就会在屏幕第一个字符块位置打印出该字符。该程序结果如图2.4。

     

    图2.4,调用光标设置与字符打印函数打印一个'C'字母

    可见我们设计的PIE机制工作正确。

     

     

    2.5 2级加载的C语言内核

    在这里我们的设计是让bootloader尽量小,装载完毕后直接跳转。而把中断处理、进程调度、系统服务等代码全都放进mini-kernel里面用C实现。这里的关键点有三个,分别是符号位置、代码格式、opcode格式 。

     

    符号位置指的是我们如何定位终端服务函数,这样才能在bootloader中劫持中断。因为在之后的工作中我们会需要将内核改为保护模式下执行,所以必须在bootloader中设置中断。这时我们就需要详细知晓交叉编译常常遇到的ABI格式转换问题也就是代码格式,同时,原ABI中有很多没有用的结构,比如图2.5所示的ELF格式中,头部有巨大的标志结构,数据稀疏度达到了98%。而bin格式则无稀疏数据。

     

     

     

    像上图这样的结构我们也应该按照ABI来剪裁,本次研究中使用的机器是darwin Mach-O 格式,该格式与linux系统采用的ELF(Executable and Linkable Format)格式类似,如图2.7所示。

     

    通过阅读苹果的ABI说明书,我们编写了工具解析该格式,以达到定位首函数、定位中端服务函数并裁减二进制文件的目的。最后我们遇到了opcode问题,为了代码的清晰、结构的明确,我们需要链接多个文件,bin格式虽然紧凑但无法链接,所以不能使用bin格式,而在使用其他可链接格式的过程中,gcc与unix-cc编译C语言代码时虽然指定了相应的参数,生成了i386代码,但是却无法在机器上正确运行。我们通过反汇编发现,指令中立即数部分被延长了,如图2.8所示。

     

    图2.8,反汇编的结果

     

    因为intel指令集是变长指令,上述地址的翻译错误导致了后续代码空间顺序紊乱。我们阅读gcc与cc的说明书后,根据其声称的编写boot代码时所需设置再次进行试验,发现没有任何效果,代码依然不是i386-generic,而是i386-long模式。经过一系列的失败和探索,我们发现只能将编译过程手动分解为前端、汇编、链接、后处理,四步操作,并在其中采用大量的参数、配合自写脚本,才能使得cc\gas\ld输出正确的结果。我们最终探索得到的步骤可在Makefile中体现。简而言之抽取其中四条命令和部分参数,其步骤可大致理解为:

    1. nasm –f bin

    2. cc -m16 $(csrc) -S -O0 -fPIC –ffreestanding

    3. Asm(".code16 \n\t");    

    4. as -arch i386 $(target).s -o $(target).o -O0

    编译的过程输出如图2.9所示。

     

    图2.9,编译过程

     

    相比之前几次探究,这次bootloader的主要改变是放弃劫持1ch,转而劫持其父硬件中断08h,绑定到mini-kernel中的int08函数上。代码如下。

     

     

    另一处不同,是对堆栈的处理,之前我们一直让堆栈处于BIOS执行完的默认状态,但是我们发现,如果在远跳转后依然保留堆栈为0000:ffff尺度的话,会因为字符串类操作与c语言传参操作占堆栈大而溢出到0x7e00以上的代码空间,所以我们在这里重新设置了对战的大小,代码如下。

    下面将介绍说明C语言实现的mini-kernel。首先是文件头,如下所示。

    这里定义了C语言一些默认的符号,定义了进程池的大小,TTY尺寸,与进程的状态标志。之后我们定义了进程描述符,结构如下所示。

     

     

     

    进程描述符内主要存储了寄存器信息、id、进程状态、进程主函数与堆栈保留空间。Bar变量的作用是检测与防止堆栈读写问题导致溢出,使得系统能对该异常做出反应。

     

     

    主要的示例过程与工具函数如下所示。

     

     

    功能分别为:中断伺服、系统idle零号进程、客户进程、清屏调用、光标调用、字符串求长、非安全栈显示字符、安全显示字符、打印字符串、自旋睡眠、获得即时esp、开始调度API、加入任务、任务切换、非栈安全设置光标。除此外还有许多辅助函数,细节繁杂就不一一解释了,只挑出其中比较重要的任务切换相关代码说明。

     

     

     

    主函数设置好段,清理堆栈,然后加入两个进程开始调度。如以上的代码。

     

     

     

    以上代码是中断处理函数,首先将当前的通用寄存器都压栈,再将堆栈指针都压栈,然后压入cs、ip后调用保存上下文函数,上下文保存完毕后,清理堆栈保证无任何泄漏,再进入进程切换。下面首先展示上下文保护函数,如下。

     

     

     

     

     

    该函数依据gcc说明书所描述的C语言标准传参过程,从右到左依次弹出,获得参数,保存到任务描述符中,并调用MOVSB指令保存当前任务从栈底到栈顶的所有信息到任务描述符中。

     

    该函数执行完毕后,int08将会调用switch_proc函数。该函数上半部分形式如下面的代码。

     

     

     

    首先switch_proc会选择一个程序来调度,在此处可以实现任意的调度算法或逻辑,本次实验中采取了便于解释2个进程关系的处理方式,即:只有idle进程时启动另外一个ready进程,idle进入paused状态,当两个进程都已启动时,进行公平时间片调度,被切换掉的进程设置为paused,新运行的进程设置为running。

     

    紧接着是通用的处理过程,主要关注如何恢复现场。首先检测被还原的进程所占堆栈式否超过了128Btye上限,如果超出了就截断,否则继续。这是为了防止客户在编写程序时出现错误而无限制地毁坏堆栈,伤及代码;另一方面也是为了限制管理开销。同时,我们也可以看到,为了防止调度嵌套,我们只在控制转移之前向20h端口发送"看门狗"喂狗信号,允许再次产生中断,这个做法在后面一半代码中也有体现。

     

    接下来展示的是switch_proc的后半部分代码。

     

    这部分代码的作用是将保存在任务描述符中的寄存器、堆栈指针恢复到对应硬件,再将堆栈写回内存,最后恢复标志位寄存器、恢复cs、ip。

     

    因为我们要恢复所有寄存器,而总是需要额外寄存器作为中转器,这就像玩华容道一样比较难搞,所以我们将要恢复的内容依次写到堆栈里,然后逐个弹出。注意,这个堆栈的位置不是随意指定的,否则客户程序栈内存恢复的过程中会破坏我们的寄存器临时栈,所以我们将这个位置显式地写在旧esp的后面。旧的esp存在esi中。

     

    首先,我们利用嵌入式汇编从任务描述符中恢复ip、flags、edi、esi,然后恢复eax/ebx/ecx/edx,然后取出ebp。此时寄存器都已经在esp栈顶存放了,我们再依次将数据复制到旧的程序栈中。接着,将各个寄存器的值通过pop重新赋给寄存器,完成恢复。

     

    在喂"看门狗"开中断后,我们将返回ip、cs、flag写到栈顶,利用正常的retl跳转到原程序运行的位置,至此完成调度。这种完全保存栈行为的切换方式能够调度任何程序,包括线性时无关或任意时相关程序(也可理解为可重入、不可重入程序)。此处有趣的一点是,int08函数进入switch_proc函数后是不会返回的,int08函数也不会返回,因为我们模拟了正常时间中断的行为后,伪造了一个正常函数调用的现场利用retl跳转到其他位置去了。

     

     

    Idle程序打印字母"I",task程序打印字母"O",实现效果如图2.10所示。

     

     

     

    图2.10,实现效果

     

    至此成功进行了bootloader加载、minikernel加载、调度、保存、切换等一系列事件驱动调度器的功能。

     

     

    总结

    本次实验已经成功地,基于Bochs在两个不同模式下分别是8086的实模式和80386的保护模式下,实现了两个简单任务的调度器。在实现过程中有对比较底层的内核结构和使用进行详细的描述。因为本次实验对编译的需求比较复杂,所以我们除了实现汇编的编写还有编写了C语言,两个语言结合地使用就是为了读者更加容易理解。

     

    参考

    [X86 WIKIBOOK]https://en.wikibooks.org/wiki/X86_Assembly

    [IntelAsm]http://www.logix.cz/michal/doc/i386

    [NASM备忘lmu]http://cs.lmu.edu/~ray/notes/nasmtutorial/ 

    [Nasm Manuall]http://www.nasm.us/doc/nasmdoc0.html

    [NASM 数据定义]http://www.nasm.us/doc/nasmdoc3.html

    [NASM]https://www.tutorialspoint.com/assembly_programming/assembly_system_calls.htm

    [GAS]http://csiflabs.cs.ucdavis.edu/~ssdavis/50/att-syntax.htm

    [Gas Doc]https://sourceware.org/binutils/docs-2.16/as/index.html

    [macOS]http://orangejuiceliberationfront.com/intel-assembler-on-mac-os-x/

    [INT table]https://en.wikibooks.org/wiki/X86_Assembly/Advanced_Interrupts

    [Inline Assembly]http://www.ibm.com/developerworks/library/l-ia/index.html

    [InlineAssemble][http://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html]

    [Code Optimization]http://www.agner.org/optimize/

    [Apple ABI Function Call Guide] [link]

     

     

     

     Aurora 极光城

    讲技术,说人话

     

     

    展开全文
  • 制作一张简单的网页(HTML+CSS+JS) 【3】

    万次阅读 多人点赞 2019-07-12 09:14:40
    前面两篇文章了HTML和CSS,接下来这篇我把我学到的JavaScript,做一下简单的总结。如果我把一张网页比作是一幢大楼,那么HTML就是大楼的根基和基本骨架,CSS就是使大楼宏伟的一些装饰物,那么JS就是大楼里面活动的...

            前面两篇文章写了HTML和CSS,接下来这篇我把我学到的JavaScript,做一下简单的总结。如果我把一张网页比作是一幢大楼,那么HTML就是大楼的根基和基本骨架,CSS就是使大楼宏伟的一些装饰物,那么JS就是大楼里面活动的东西。简单的说就是网页上的特效效果都要靠JS来完成,比如一些焦点新闻的轮换、与用户的交互等等。

    一.JS的基本知识及窗口交互

    1.写js的基本格式:

     <script type="text/javascript”>...</script>

    2.有关注释:

     (1)单行注释://

     (2)多行注释:/*.......*/ 

    3.定义变量:var 变量名;

    4.函数:

    function 函数名() 
    {      
         函数代码; 
    }

    5.输出内容:document.write()

       (1)多项内容之间用“+”

       (2)要输出空格的时候,可以在输入时加“&nbsp”,一个“&nbsp”表示一个空格。 

    6.窗口交互方法

    (1)消息对话框-警告:alert(字符串或变量);

     

    (2)消息对话框-确认:confirm(str);

     

    (3)消息对话框-提问:prompt(str1,str2);

       需要注意的是str1是显示在文本框中的内容,不可以修改

       而str2是自己要输进去的内容,这是可以修改的 

     

    (4)打开新窗口

    window.open([URL], [窗口名称], [参数字符串]}

    1>URL这是在网页打开的地址或路径。

    2>窗口名称:被打开窗口的名称,由字母、数字和下划线组成 

        一些特殊的名称:

    _blank:在新窗口显示目标网页

    _self:在当前窗口显示目标网页

    _top:框架网页中在上部窗口中显示目标网页

    (5)关闭新窗口

    Window.close();

    二.认识事件和内置对象

    我把一些常用的事件整理出来:

    1.onclick(鼠标单击事件)

    2.onmouseover(鼠标经过事件)

    3.onmouseout(鼠标移开事件)

    4.onfocus(光标聚焦事件)

    5.onblur(失焦事件)

    6.onselect(内容选中事件)

    7.onchange(文本框内容改变事件)

    8.onload(加载事件)

    9.onunload(卸载事件)

     

    接下来的内容是在js中内置对象

    1.定义一个时间对象:

    var Udate=new Date();

    定义初始化:

    var d = new Date(2017, 10, 1);  //2017年10月1日
    var d = new Date('Oct 1, 2017'); //2017年10月1日

    2.返回、设置年份,用四位数表示

    get/setFullYear() 

    3.返回星期,返回的是0-6的数字,0表示星期天。如果要返回相对应“星期”,通过数组完成

    getDay()

    4.返回/设置时间,单位毫秒数

    get/setTime()

    5.小写字母转大写字母

    var mystr="Hello world!"; 
    var mynum=mystr.toUpperCase();

    大写字母转小写字母

    toLowerCase()

    6.返回指定位置的字符

     字符串中第一个字符的小标是0,最后一个字符的下标为字符串长度减一(string.length-1)

    7.返回指定的字符串首次出现的位置

    <script type="text/javascript">
      var str="I love JavaScript!"
      document.write(str.indexOf("I") + "<br />");
      document.write(str.indexOf("v") + "<br />");
      document.write(str.indexOf("v",8));
    </script>
    

    结果:0

               4

               9

    在该例子中,str.indexof(“v”,8) 这个的意思是说从该字符串的下标为8的数开始寻找v这个字符,找到它在9这个位置。

    如果没有找到该字符,则会输出-1. 

    8.字符串的分割split()

    var mystr = "www.baidu.com";
    document.write(mystr.split(".")+"</br>");
    document.write(mystr.split("", 2)+"</br>“);

    三.DOM操作

    1.在网页中,我们通过id先找到标签,然后进行操作,这是通过ID获取元素。

    document.getElementById(“id”)

    当然,通过name属性查也未尝不可。

    ducument,getElementsByname("name")
    

    需要注意的是,id只能有一个,但是name属性可以不唯一。

    2.通过innerHTML我们可以获取甚至改变网页上的内容。

    Object.innerHTML

    举个例子��:

    <html>
        <head>
            <title>TODO supply a title</title>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
        </head>
        <body>
            <p id="test">hello world!</p>
        </body>  
        <script type="text/javascript">
            var mystr = document.getElementById("test")
            document.write("p标签修改之前:"+mystr.innerHTML+ "</br>");
            mystr.innerHTML="hello DSCN!"
            document.write("p标签修改之前:"+mystr.innerHTML + "</br>");
        </script>
    </html>
    

    显示效果:

    3.在js中还能改变HTML的样式

    (1)基本语法:

    Object.style.property=new style;

    (2)基本属性(property)

    backgroundColor(元素的背景颜色)

    height(元素的高度)

    width(元素的宽度)

    color(文本的颜色)

    font(在一行设置所有的字体属性)

    fontFamily(设置元素的字体系列)

    fontSize(设置元素的字体大小)
    4.显示和隐藏(display)

    Object.style.display = value

    值为none时,此元素会被隐藏;值为block时,此元素会被显示。

    四.实例介绍

    我们已经把网页加上了一些图片来修饰,接下来,我在上面两篇的代码基础之上,在加上一些其他的功能。比如检查表单有没有空格项用户没有填写,而这些需要js来完成。

    代码如下:

    <html>
        <head>
            <style type="text/css">
                div {
    
                    font-weight:bold;    
                    font-family: Microsoft Yahei;
                    width: 400px;
                    padding-left: 50px;
                    margin-left: 450px;
                }
    
                h1 {
                    text-align: center;
                }
    
                #sub {
                    background-color: #689;
                    width: 250px;
                    height: 30px;
                    color:white;
                    font-weight: bold;
                }
                body {
                    background-image: url("http://pic.qiantucdn.com/58pic/20/13/88/25r58PICqQa_1024.jpg");
                }
            </style>
            <script type="text/javascript">
                function abc()
                {
                    if (document.getElementById("name").value == "")
                    {
                        alert("Please write your name!");
                        form.myname.focus();
                        return false;
                    }
                    if (document.getElementById("passward").value == "")
                    {
                        alert("Please write your password!");
                        form.mypassward.focus();
                        return false;
                    }
                    if (document.getElementById("profile").value == "")
                    {
                        alert("please write your profile!");
                        form.profile.focus();
                        return false;
                    }
                }
    
    
            </script>
    
        </head>
    
        <body>
            <div id="sign">
                <h1>Sigh Up</h1></br>
                <h2>Your basic info</h2>
                <form>
                    <strong>Name:</strong>
                    <input type="text" name="myname" id="name"/></br>
                    </br>
    
                    <strong>Passward:</strong>
                    <input type="password" name="mypassward" id="passward"/></br>
                    </br>
    
                    <strong>Age:</strong></br>
                    <input type="radio" name="age" value="1" checked="checked"/>Under 13</br>
                    <input type="radio" name="age" value="2" />13 or older</br>
                    </br>
    
                    <strong>Your profile:</strong></br>
                    <textarea cols="50" rows="4" name="profile" id="profile"></textarea></br>
                    </br>
    
                    <strong>Job Role:</strong></br>
                    <select>
                        <option value="Front-End Developer" selected="selected" name="job">Front-End Developer</option>
                        <option value="Back-End Developer" name="job">Back-End Developer</option>
                    </select></br>
                    </br>
    
                    <strong>Interests:</strong></br>
                    <input type="checkbox" name="development" value="1" checked="checked">Development</br>
                    <input type="checkbox" name="design" value="2" >Design</br>
                    <input type="checkbox" name="business" value="3" >Business</br>
                    </br>
    
                    <input type="submit" name="Sign Up" value="Sign Up" id="sub" onclick="abc()">
                    </input>
                </form>
            </div>
        </body>
    </html>

    显示效果:

    当我没填姓名时:

    当我没输入密码时,然后以此类推:

     


     


     

     

     


     

     

    展开全文
  • 利用python一个简单的双色球彩票系统 1.设置每次买的号码一样 一个双色球彩票系统,系统可以随机产生一组数据,一组彩票数据有六位数,这六位数的的取值范围是0和1。 一张彩票是两块钱,用户可以选择购买彩票的...

    利用python写一个简单的双色球彩票系统

    1.设置每次买的号码一样

    写一个双色球彩票系统,系统可以随机产生一组数据,一组彩票数据有六位数,这六位数的的取值范围是0和1。

    一张彩票是两块钱,用户可以选择购买彩票的张数,若余额充足,用户可以开始游戏,要求从控制台输入6位的0或者1

    若用户输入的不对,要求用户重新输入,直到输入成功为止。若中奖的话,中奖金额为购买彩票金额的50倍,

    若没中奖则打印继续努力!用户可以选择继续买票或者是退出。买票和退出的时候要求打印剩余金额。

    余额不足的时候提示用户充值。

    import random
    
    
    print("开始游戏".center(50,"*"))
    money = int(input("请输入充值的金额:"))
    while True:
        count = int(input("请输入购买彩票的张数:"))
        if money>= 2*count:
            money -= 2*count
            jiang = ""
            for x in range(6):
                jiang += random.choice(["0","1"])
            print(jiang)
            
            number = input("请输入猜测的号码:")
            if jiang == number:
                print("恭喜你中大奖了,中奖的金额为%d" % (count * 2 * 50))
                money += count*2*50
            else:
                print("很遗憾,没中奖,继续努力!!!")
    
            print("您当前余额为%d"%money)
            jixu = input("是否继续游戏? yes/no")
            if jixu == "yes":
                continue
            else:
                print("退出游戏")
                break
        else:
            print("余额不足,当前余额为%d"%money)
            cong = input("是否充值?yes/no")
            if cong == "yes":
                money += int(input("请输入充值的金额:"))
                continue
            else:
                print("余额不足退出游戏")
                break
    
    

    2.设置每次买的号码不一样

    import random
    
    
    print("开始游戏".center(50,"*"))
    money = int(input("请输入充值的金额:"))
    while True:
        count = int(input("请输入购买彩票的张数:"))
        if money>= 2*count:
            money -= 2*count
            jiang = ""
            for x in range(6):
                jiang += random.choice(["0","1"])
            print(jiang)
    
            numlist = []
            for i in range(1,count+1):
                print("请输入第%d张号码:"%i)
                while 1:
                    number = input("请输入猜测的号码:")
                    if len(number) == 6:
                        for n in number:
                            if n in ["0","1"]:
                                pass
                            else:
                                print("号码非法,请重新输入")
                                break
                        else:
                            print("号码合法")
                            break
                    else:
                        print("长度非法请重新输入")
                numlist.append(number)
            jiangcount = numlist.count(jiang)
            if jiangcount>0:
                print("恭喜你中大奖了,中奖的金额为%d"%(jiangcount*2*50))
                money += jiangcount*2*50
            else:
                print("很遗憾,没中奖,继续努力!!!")
            number = input("请输入猜测的号码:")
            if jiang == number:
                print("恭喜你中大奖了,中奖的金额为%d" % (count * 2 * 50))
                money += count*2*50
            else:
                print("很遗憾,没中奖,继续努力!!!")
    
            print("您当前余额为%d"%money)
            jixu = input("是否继续游戏? yes/no")
            if jixu == "yes":
                continue
            else:
                print("退出游戏")
                break
        else:
            print("余额不足,当前余额为%d"%money)
            cong = input("是否充值?yes/no")
            if cong == "yes":
                money += int(input("请输入充值的金额:"))
                continue
            else:
                print("余额不足退出游戏")
                break
    
    
    展开全文
  • 用python60行代码一个简单的笔趣阁爬虫

    万次阅读 多人点赞 2021-01-11 18:30:39
    系列文章目录 python爬虫实战——爬取淘宝商品信息并导入EXCEL表格(超详细) python多线程爬取壁纸 妈妈再也不担心我没壁纸了!. python爬虫爬取虎牙数据(简单...利用python一个简单的笔趣阁爬虫,根据输入的小说

    系列文章目录

    python爬虫实战——爬取淘宝商品信息并导入EXCEL表格(超详细)
    python多线程爬取壁纸 妈妈再也不担心我没壁纸了!.

    python爬虫爬取虎牙数据(简单利用requests库以及Beautifulsoup).

    python爬虫之爬取壁纸(新手入门级).
    python爬虫实战——爬取猫眼电影TOP100并导入excel表



    前言

    利用python写一个简单的笔趣阁爬虫,根据输入的小说网址爬取整个小说并保存到txt文件。爬虫用到了BeautifulSoup库的select方法
    结果如图所示:
    在这里插入图片描述
    本文只用于学习爬虫


    一、网页解析

    这里以斗罗大陆小说为例 网址:
    http://www.biquge001.com/Book/2/2486/
    在这里插入图片描述
    可以发现每章的网页地址和章节名都放在了 <"div id=list dl dd a>中的a标签中,所以利用BeautfulSoup中的select方法可以得到网址和章节名

    Tag = BeautifulSoup(getHtmlText(url), "html.parser") #这里的getHtmlText是自己写的获取html的方法
    urls = Tag.select("div #list dl dd a")
    

    然后遍历列表

    for url in urls:
        href = "http://www.biquge001.com/" + url['href']  # 字符串的拼接 拼接成正确的网址
        pageName = url.text  # 每章的章名
    

    然后每章小说的内容都存放在<div id=“content” 里 同理得
    在这里插入图片描述

      substance = Tag.select("div #content")  # 文章的内容
    

    最后同理在首页获取小说的名称
    <"div id = info h1>

    在这里插入图片描述

    bookName = Tag.select("div #info h1")
    

    二、代码填写

    1.获取Html及写入方法

    def getHtmlText(url):
        r = requests.get(url, headers=headers)
        r.encoding = r.apparent_encoding  # 编码转换
        r.raise_for_status()
        return r.text
    
    def writeIntoTxt(filename, content):
        with open(filename, "w", encoding="utf-8") as f:
            f.write(content)
            f.close()
            print(filename + "已完成")
    
    

    2.其余代码

    代码如下(示例):

    url = "http://www.biquge001.com/Book/2/2486/"
    substanceStr = ""
    bookName1 = ""
    html = getHtmlText(url)
    # 判断是否存在这个文件
    Tag = BeautifulSoup(getHtmlText(url), "html.parser")
    urls = Tag.select("div #list dl dd a")
    bookName = Tag.select("div #info h1")
    for i in bookName:
        bookName1 = i.text
    if not os.path.exists(bookName1):
        os.mkdir(bookName1)
        print(bookName1 + "创建完成")
    else:
        print("文件已创建")
    for url in urls:
        href = "http://www.biquge001.com/" + url['href']  # 字符串的拼接 拼接成正确的网址
        pageName = url.text  # 每章的章名
        path = bookName1 + "\\"  # 路径
        fileName = path + url.text + ".txt"  # 文件名 = 路径 + 章节名 + ".txt"
        Tag = BeautifulSoup(getHtmlText(href), "html.parser")  # 解析每张的网页
        substance = Tag.select("div #content")  # 文章的内容
        for i in substance:
            substanceStr = i.text
        writeIntoTxt(fileName, substanceStr)
        time.sleep(1) 
    
    

    总结

    简单利用了BeautfulSoup的select方法对笔趣阁的网页进行了爬取
    展开全文
  • 实现简单的轮播图(单图片、多图片)

    万次阅读 多人点赞 2020-02-09 18:28:40
    前言 刚学js没多久,这篇博客就当做记录了,以后还会完善的,希望大佬们多多指点。ps:下面出现的都是直接闪动,没有滑动效果的轮播图。 单图片的替换 · ...
  • 制作一张简单的网页(HTML+CSS+JS)【1】

    万次阅读 多人点赞 2017-02-06 12:18:20
    前段时间学习了HTML和一些简单的CSS样式,自己也简单做了尝试,下面是我对HTML+CSS+Javascript的一些总结。 一.网页的基本格式 1.下面是一张html文件的基本格式: ... ... 2.在head的标签里,也可以添加其他...
  • 一起个Dubbo——1. 一个最简单的实现

    千次阅读 多人点赞 2020-06-26 17:53:05
    面试问你RPC,一起个Dubbo吧! 一起个Dubbo第一章,一个最简单的RPC框架实现
  • 斗地主 定义一副扑克牌,按照斗地主的规则,为三家随机发17牌,并且留3底牌。同时指定一个地主。 很好玩的,求源码就这一个简单功能就行了
  • 如题,尽可能的简单,少用js代码,在线求答案。。
  • c++的一个简单的2048小游戏

    万次阅读 多人点赞 2016-08-12 21:27:25
    其实也挺简单的。 但是我比较菜,不会做图形界面,只有黑框。。。 这个游戏需要实现的主要功能如下:随机生成数字 数字消除合并 判定游戏结束 游戏主体: 因为用C++的,所以干脆用了类,不过其实不用的话也没什么...
  • 一个简单的Java界面程序

    万次阅读 2016-11-08 23:13:09
    有时候未免想一些有界面的java小程序练练手,那么如何一个比较好看的界面话程序呢?下面小编就带你一步一步来搭建这个小洋房。实现界面化编程要用到的一个主要包import javax.swing.*。下面以一个学生选课界面为...
  • 俗话说:“人生苦短,我学Python”,照目前互联网的趋势,Python编程语言是越来越火旺,经常刷到很多文章博主都说Python是很容易入门,但是我学了一段时间之后,发现事情并不简单....(也许是自己太笨拙哈哈哈憨笑)...
  • 二、出文件Demo&lt;并设置编码&gt; 三、读取文件Demo&lt;并设置编码&gt; 一、流的简单介绍 Java中流主要分为两大类:字符流和字节流,放一张我盗的图: IO流的大体分类  字节流&lt;图中...
  • Android用两图片实现简单动画效果

    千次阅读 2016-03-22 17:35:15
    直接在java代码里图片布局,而不用XML文件。 先声明定义: ImageView imageviewsudubg; ImageView imageviewsudu;LinearLayout layoutChart; onCreate里的代码: layoutChart = new LinearLayout(this); ...
  • 初学机器学习,第一步是做一个简单的手写数字识别,我选用的是MNIST数据集(用其他数据集也可以,原理都差不多),算法是KNN(下载库直接调用函数,算法的具体实现没有过多关心)。在网上也看到过MNIST数据集的...
  • 利用js实现简单的图片切换(上下切换) 简单的图片切换 var crr=1; function changePic(){ var imgObj=document.getElementById("myPic"); imgObj.src=crr+".jpg";
  • 制作一张简单的网页(HTML+CSS+JS) 【2】

    万次阅读 多人点赞 2019-07-12 09:14:25
    在上一篇文章中,我总结了一下HTML,这一篇我把CSS简单做一下归纳,使网页变得富有美感。 一.CSS样式的基本知识 1.关于注释: /*注释内容*/ 2.最常见的css样式格式——嵌入式 例如对span里的内容的字改为蓝色...
  • 这就是最简单的一个用c#作为后代数据,iis作为服务器开发的一个小程序,后期我还打算好好做一个
  • 前几天简单的从页面对数据库进行的操作,下面对该功能进行升级,操作两相关联的表;上次是对新闻类型的修改,我在这里就不重复了,可以查看我之前的博客, 首先从页面说起: 页面部分: 页面部分我用了10个...
  • 接下来又学习了html5,其实主要练习的是html5的canvas,下面是用基于html5的canvas标签的连连看游戏。 var cwidth=120;//画布的宽 var cheight=600;//画布的高 var firstpick=true;//是否是第一次挑选 var ...
  • 现在学了python后,才发现,原来下载一个东西是十分简单的。举个例子吧。我们来看这图片和它的URL。 这里还是要推荐下小编的Python学习群:483546416,不管你是小白还是大牛,小编我都欢迎,不定期分享干货,包括...
  • 一个简单的python爬虫程序,爬取一下百度图片

    千次阅读 多人点赞 2020-04-15 11:01:28
    一个最最简单的爬虫好了,但是稍微有一点点错误就会报错,没有所谓的健壮性,怎么改进爬虫呢,将在下一篇文章进行讲解。 完整代码如下,大家可以自行体验一下: import requests import re import time url = '...
  • 最近在论文画图过程中遇到的问题,记录下。 (一)使用工具 AdobeAcrobatProDC版本2015; Win10。 (二)步骤 2.1找到工具栏,点击合并文件。 2.2将待合并的多个pdf格式图片按照顺序拖进去,点击合并。...
  • 用VC稍微漂亮的界面就是这么简单

    千次阅读 2012-06-23 14:09:14
    不是使用skinmagic,directui去动态加载皮肤,仅仅是使用VC/VS+MFC,再稍微上几行代码,自己做一个稍微看得过去的界面,做的多的大牛就不看也罢了,呵呵 我就贴一个界面聊一个好了 我尽量的缩小了界面截图,大致...
  •   近期在群里面看到了如下这样一个面试题目,这个题目其实难度不大,但是你是否能够很快出这个答案来呢? 建表语句 create table student ( id varchar(20), name varchar(20), gender char(1), birth...
  • 小米手环复制校园卡(最简单方式),解决小米手环不能复制门禁卡。 注意,这里的校园卡是加密卡,一般不容易破解,这里只是读取并写入0扇区的第一行数据,只能够身份识别充当门禁卡,不能进行消费等其他行为。(...
  • $thread['attachments']['used']['0']['attachment']" alt="$thread[subject]"/>你没看错。就是这样!要放在列表循环中//[loop]至于没有图片的情况下 自己判断咯,打印一下就知道怎么了 //var_dump()
  • 我要做的业务是上传文件(假上传),同时新增文件信息表和文件审核表,他们有共同的ID为文件ID(为了后面的关联查询)。 定义接口: public interface UpFileMapper { //上传文档 //1....
  • 作者:一苇 链接:https://www.zhihu.com/question/21351965/answer/31050145 来源:知乎 著作权归作者所有,转载请联系作者获得授权。 本题目前下面的解释都是线性代数教材上的各种定义,但都太过复杂了。我尝试...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 527,970
精华内容 211,188
关键字:

张怎么写简单