《Linux内核设计与实现》
笔记系列一
进程创建
许多其他的操作系统都提供了产生(spawn)
进程的机制,首先在新的地址空间里创建进程,读入可执行文件,最后开始执行。
unix
中才用把上述步骤分解为两步:
- fork( ),通过拷贝当前进程创建一个子进程。
- exec( ),负责读取可执行文件并将其载入到地址空间开始运行。
写时拷贝(copy-on-write COW)
Linux
中的fork()
使用了copy-on-write
页实现。
COW
只有在需要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝。
具体来说:
当进程A使用系统调用
fork()
创建子进程B时,由于子进程B实际上是父进程A的一个拷贝.因此会拥有与父进程相同的物理页面
.为了节约内存和加快进程的创建速度,fork()
会让子进程以只读的方式共享父进程A的物理页面
,同时也将父进程A对这些物理页面的访问权限设置为只读
.这样,当父进程A或子进程B任何一方对这些已共享的物理页面执行写操作时,都会产生页面出错异常(page_fault)
中断,此时CPU
会执行系统提供的异常处理函数do_wp_page()
来解决这个异常.
do_wp_page()
会对这块导致写入异常中断的物理页面进行取消共享操作,为写进程复制一份新的物理页面
,使得进程A与进程B各自拥有一块相同的物理页面,最后从异常处理函数中返回时,CPU
就会重新执行刚才导致异常的写入操作指令,使进程继续下去.
fork()
的实际开销主要是复制父进程的页表以及子进程创建唯一的task_struct(进程描述符)
.在一般情况下,进程创建后会马上运行一个可执行文件,这种优化可以避免拷贝根本不会被使用的数据.
fork()、vfork()和clone() 之间的关系
系统调用 | 描述 |
---|---|
fork() |
fork 创造的子进程是父进程的完整副本,复制了父亲进程的资源,包括内存的内容task_struct 内容 |
vfork() |
vfork 创建的子进程与父进程共享数据段,而且由vfork() 创建的子进程将先于父进程运行 |
clone() |
linux上创建线程一般使用的是pthread库 实际上linux也给我们提供了创建线程的系统调用,就是clone |
fork
1 |
|
从运行结果可以看出父子进程的PID
不同,堆栈和数据资源都是完全相同。
当父进程改变cnt
的值并不会影响子进程中的cnt
的值。
vfork
vfork()
的做法较之fork()
更加粗暴,内核连子进程的虚拟地址空间结构也不创建,直接共享了父进程的虚拟地址空间。
1 |
|
vfork()
保证了子进程先运行(父进程被挂起),再子进程调用exec
和_exit
后父进程才有可能被调度.
从运行结果可以看出: 子进程对cnt
加一,父进程中的cnt
也加一,说明两者的cnt
在同一内存.
另外在vfork()
中退出一定要使用_exit()
而不要使用return.
vfork()
时父子进程共享数据段,如果vfork()
的子进程中使用return
,父进程去访问数据将会出现段错误.
这是因为如果vfork()
生成的子进程在函数中使用return
就结束了函数,其函数的栈空间全部被系统回收,父进程再去访问原来的数据就访问错误的地址,导致段错误.
exit 与 return 的区别
exit(0)
:正常运行程序并退出程序;
exit(1)
:非正常运行导致退出程序;
return()
:返回函数,若在主函数中,则会退出函数并返回一个值。]
return是语言级别的,它表示了调用堆栈的返回;而exit是系统调用级别的,它表示了一个进程的结束。
return是函数的退出(返回);exit是进程的退出。
return用于结束一个函数的执行,将函数的执行信息传出个其他调用函数使用;exit函数是退出应用程序,删除进程使用的内存空间,并将应用程序的一个状态返回给OS,这个状态标识了应用程序的一些运行信息,这个信息和机器和操作系统有关,一般是 0 为正常退出,非0 为非正常退出。
非主函数中调用return和exit效果很明显,但是在main函数中调用return和exit的现象就很模糊,多数情况下现象都是一致的。
linux中的线程
linux
中把所有的线程都当做进程来实现,内核并没有特别的调度算法或是定义特别的数据结构来表征线程.相反,线程仅仅是被视为一个与其他进程共享某些资源的进程。每个线程都拥有唯一隶属自己的task_struct
,所以在内核中,它看起来就像一个普通的进程。(只是线程和其他一些进程共享某些资源)
对于Linux
来说,线程只是进程间的一种共享资源的手段(linux中的进程本身已经够轻量级了)。
linux
中线程的实现方式与进程一样,只不过通过对clone()
参数指定来使得多个进程之间共享某些资源(如虚拟内存、页表、文件描述符),函数调用栈、寄存器等线程私有数据则独立。
1 | // 创建线程 |
进程与线程
在linux
中进程和线程实际上都是使用一个结构体task_struct
来表示一个执行任务的实体。进程创建调用fork()
系统调用,而线程创建则是pthread_create()
方法,但是这两个方法最终都会调用do_fork()
函数来创建操作,区别就送在传入的参数不同。
参考链接: