Content #
在谈判中,我最常用的开场白是“一切都好吗?”这看似一个普普通通的问题,但这个问题中至少包含了4种谈判技巧。
- 这个问题有助于你和对方建立一种良好的人际关系—你一开始就表现得亲切健谈。
- 这是一个问句—提问是一种搜集信息的极佳方式。
- 这个问题首先关注对方以及他们的情绪和感受,而不是“谈判”本身。
- 这个问题是随意的闲聊,有助于为双方营造一个轻松舒适的氛围。
From #
沃顿商学院最受欢迎的谈判课
通过调用门实施特权级之间的控制转移时,可以使用jmp far 指令,也可以使用 call far 指令。如果是后者,会改变当前特权级CPL。因为栈段的特权级必须同当前特权级保持一致,因此,还要切换栈,即,从低特权级的栈切换到高特权级的栈。
比如,一个特权级为3 的程序必须使用自己的3 特权级栈工作。当它通过调用门进入0 特权级的代码段执行时,当前特权级由3 变为0。此时,栈也要跟着切换,从3 特权级的栈切换到0 特权级的栈。这主要是为了防止因栈空间不足而产生不可预料的问题,同时也是为了防止栈数据的交叉引用。
为了切换栈,每个任务除了自己固有的栈之外,还必须额外定义几套栈,具体数量取决于任务的特权级别。
这些额外的栈,一般会由操作系统加载程序时自动创建。其描述符位于任务自己的LDT 中。同时,还要在任务的TSS 中登记,原因是,栈切换是由处理器固件自动完成的,处理器需要根据TSS 中的信息来完成这一过程。
如 任务状态段TSS的格式图所示,在TSS 内,从偏移4~24 处登记有特权级 0 到2 的栈段选择子,以及相应的ESP 初始值。任务自己固有的栈信息则位于偏移量为56(ESP)和80(SS)的地方。任务寄存器TR 总是指向当前任务的任务状态段TSS,其内容为该TSS 的基地址和界限。在切换栈时,处理器可以用TR 找到当前任务的TSS,并从TSS 中获取新栈的信息。
在一个多任务的环境中,当任务切换发生时,必须保护旧任务的运行状态,或者说是保护现场,保护的内容包括通用寄存器、段寄存器、栈指针寄存器ESP、指令指针寄存器EIP、状态寄存器EFLAGS,等等。否则的话,等下次该任务又恢复执行时,一切都会变得茫然而毫无头绪。
为了保存任务的状态,并在下次重新执行时恢复它们,每个任务都应当用一个额外的内存区域保存相关信息,这叫做任务状态段(Task State Segment:TSS)。任务状态段TSS 具有固定的格式,最小尺寸是104 字节,下图中所标注的偏移量是十进制的。

处理器用TR 寄存器来指向当前任务的TSS。
当任务切换发生的时候,TR 寄存器的内容也会跟着指向新任务的TSS。这个过程是这样的:
2.再使TR 寄存器指向新任务的TSS,并从新任务的TSS 中恢复现场。
比较奇怪的是,为什么这个寄存器叫TR,而不是TSSR。原因很简单,TSS 是一个任务存在的标志,用于区别一个任务和其他任务。所以,这个寄存器叫做任务寄存器(Task Register:TR)。
当特权级变化时,CPU从TSS中加载对应DPL的栈指针(用户态使用ss1+esp1,内核态使用ss0+esp0).
调用门(Call-Gate)用于在不同特权级的程序之间进行控制转移。本质上,它只是一个描述符,一个不同于代码段和数据段的描述符,可以安装在GDT 或者
LDT 中。该描述符的格式如下图所示,下面是低32 位,上面是高32 位。
调用门描述符的格式
调用门描述符给出了例程所在代码段的选择子,而不是32 位线性地址。有了段选择子,就能访问描述符表得到代码段的基地址,这样做间接了一点,但却可以在通过调用门进行控制转移时,实施代码段描述符有效性、段界限和特权级的检查。
例程在代码段中的偏移量也是在描述符中直接指定的,只是被分成了两个16 位的部分。很显然,在通过调用门调用例程时,不使用指令中给出的偏移量。
描述符中的TYPE 字段用于标识门的类型,共4 比特,值“1100”表示调用门。
描述符中的P 位是有效位,通常应该是“1”。当它为“0”时,调用这样的门会导致处理器产生异常中断。对于操作系统来说,这个机关可能会很有用。比如,为了统计调用门的使用频率,可以将它置“0”。然后,每当因调用该门而产生异常中断时,在中断处理程序中将该门的调用次数加一,同时把P 位置“1”。对于因P 位为“0”而引起的中断来说,它们属于故障中断,从中断处理过程返回时,处理器还会重新执行引起故障的指令。此时,因P 已经为“1”,所以可以执行。就当前的例子而言,因为在提供调用门服务的同时,还要统计门的调用次数,故,可以在该调用门所对应的例程中将P 位清零。这样,下一次该门被调用时,又会重复以上过程。
有libfoo1.c和libfoo2.c两个源代码文件,希望产生一个libfoo.so.1.0.0的共享库,这个共享库依赖于libbar1.so和 libbar2.so这两个共享库,可以使用如下命令行:
$gcc –shared -fPIC –Wl,-soname,libfoo.so.1 –o libfoo.so.1.0.0 \
libfoo1.c libfoo2.c -lbar1 -lbar2
当然我们也可以把编译和链接的步骤分开,分多步进行:
$gcc –c –g –Wall –o libfoo1.o libfoo1.c
$gcc –c –g –Wall –o libfoo2.o libfoo2.c
$ld –shared –soname libfoo.so.1 –o libfoo.so.1.0.0 \
libfoo1.o libfoo2.o –lbar1 –lbar2
几个值得注意的事项:不要把输出共享库中的符号和调试信息去掉,也不要使用GCC的“-fomit-frame-pointer”选项,这样做虽然不会导致共享库停止运行,但是会影响调试共享库,给后面的工作带来很多麻烦。
在开发过程中,你可能要测试新的共享库,但是你又不希望影响现有的程序正常运行。我们前面提到的LD_LIBRARY_PATH是一个很好的方法,用它可以指定共享库的查找路径。还有一种方法是使用链接器的“-rpath”选项(或者 GCC的-Wl,-rpath),这种方法可以指定链接产生的目标程序的共享库查找路径。比如我们用如下命令行产生一个可执行文件:
$ld –rpath /home/mylib –o program.out program.o –lsomelib
这样产生的输出可执行文件program.out在被动态链接器装载时,动态链接器会首先在“/home/mylib”查找共享库。
程序员的自我修养