按alt+f2,在弹出的运行窗口中输入:gnome-terminal,回车。
如果新装的系统还没有设置root密码可以在这个界面输入:sudo passwd root,回车后按提示输入两次root的密码(注意,输入时是不会提示*号的,直接输入即可)。
UEFI系统安装U盘的制作方式
使用Windows7 USB download Tool利用Windows7 Ultimate x64 SP1的iso制作了启动用的U盘, 并删除source/ei.cfg文件, 这样安装的时候就会询问需要安装的系统版本了.
但是这样做完的U盘不支持EFI启动, 无法安装在GPT分区上. 所以需要处理一下.
首先, 把U盘上的所有文件复制出来, 放到硬盘上.
然后格式化U盘, 选FAT32.
之后把文件全部复制回去.
然后使用7z或者好压, 打开U盘\sources\install.wim文件, 注意不要修改此文件! 打開压缩文件中的 \1\Windows\Boot\EFI\ 这个目录, 找到bootmgfw.efi, 复制出来, 并重命名为bootx64.efi.
之后在U盘里找到\efi\Microsoft这个目录, 把boot文件夹复制到上一级目录去, 也就是\efi\boot, 并把bootx64.efi也放进这个boot文件夹.
完成了, 现在这个U盘就可以被主板认为是UEFI启动设备了.
安装过程中系统会自动创建MSR-GPT保护分区, 这就没问题了.
VC预处理指令和宏定义
刚接触到MFC编程的人往往会被MFC 向导生成的各种宏定义和预处理指令所吓倒,但是预处理和宏定义又是C语言的一个强大工具。使用它们可以进行简单的源代码控制,版本控制,预警或者完成一些特殊的功能。
一个经典的例子
使用预处理与宏定义最经典的例子莫过于加在一个头文件中以避免头文件被两次编译。试想这种的情况,有一个文件headerfile.h 它被包含在headerfile1.h中,同时在headerfile2.h 中也被包含了,现在有一个CPP文件,implement.cpp 包含了headerfile1.h 和headerfile2.h:
#include “headerfile1.h” |
假设headerfile.h 中定义了一个全局变量 iglobal 。
int iglobal; |
在编译的时候编译器两次编译headerfile,也就会发现iglobal被定义了两次,这时就会发生变量重定义的编译错误。
传统的解决办法是使用#ifdef 以及#endif 来避免头文件的重复编译,在上面的例子中,只需要加上这么几行:
#ifndef int iglobal; #endif |
仔细的考虑上面的宏定义,会发现当编译器编译过一次headerfile.h以后,smartnose_2002_6_21_headerfile_h
这个宏就被定义了,以后对headerfile.h的编译都会跳过int iglobal 这一行。当然smartnose_2002_6_21_headerfile_h
这个宏是可以任意定义的,但是这个宏本身不能和其它文件中定义的宏重复,所以MFC在自动生成的文件中总是使用一个随机产生的长度非常长的宏,但我觉得这没有必要,我建议在这个宏中加入一些有意义的信息,比方作者,文件名,文件创建时间等等,因为我们有时候会忘记在注释中加入这些信息。
在VC.net 中我们不会再看见这些宏定义了,因为在这里会普遍使用一个预处理指令:
#pragma once |
只要在头文件的最开始加入这条指令就能够保证头文件被编译一次,这条指令实际上在VC6中就已经有了,但是考虑到兼容性并没有太多的使用它。
源代码版本控制
当我们为许多平台开发多个版本的时候预编译指令和宏定义也能够帮我们的忙。假设我们现在为Windows 和Linux开发了一套软件,由于这两种系统的不同,我们不得不在程序控制源代码的版本。比方内存的分配,我们可以在LINUX上使用标准C的malloc 函数,但是我们希望在 Windows上使用HeapAlloc API。下面的代码演示了这种情况:
main() |
当我们在WINDOWS 平台上编译此程序的时候,只需要定义_WINDOWS_PLATFORM这个宏,那么HeapAlloc这条语句就能够起作用了。这样就能够让我们在同一个文件中为不同的平台实现不同版本的代码,同时保持程序的良好结构。在许多情况下,我们还可以为一个方法使用不同的算法,然后用宏定义来针对不同的情况选择其中的一个进行编译。这在MFC应用程序中是使用得最多的。最明显的就是文件中经常存在的
#ifdef _DEBUG …………………….some code……….. #endif |
这样的代码,这些代码在应用程序的调试版(DEBUG)中会发挥其作用。
#Pragma 指令
在所有的预处理指令中,#Pragma 指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。其格式一般为
#Pragma Para
其中Para 为参数,下面来看一些常用的参数。
message 参数。 Message 参数是我最喜欢的一个参数,它能够在编译信息输出窗口中输出相应的信息,这对于源代码信息的控制是非常重要的。其使用方法为:
#Pragma message(“消息文本”)
当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。
当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,此时我们可以用这条指令在编译的时候就进行检查。假设我们希望判断自己有没有在源代码的什么地方定义了_X86这个宏可以用下面的方法
#ifdef _X86 #Pragma message(“_X86 macro activated!”) #endif |
当我们定义了_X86这个宏以后,应用程序在编译时就会在编译输出窗口里显示“_X86 macro
activated!”。我们就不会因为不记得自己定义的一些特定的宏而抓耳挠腮了。
另一个使用得比较多的pragma参数是code_seg。格式如:
#pragma code_seg( |
它能够设置程序中函数代码存放的代码段,当我们开发驱动程序的时候就会使用到它。
最后一个比较常用的就是上面所说的#pragma once 指令了。
VC预定义的宏
在VC中有一类宏并不是由用户用#define语句定义的,而是编译器本身就能够识别它们。这些宏的作用也是相当大的。让我们来看第一个,也是MFC中使用得最频繁的一个:__FILE__ 。
当编译器遇到这个宏时就把它展开成当前被编译文件的文件名。好了,我们马上就可以想到可以用它来做什么,当应用程序发生错误时,我们可以报告这个错误发生的程序代码在哪个文件里,比方在文件test.cpp中有这样的代码:
try |
在程序运行的时候,如果内存分配出现了错误,那么在调试窗口中会出现there is an error in
file: test.cpp 这句话,当然,我们还可以把这个错误信息显示在别的地方。
如果我们还能够记录错误发生在哪一行就好了,幸运的是,与__FILE__宏定义一样,还有一个宏记录了当前代码所在的行数,这个宏是__LINE__。使用上面的两个宏,我们可以写出一个类似于VC提供的ASSERT语句。下面是方法
#define MyAssert(x) \ |
我们在应用程序中可以象使用ASSERT语句一样使用它,在错误发生时,它会弹出一个对话框,其标题和内容告诉了我们错误发生的文件和代码行号,方便我们的调试,这对于不能使用ASSERT语句的项目来说是非常有用的。
除了这两个宏以外,还有记录编译时间的__TIME__,记录日期的__DATE__,以及记录文件修改时间的__TIMESTAMP__宏。
使用这些预定义的宏,我们几乎可以生成和VC能够生成的一样完整的源代码信息报表。
结论
翻开MFC和Linux的源代码,宏定义几乎占据了半边天,消息映射,队列操作,平台移植,版本管理,甚至内核模块的拆卸安装都用宏定义完成。毫不夸张的说,有些文件甚至就只能看见宏定义。所以学习宏定义,熟练的使用宏定义对于学习C语言乃至VC都是非常关键的。
(之二)
在上一篇文章中,我演示了几个常用的宏定义和预处理指令,但可以说这些都是相当常规的技巧。下面要介绍的宏定义与预处理指令的用法也是ATL,MFC以及Linux中使用得比较多的非常重要的技巧。
## 连接符与# 符
## 连接符号由两个井号组成,其功能是在带参数的宏定义中将两个子串(token)联接起来,从而形成一个新的子串。但它不可以是第一个或者最后一个子串。所谓的子串(token)就是指编译器能够识别的最小语法单元。具体的定义在编译原理里有详尽的解释,但不知道也无所谓。同时值得注意的是#符是把传递过来的参数当成字符串进行替代。下面来看看它们是怎样工作的。这是MSDN上的一个例子。
假设程序中已经定义了这样一个带参数的宏:
#define paster( n ) printf( "token" #n " = %d", token##n )
同时又定义了一个整形变量:
int token9 = 9;
现在在主程序中以下面的方式调用这个宏:
paster( 9 );
那么在编译时,上面的这句话被扩展为:
printf( "token" "9" " = %d", token9 );
注意到在这个例子中,paster(9);中的这个”9”被原封不动的当成了一个字符串,与”token”连接在了一起,从而成为了token9。而#n也被”9”所替代。
可想而知,上面程序运行的结果就是在屏幕上打印出token9=9
在ATL的编程中,我们查看它的源代码就会经常看见这样的一段:
#define IMPLEMENTS_INTERFACE(Itf) \
{&IID_##Itf, ENTRY_IS_OFFSET,BASE_OFFSET(_ITCls, Itf) },
我们经常不假思索的这样使用它:
……
IMPLEMENTS_INTERFACE(ICat)
……
实际上IID_ICat 已经在别的地方由ATL向导定义了。当没有向导的时候,你只要遵循把IID_加在你的接口名前面来定义GUID的规则就也可以使用这个宏。在实际的开发过程中可能很少用到这种技巧,但是ATL使用得如此广泛,而其中又出现了不少这样的源代码,所以明白它是怎么一回事也是相当重要的。我的一个朋友就是因为不知道IMPLEMENTS_INTERFACE宏是怎么定义的,而又不小心改动了IID_ICat的定义而忙活了一整天。
Linux的怪圈
在刚开始阅读Linux的时候有一个小小的宏让我百思不得其解:
#define wait_event(wq,condition) \
do{ \
if(condition) \
break; \
__wait_event(wq,condition); \
}while(0)
这是一个奇怪的循环,它根本就只会运行一次,为什么不去掉外面的do{..}while结构呢?我曾一度在心里把它叫做“怪圈”。原来这也是非常巧妙的技巧。在工程中可能经常会引起麻烦,而上面的定义能够保证这些麻烦不会出现。下面是解释:
假设有这样一个宏定义
#define macro(condition) \
if(condition) dosomething();
现在在程序中这样使用这个宏:
if(temp)
macro(i);
else
doanotherthing();
一切看起来很正常,但是仔细想想。这个宏会展开成:
if(temp)
if(condition) dosomething();
else
doanotherthing();
这时的else不是与第一个if语句匹配,而是错误的与第二个if语句进行了匹配,编译通过了,但是运行的结果一定是错误的。
为了避免这个错误,我们使用do{….}while(0) 把它包裹起来,成为一个独立的语法单元,从而不会与上下文发生混淆。同时因为绝大多数的编译器都能够识别do{…}while(0)这种无用的循环并进行优化,所以使用这种方法也不会导致程序的性能降低。
几个小小的警告
正如微软声称的一样,宏定义与预编译器指令是强大的,但是它又使得程序难以调试。所以在定义宏的时候不要节省你的字符串,一定要力争完整的描述这个宏的功能。同时在定义宏的时候如有必要(比方使用了if语句)就要使用do{…}while(0)将它封闭起来。在宏定义的时候一定要注意各个宏之间的相互依赖关系,尽量避免这种依赖关系的存在。下面就有这样一个例子。
设有一个静态数组组成的整型队列,在定义中使用了这样的方法: int array[]={5, 6, 7, 8};
我们还需要在程序中遍历这个数组。通常的做法是使用一个宏定义
#define ELE_NUM 4
…………………………..
……………………………..
for(int I=0;I<ELE_NUM;I++)
{
cout<<array[I];
}
由于某种偶然的原因,我们删除了定义中的一个元素,使它变成:
array[]={5,6,7}
而却忘了修改ELE_NUM的值。那么在上面的代码中马上就会发生访问异常,程序崩溃。然后是彻夜不眠的调试,最后发现问题出在这个宏定义上。解决这个问题的方法是不使用
array[]={….}这样的定义,而显式的申明数组的大小:
array[ELE_NUM]={….}
这样在改动数组定义的时候,我们就不会不记得去改宏定义了。总之,就是在使用宏定义的时候能够用宏定义的地方统统都用上。
我发现的另一个有趣的现象是这样的:
假设现在有一个课程管理系统,学生的人数用宏定义为:
#define STU_NUM 50
而老师的人数恰好也是50人,于是很多人把所有涉及到老师人数的地方通通用上STU_NUM这个宏。另一个学期过去,学生中的一个被开除了,系统需要改变。怎么办呢?简单的使用#define STU_NUM 49 么?如果是这样,一个老师也就被开除了,我们不得不手工在程序中去找那些STU_NUM宏然后判断它是否是表示学生的数目,如果是,就把它改成49。天哪,这个宏定义制造的麻烦比使用它带来的方便还多。正确的方法应该是为老师的数目另外定义一个宏:
#define TEA_NUM 50
当学生的数目改变以后只要把STU_NUM 定义为49就完成了系统的更改。所以,当程序中的两个量之间没有必然联系的时候一定不要用其中的一个宏去替代另一个,那只会让你的程序根本无法改动。
最后,建议C/C++语言的初学者尽可能多的在你的程序中使用宏定义和预编译指令。多看看MFC,ATL或者LINUX的源代码,你会发现C语言强大的原因所在。
神十将于6月中旬发射 3名航天员再访天宫
央视新闻【神十6月发射 3名航天员再访天宫】中国载人航天工程新闻发言人今天宣布,我国将于6月中旬择机发射神舟十号飞船、3名航天员将再次访问天宫一号。
Windows 7设置由VHD启动系统
VHD Native Boot作为Windows 7和Windows Server 2008 R2新增重要功能之一,它支持系统直接从VHD启动。
1. 说明
系统要求:Windows 7 Enterprise 或 Ultimate版,其它版本不支;Windows Server 2008 R2 除 Foundation Edition版本不支持外,其它版本都支持
磁盘要求:系统必须有两个分区,System分区必须有Windows 7 Boot environment Files和BCD存储。如果VHD文件类型为动态扩展,必须确保磁盘有足够的磁盘空间。最多支持挂接 512 个VHD文件。
另外,从VHD启动的系统,不支持系统休眠和Over SMB 共享;保存VHD文件的分区不支持Bit-locker加密,VHD文件的父分区不支持Volume Snaphot功能;VHD不能配置为动态磁盘,也不支持建立软件Raid。相比物理硬盘,VHD没有CACHE,所以在写数据时,VHD硬盘相比物理硬盘有些差异,但读性能基本上和物理硬盘相近。
2. 必备工具
(1) bcdboot - bcd 启动文件创建和修复工具。
bcdboot.exe 命令行工具用于将关键启动文件复制到系统分区以及创建新的系统 BCD 存储。
bcdboot <source> [/l <locale>] [/s <volume-letter>] [/v] [/m [{OS Loader ID}]]
source——指定 windows 系统根目录的位置。
/l ——指定在初始化 BCD 存储时使用的可选区域设置参数。默认值为“简体中文”。
/s ——指定一个可选的卷号参数,该参数用于指定要将启动环境文件复制到的目标系统分区。默认值为固件标识的系统分区。
/v ——启用详细模式。
/m ——如果提供了操作系统加载器 GUID,则此选项可以将给定的加载器对象与系统模板合并起来,以产生可启动条目。否则,只合并全局对象。
(2) Bcdedit
Bcdedit.exe 命令行工具用于修改启动配置数据存储。启动配置数据存储包含启动配置参数并控制操作系统的启动方式。
(3) diskpart
磁盘分区管理工具。在本例中,它用来在命令行状态产生VHD文件,分区,格式化等
(4) imagex
WIM映像管理工具,它包含在微软AIK工具包中。本例中,用它来释放安装映像Install.WIM文件到VHD文件。
(5) Windows Deployment Services部署服务
3. 部署
用VHD启动计算机,有以下两种场景,一是将VHD部署在已有OS的计算机上,二是裸机的部署。两者的最大区别是对裸机,由于硬盘上没有OS,也就没有Windows 7的启动文件和MBR,所以必须首先产生一个系统分区,并使用Bcdboot将Windows 7启动文件写入这个分区;同时也必须产生一个主分区,用来存放VHD文件。对于在已有OS的计算机上部署,主要是要产生VHD文件,并将Windows 7或Windows 2008 R2映像释放到VHD硬盘,并使用BCDEDIT工具增加启动项目。
由于VHD文件有动态,固定,差异这三种类型,为了保证性能,推荐使用固定尺寸大小的VHD文件。
在已有OS的计算机上部署
实验环境:使用Microsoft Virtual Server 2005 SP1产生一台虚拟机Windows 7并安装好WINDOWS 7 7600操作系统和AIK工具包,这台虚拟机有两块虚拟硬盘,一块c:,已安装好Windows 7;另一块e:,用来产生VHD文件;光驱为D:
启动计算机,进行如下操作(注意以下操作均以管理员BILL.xu进行操作)
(1) 以管理员身份进入命令提示符状态,运行DISKPART
(2) 依次输入以下命令:
Create vdisk file=e:\win2007.vhd maximum=10000 type=fixed(在e盘产生名为win2007.vhd,大小为10000MB,类型为固定尺寸的VHD硬盘),等待一段时间完成。
Select vdisk file=e:\win2007.vhd(选择e:\win2007.vhd磁盘文件)
Attach vdisk(连接到虚拟磁盘文件)
Create Partition Primary(产生主分区)
Assign Letter R(分配盘符为R)
Format Quick Fs=ntfs(以NTFS文件系统快速格式磁盘),此时在我的电脑会多出一块盘符为R的硬盘。
(3) 打开虚拟服务器管理网站,编辑虚拟机Windows 7设置,在光驱中加载windows 7安装光盘ISO文件。
(4) 以管理员身份运行AIK工具包中的Deployment Tools Command Prompt,运行以下命令
imagex /info d:\sources\install.wim(显示安装映像文件的信息,主要是查找映像文件中各个版本的image index号)
imagex /apply d:\sources\install.wim 5 r:\(将安装映像中image index=5映像释放到r:盘,即释放到VHD文件上,image index=5这旗舰版),等待完成。
(5) 回到第二步的窗口,输入以下命令
detach vdisk(分离虚拟磁盘文件)
exit(退出DISKPART命令)
(6) 在命令提示符状态下,输入以下命令
bcdedit /copy {current} /d "Starting From VHD"(编辑BCD数据库,将{current}复制为副本,注释为Starting From VHD。执行后会返回新项目的GUID)。在本操作中返回的GUID为 {91efbc47-7796-11de-8788-b469c8eae731}
bcdedit /set {91efbc47-7796-11de-8788-b469c8eae731} device vhd=[e:]\win2007.vhd(编辑BCD数据库,修改guid为{91efbc47-7796-11de-8788-b469c8eae731}的device值修改为[e:]\win2007.vhd,注意[])
bcdedit /set {91efbc47-7796-11de-8788-b469c8eae731} osdevice vhd=[e:]\win2007.vhd
bcdedit /set {91efbc47-7796-11de-8788-b469c8eae731} detecthal on(允许启动时硬件抽像层检测)完成后,将e:\win2007.vhd备份到其它位置,备用。
(7)重新启动计算机,选择“Starting From VHD”,出现安装界面,安装操作系统,完成。
在裸机上的部署
实验环境:使用Microsoft Virtual Server 2005 SP1产生一台虚拟机Win7VHD,一块硬盘c:,光驱为D:。
(1) 打开虚拟服务器管理网站,编辑虚拟机Win7VHD设置,在光驱中加载WINPE的ISO文件。WINPE光盘的ISO文件可以在装好AIK的文件夹下找到。
(2) 用WINPE光盘启动计算机,选择命令提示符
(3) 依次输入以下命令
create partition primary size=200
format quick fs=ntfs
assign letter=s
active
产生个尺寸大小为200M的文件,以NTFS格式化,并指定盘符为S,激活这个分区
create partition primary
format quick fs=ntfs
assign letter=c
exit
将其它的空间产生一个主分区,以NTFS格式化这个分区,分配盘符为C。
(4) 将我们上例中第6步产生的备份的文件复制到c盘
(5) 在命令提示符状态使用Diskpart,连接虚拟磁盘文件win2007.vhd
Diskpart
select vdisk file=c:\win2007.vhd
attach vdisk
exit
(6) cd f:\windows\system32 (F:为虚拟磁盘文件加载后产生的盘符)
bcdboot f:\windows /s s: 将系统文件复制到系统盘(即前面产生200M的系统分区)
(7) 以硬盘启动计算机,完成安装。