linux内核设计与实现 LINUX内核源代码情景分析( 二 )

3. 进程创建3.1 基本概念

  • unix进程创建分为:fork和exec两步
  • fork通过拷贝当前进程创建子进程 。子进程和父进程仅有很少差异:pid,ppid,某些资源和统计量
  • exec负责读取可执行文件并载入地址空间开始运行
3.2 写时拷贝(COW)
  • 传统的fork直接拷贝资源效率低下,linux使用写时拷贝(copy on write)技术提高效率
  • COW并不会复制整个地址空间,而是让父子进程以只读方式共享内存,数据的复制只有在写入时才进行
3.3 fork函数
  • linux通过clone()系统调用实现fork(), 这个调用通过参数标识(很多种类型)指明需要共享的资源
  • clone内部调用do_fork完成主要工作(kernel/fork.c)
  • do_fork内部调用copy_process,然后让进程运行
  • copy_process调用过程: 调用dup_task_struct为新进程创建内核栈,thread_info结构和task_struct,这些值与当前进程相同,此时描述符完全相同 检查系统拥有的进程数是否超过限制 将很多成员重置 设置状态为TASK_UNINTERRUPTIBLE保证不会被运行 调用copy_flags以更新task_struct的flags成员 调用get_pid获取新的pid 根据参数标识,拷贝或共享打开的文件,文件系统信息,信号处理函数,进程地址空间,命名空间等 。一般情况下,这些资源是线程共享的 父子进程平分时间片 扫尾工作,并返回指向子进程的指针
  • 新创建的进程被唤醒并让其投入运行,一般优先子进程首先执行
3.4 vfork函数
  • 和fork功能相同,除了补考吧父进程的页表项
  • 通过向clone系统调用传递一个特殊标志进行的
  • 该函数的设计并不是很优良的
4. 线程在linux中的实现4.1 liunx线程概述
  • 一组线程共享进程内的内存地址空间,打开的文件和其他资源
  • 线程机制支持并发程序设计技术,多处理器上保证真正的并行处理
  • linux实现线程的机制非常独特,从内核角度看,没有线程的概念
  • linux把所有线程都当做进程来实现,内核没有特别的调度算法或数据结构来表征线程,被视为一个使用某些共享资源的进程
  • 每个线程有自己的task_struct,就像一个普通的进程,这个进程和其他进程共享某些资源
  • 与其他系统(windows,solaris)实现差异巨大,这些系统内核专门提供线程的支持
4.2 linux线程创建
  • 线程的创建和普通进程创建类型,只不过调用clone时需要传递一些参数标志,指明需要共享的资源
  • 参数标志说明: CLONE_VM:父子进程共享地址空间 CLONE_SIGHAND:父子进程共享信号处理函数 CLONE_THREAD:父子进程放入相同线程组 CLONE_FS:父子进程共享文件系统信息 CLONE_FILES:共享打开的文件 …
4.3 内核线程
  • 内核线程:独立运行在内核空间的标准进程
  • 和普通进程的区别:没有独立的地址空间,只能在内核空间运行
  • 创建只能由其他内核线程创建,函数为kernel_thread
4.4 进程终结释放资源
  • 进程终结时,内核必须释放它所占有的资源,并通知父进程
  • 结束可以是正常,异常,还可以注册终止清理函数
  • 最终结束会调用do_exit(kenel/exit.c),完成的工作包括: 将task_struct的标志成员设置为PF_EXITING 如果进程会计功能开启,会调用acct_process输出统计信息 调用_exit_mm函数放弃进程占用的mm_struct,如果没有被共享就彻底释放 调用sem_exit 。如果排队等待IPC信号,则离开队列 调用__exit_files:递减文件描述符;__exit_fs:递减文件系统数据;exit_namespace:名字空间引用计数;exit_sighand:信号处理函数引用计数,如果某个降为0,则可释放 task_struct的exit_code设置退出代码 调用exit_notify向进程发送信号,父进程修改为其他线程或init进程,进程状态设置为TASK_ZOMBLE(僵死,不被调度) 最后,调用schedule切换到其他进程
  • 调用完do_exit,与进程相关的所有资源都被释放了,它所占有的资源只剩报错thread_info的内核栈和保存task_struct的那一小片slab,存在的唯一目的就是向父进程提供信息 。
删除进程描述符