在internet services课上老师说到Capriccio 是用户模式下的thread library,OS课里第2个project也是实现一个用户模式下的线程库。之前用过POSIX库,我知道这是在内核模式里的线程库,那就表示由内核来创建、调度线程吧。但内核就像一个黑盒,一直不明白它做了什么,怎么做到的。Modern Operating System有章讲user space和kernel space控制线程。
在user space:所有线程的管理都在用户区,内核不知道多线程的存在。在用户区实现的好处是,可以在不支持多线程的操作系统上运行多线程,减少在用户模式和内核模式间切换,可以用应用程序来进行线程调度(比如有个garbage collector线程,我们希望这个线程不会打扰其他线程的执行)。坏处是,如果某个线程里发生了一个阻塞的系统调用,系统切换到内核态。但由于线程管理在用户态,内核不知道多线程的存在,不知道除了当前被阻塞的这个线程,还有其他线程想要执行,所以任由该线程阻塞,直到得到结果、阻塞结束或者分给这个进程的时间片用完了。
在用户模式和内核模式间切换的过程发生了什么呢,为什么会有很大的开销?从发生一个系统调用开始说吧。单核CPU任何时间只能执行一条指令,当某个应用运行在用户态,它需要获取系统资源时,如从disk上读数据,它就必须转换到内核态、去执行内核里的代码。以调用read函数为例,首先应用调用C标准库里的read函数(library procedure),标准库函数里它发生一个系统调用,也叫read。在这个过程里,库函数把要发生的系统调用的编号存在某个地方如寄存器里,然后执行TRAP指令,从用户态进入内核态。TRAP和普通的函数调用类似,将函数指针指向另一个函数入口并把返回地址入栈,实现跳转。不同的是,TRAP后指针指向内核代码,普通函数调用是指向用户态的另一段代码;TRAP不能跳到一个任意的地址。接下来,内核会检查之前放在寄存器里的系统调用的编号,通过查表跳到对应的处理函数里。等系统调用完成了,重新回到用户态。
http://www.cnblogs.com/gtarcoder/articles/5278074.html
哪些代码是属于内核的?为什么要把指令分为两种等级,用户态和内核态?
在kenel space:在内核区进行线程调用的话,若某个线程发生阻塞型的系统调用,内核会切换到另一个线程,解决了用户区的问题。在多线程运行中,有如何处理信号的问题。信号是发送给一个进程,而不是单个线程。所以当收到一个信号,由哪个线程来处理呢?书里简单介绍,不同的线程注册不同的信号,所以某个信号会由对它感兴趣的那个线程来处理。
之前不明白,怎样就是创建了一个新的线程呢?思考一下什么构成了一个线程,线程可以由哪些来表示?当然是线程状态了,包括函数计数器,线程栈,一些寄存器值。就像一个snapshot,能从这些数据重新恢复一个暂停的线程。所以在thread library里,要保存下每个线程的snapshots,调出某个snapshot,恢复寄存器的各种状态,从函数计数器指向的地方开始执行。
大部分操作系统在存储上都用了虚拟。代码看见的地址都是虚拟地址,不论是用户态还是内核态,每个进程有自己的页目录和页表,页表里记录了虚拟地址到物理地址的映射。为什么要用虚拟地址?首先虚拟空间可以大于物理空间、即内存的大小。可能会有疑问,因为一个进程用的是虚拟地址,如果它用到的地址空间大于内存空间,怎么正常运行?实际上,把需要用到的数据划成很多固定大小的页,在某个执行时刻,并不是所有的页都要用到。内存中只需要存当前用到的页面。当某个时候进程请求的页不在内存中,产生page fault,操作系统将把对应的页从磁盘加载到内存里,替换掉不需要的页。另外,虚拟地址给一个进程提供了一块连续的地址空间。
什么时候操作系统需要处理页?创建进程、执行进程、页故障、结束进程时。
数据库里如何保证一个交易的原子性呢?原子性表示一个交易或者发生、或者不发生,all or none. 故障可能发生在交易进行的过程中,原子性不允许这种交易完成一半的情况,所以要么继续进行、完成交易,要么回退到最初的状态。
一种办法是deferred-modification,就像ds第二个project里cache时采用copy-on-write。在这里,要更新时,先创建一个数据库的副本,在副本上更新,到commit时,把指向原数据库的指针更新为指向最新副本的指针。如果在更新之前发生故障,重启后还是指向原数据库,相当于交易abort,如果更新之后发生故障,重启后指向更新的数据库,交易成功commit。但在这里需要保证指针更新的原子性,这个原子性可以由disk硬件来保证。
一种办法是直接在原数据上修改,但为每个修改都创建一条日志。可以根据日志判断某个交易进行到哪个状态,如果需要回退,就是撤销之前的操作。日志需要及时写入disk,保证不会在故障中消失。