Blog

给他指明一个未来的目标

Content #

要消除集中营生活对犯人在心理和病理方面的影响,就要运用心理治疗和心理卫生的方法,给他指明一个未来的目标,以使他恢复内在的力量。有些犯人本能地会给自己确定这样一个目标。人的独特之处在于只有人才能着眼于未来。在极端困难的时刻,这就是他的救赎之道,不过他得迫使自己将精神专注于此。

我就有过这样的经历。因为老穿破鞋,我的脚伤得很重。有一天,脚疼得厉害,我一瘸一拐地跟大家走了几公里路,从集中营到工地干活去。那天非常冷,寒风刺骨。我不停地想着悲惨生活中的琐屑之事。今晚吃什么?要是能额外得到一根香肠,要不要拿它去换一片面包呢?要不要用最后一支香烟去换一碗汤喝?去哪里弄一根好点儿的鞋带?到工地后是跟原来的小队一起干活儿呢,还是会被派到其他凶恶监工的小队去?怎样跟囚头搞好关系,让他帮我在营里找个活干,而不用走那么远的路到工地上?

我对时时刻刻想着这些琐事的情况感到厌烦了,就迫使自己去想别的事。突然,我看到自己站在明亮、温暖而欢快的讲台上,面前坐着专注的听众。我在给他们讲授集中营心理学!那一刻,我从科学的角度客观地观察和描述着折磨我的一切。通过这个办法,我成功地超脱出当时的境遇和苦难,好像所有这些都成了过去。我和我的痛苦都成为自己心理学研究的有趣对象。斯宾诺莎在《伦理学》中谈到“作为痛苦的激情,一旦我们对它有了清晰而明确的认识,就不再感到痛苦了”。

From #

活出生命的意义

未知期限的临时存在

Content #

从前的囚犯在回忆牢狱生活时都觉得集中营对人最压抑的影响是你不知道自己要被关多久,你不知道哪天会被释放(在我们那个集中营,对这个问题大家甚至谈都懒得谈)。实际上,犯人的刑期不光不确定,而且是无期限的。某位著名的心理学家就说过,集中营生活是一种“临时的存在”。我们还可以补充几个字,那是“未知期限的临时存在”。

新来的犯人一般对集中营的条件一无所知。那些从别的集中营来的犯人则不得不保持沉默,有些集中营则从来没有人回来过。一进入集中营的大门,人的心理就会发生变化。不确定性结束以后,又是结局的不确定性。你不可能预测这样一种生存状态何时能结束,或者到底能否结束。

拉丁词finis有两个含义:“结尾或结局”和“要达到的目标”。看不到“临时的存在”何时结束的人,也不可能去追求生活的终极目标。他不再像正常人那样为了将来而生存。因此,他内在生命的这个结构就改变了,我们从生活其他领域所知道的堕落迹象就开始了。比如,失业工人的情况就是这样。他的存在成了临时性的,在一定意义上说,他不能够为未来而生活,也不可能确定什么目标。针对失业煤矿工人的研究表明,因为失业,他们受到一种特殊的、扭曲的心理时间的折磨。犯人同样饱受这种奇特的“时间—体验”之苦。在集中营,很短的时间,比方说一天,由于充满了折磨和痛苦,所以显得特别漫长。而大点的时间单位,比如一个星期,则过得很快。我的狱友都同意我所说的:在集中营里,一天过得比一个星期慢。我们的时间—体验是多么荒诞!在这一点,我们想起了托马斯·曼的《神山》,其中有一些非常到位的心理学评论。托马斯研究过人在类似集中营环境(比如隔离病区那些不知何时能回家的结核病人)中的心理变化过程。他们也经历着类似的生活状态——没有未来,没有目标。

From #

活出生命的意义

任何人都能够决定自己成为什么样的人

Content #

从这个角度看,集中营犯人的心理反应似乎不仅仅是对确定的物质和社会条件的表达。即使像缺少睡眠、食物不足以及心理紧张等类似的情况,也只能说是限定了犯人可能的应对方式。在最后的分析中我们可以看出,犯人最终成为什么样的人,仍然取决于他自己内心的决定,而不单单取决于集中营生活的影响。因此,在心理和精神的层面,基本上任何人都能够决定自己成为什么样的人。即便在集中营,他也能保持自己作为人的尊严。

陀思妥耶夫斯基说过:“我只害怕一样——那就是配不上我所受的痛苦。”在我结识了那些烈士之后,这句话常常出现在我脑海里。那些烈士的行为,他们的痛苦和死亡,都表明人不能丧失内在的自由。他们可以说配得上他们的苦难,他们忍受痛苦的方式是一种真正的内在升华。就是这种精神的自由——任谁也无法夺走——使生活变得有目的、有意义。

From #

活出生命的意义

集中营中的幽默

Content #

对一个外人来说,发现在集中营里居然还有类似艺术的东西存在,一定会令他惊咤不已,但当他听到你还能从中找到幽默感时更会目瞪口呆。当然,这种幽默感非常细微,而且只延续数秒。幽默是灵魂保存自我的另一件武器。大家都知道,幽默比人性中的其他任何成分更能够使人漠视困苦,从任何境遇中超脱出来,哪怕只是几秒种。

From #

活出生命的意义

进程0设置LDT和TSS描述符

Content #

代码路径:include/asm/system.h

#define _set_tssldt_desc(n,addr,type) \
__asm__ ("movw $104,%1\n\t" \            //将104,即1101000存入描述符的第1、2字节
          "movw %%ax,%2\n\t" \           //将tss或ldt基地址的低16位存入描述符的第
                                          //3、4字节
          "rorl $16,%%eax\n\t" \         //循环右移16位,即高、低字互换
          "movb %%al,%3\n\t" \           //将互换完的第1字节,即地址的第3字节存入第5字节
          "movb $" type ",%4\n\t" \      //将0x89或0x82存入第6字节
          "movb $0x00,%5\n\t" \          //将0x00存入第7字节
          "movb %%ah,%6\n\t" \   //将互换完的第2字节,即地址的第4字节存入第8字节
          "rorl $16,%%eax" \      //复原eax
          ::"a" (addr), "m" (*(n)), "m" (*(n + 2)), "m" (*(n + 4)), \
          "m" (*(n + 5)), "m" (*(n + 6)), "m" (*(n + 7)) \
    //"m" (*(n))是gdt第n项描述符的地址开始的内存单元
    //"m" (*(n + 2)) 是gdt第n项描述符的地址向上第3字节开始的内存单元
    //其余依此类推
    )
//n:gdt的项值,addr:tss或ldt的地址,0x89对应tss,0x82对应ldt
#define set_tss_desc(n,addr) _set_tssldt_desc(((char *) (n)),addr,"0x89")
#define set_ldt_desc(n,addr) _set_tssldt_desc(((char *) (n)),addr,"0x82")

movw $104,%1是将104赋给了段限长15:0的部分;粒度G为0,说明限长就是104字节,而TSS除去struct i387_struct i387后长度正好是104字节。LDT是3×8 =24 字节,所以104字节限长够用。(取两者的最大值,LDT有部分浪费。)

TSS的类型是0x89,即二进制的10001001,可以看出movb $" type “,%4在给type 赋值1001的同时,顺便将P、DPL、S字段都赋值好了。同理,movb $0x00,%5在给段限长19:16部分赋值0000的同时,顺便将G、D/B、保留、AVL字段都赋值好了。

...

进程0需要具备的三种能力

Content #

进程0是Linux操作系统中运行的第一个进程,也是Linux操作系统父子进程创建机制的第一个父进程。主要包含如下三方面的内容。

1)系统先初始化进程0。进程0管理结构task_struct的母本(init_task = {INIT_TASK,})已经在代码设计阶段事先设计好了,但这并不代表进程0已经可用了,还要将进程0的task_struct中的LDT、TSS与GDT相挂接,并对GDT、 task[64]以及与进程调度相关的寄存器进行初始化设置。 2)多进程轮询的能力。系统对时钟中断进行设置,以便在进程0运行后,为进程 0以及后续由它直接、间接创建出来的进程能够参与轮转奠定基础。 3)进程0要具备处理系统调用的能力。每个进程在运算时都可能需要与内核进行交互,而交互的端口就是系统调用程序。系统通过函数set_system_gate将 system_call与IDT相挂接,这样进程0就具备了处理系统调用的能力了。这个 system_call就是系统调用的总入口。

进程0只有具备了以上三种能力才能保证将来在主机中正常地运行,并将这些能力遗传给后续建立的进程。

From #

Linux内核设计的艺术

move_to_user_mode

Content #

在进程0正式创建进程1之前,要将进程0由0特权级转变为3特权级。方法是调用 move_to_user_mode()函数,模仿中断返回动作,实现进程0的特权级从0转变为3。 //代码路径:include/system.h:

#define move_to_user_mode() \     //模仿中断硬件压栈,顺序是ss、esp、eflags、cs、eip
__asm__("movl %%esp,%%eax\n\t" \  //保存内核栈指针
        "pushl $0x17\n\t" \     //SS进栈,0x17即二进制的10111(3特权级、LDT、数据段)
        "pushl %%eax\n\t" \      //ESP进栈
        "pushfl\n\t" \            //EFLAGS进栈
        "pushl $0x0f\n\t" \     //CS进栈,0x0f即1111(3特权级、LDT、代码段)
        "pushl $1f\n\t" \        //EIP进栈,用户模式代码入口
        "iret\n" \              //出栈恢复现场、翻转特权级从0到3
        "1:\tmovl $0x17,%%eax\n\t" \    //将数据段寄存器(DS、ES、FS、GS)
            "movw %%ax,%%ds\n\t" \      //设置为用户数据段选择子0x17,
            "movw %%ax,%%es\n\t" \      //确保用户程序正确访问数据段。
            "movw %%ax,%%fs\n\t" \
            "movw %%ax,%%gs" \
        :::"ax")

IA-32体系结构翻转特权级的方法之一是用中断。当CPU接到中断请求时,能中断当前程序的执行序,将CS:EIP切换到相应的中断服务程序去执行,执行完毕又执行iret指令返回被中断的程序继续执行。

栈中的SS值是0x17,用二进制表示就是00010111,最后两位表示3,是用户特权级,倒数第3位是1,表示从LDT中获取段描述符,第4~5位的10表示从LDT的第3 项中得到进程栈段的描述符。 进程0中GDT、LDT、TSS关系示意图

iret从栈中依次弹出EIP、CS、EFLAGS、ESP、SS,将CPU切换至用户模式。

中断与函数调用不同的是,函数调用是程序员事先设计好的,知道在代码的哪个地方调用,编译器可以预先编译出压栈保护现场和出栈恢复现场的代码;而中断的发生是不可预见的,无法预先编译出保护、恢复的代码,只好由硬件完成保护、恢复的压栈、出栈动作。所以,int指令会引发CPU硬件完成SS、ESP、EFLAGS、 CS、EIP的值按序进栈,同理,CPU执行iret指令会将栈中的值自动按反序恢复给这5个寄存器。

From #

Linux内核设计的艺术

段选择子格式

挂接中断服务程序(_set_gate)

Content #

#define _set_gate(gate_addr,type,dpl,addr)  \
__asm__("movw %%dx,%%ax\n\t" \       //将edx的低字赋值给eax的低字
        "movw %0,%%dx\n\t" \        //%0对应第二个冒号后的第1行的"i"
        "movl %%eax,%1\n\t" \       //%1对应第二个冒号后的第2行的"o"
        "movl %%edx,%2" \           //%2对应第二个冒号后的第3行的"o"
        : \                        //这个冒号后面是输出,下面冒号后面是输入
        : "i" ((short) (0x8000 + (dpl<<13) + (type<<8))), \ //立即数
          "o" (*((char *) (gate_addr))), \             //中断描述符前4个字节的地址
          "o" (*(4 + (char *) (gate_addr))), \         //中断描述符后4个字节的地址
          "d" ((char *) (addr)),"a" (0x00080000))      //"d"对应edx,"a"对应eax

set_trap_gate定义如下:

#define set_trap_gate(n,addr) _set_gate(&idt[n],15,0,addr)

因此:

set_trap_gate(0,&divide_error)<=> _set_gate(&idt[n],15,0,addr)

可以看出,n是0;gate_addr是&idt[0],也就是idt的第一项中断描述符的地址; type是15;dpl(描述符特权级)是0;addr是中断服务程序divide_error(void)的入口地址.

“movw %%dx,%%ax\n\t”是把edx的低字赋值给eax的低字; edx是(char *)(addr),也就是&divide_error; eax的值是0x00080000,8应该看成1000,这样eax的值就是 0x00080000 +((char *)(addr)的低字),其中的0x0008是段选择符,

“movw %0,%%dx\n\t”是把(short)(0x8000 +(dpl<<13)+(type<<8))赋值给dx。别忘了,edx是(char *)(addr),也就是&divide_error。

set_system_gate(n,addr)与set_trap_gate(n,addr)用的_set_gate(gate_addr,type,dpl,addr)是一样的;差别是set_trap_gate的 dpl是0,而set_system_gate的dpl是3。dpl为0的意思是只能由内核处理,dpl为 3的意思是系统调用可以由3特权级(也就是用户特权级)调用。

From #

Linux内核设计的艺术

IDT结构图(head.s执行后)

Content #

在x86架构中,IDTR (中断描述符表寄存器) 需要通过lidt指令加载一个6字节的操作数,这6字节包含了IDT的界限(limit)和基地址(base)。

lidt idt_descr

idt_descr就是定义了这个6字节结构。

.align 2
.word 0
idt_descr:
        .word 256*8-1		# idt contains 256 entries(0x07FF)
        .long _idt

图右下侧即为将EDX和EAX的内容反复填入IDT的条目中。 setup_idt: lea ignore_int,%edx movl $0x00080000,%eax movw %dx,%ax * selector = 0x0008 = cs * movw $0x8E00,%dx * interrupt gate - dpl=0, present *

    lea \_idt,%edi
    mov $256,%ecx

rp_sidt: movl %eax,(%edi) movl %edx,4(%edi) addl $8,%edi dec %ecx jne rp_sidt lidt idt_descr ret

From #

Linux内核设计的艺术

...