《linux内核设计与实现》学习笔记3进程管理

 

1.    进程管理

进程包括可执行代码,打开的文件,挂起的信号,处理器状态,内核内部数据,地址空间,拥有的线程,存储全局变量的数据段。

linux对进程和线程不特别区分

linux中通常用fork()系统调用复制一个进程创建一个新的进程,分别为父进程和子进程。之后父子进程同时执行。

最后通过exit()系统调用退出执行。父进程可以通过wait4()查询子进程是否结束。父进程调用wait()wait4()waitid()之前,退出执行的进程设置为僵死状态。

1.1      进程描述符及任务结构

Struct task_struct Process descriptor;它包括了描述进程的完整信息。

任务队列是装了Process descriptortask_struct结构体。

1.1.1.分配进程描述符

通过slab分配器分配task_struct结构。实现对象复用和缓存着色。

创建时只需在进程内核栈栈底创建thread_info(包含一个指向task_struct的指针)。

1.1.2.Process descriptor的存放

Task_struct中定义了pid_t pid;用于存放唯一进程标识值。

通过current_thread_info()->task返回当前进程的task_struct地址。

PowerPC的寄存器多,所以可以直接把task_struct的地址存于寄存器中,而x86只有通过current_thread_info()函数完成,还是比较快。

1.1.3.进程状态

Task_struct中的state保存了进程了五种基本状态:Task_runningTask_interruptibleTask_uninteruptibleTask_zombleTask_stop

其中就绪状态分为了可以接收信号而被唤醒的task_interruptible状态和不可被信号唤醒的task_uninterruptible状态。Task_stop通常发生在接收到SIGSTOPSIGTSTPSIGTTINSIGTTOUT信号的时候。

1.1.4.设置当前进程状态

Set_task_state(tase,state)

Set_current_state(state);等价于set_task_state(current,state)

1.1.5.进程上下文

系统调用和异常处理程序是内核明确定义的接口,只有通过他们才能陷入内核执行,访问内核数据。

陷入内核执行后,就称内核代表进程执行,并处于进程上下文中,Current宏此时有用。若调度器没有做出调整,内核执行完后恢复用户空间执行。

1.1.6. 进程家族树

所有进程都是PID为的init进程的后代

获得父进程:struct task_struct *my_parent=currnet->parent;

依次访问子进程:list_for_each(list,$current->children){

                  task=list_entry(list,struct task_struct,sibling);}

获取链表下一个或上一个进程:list_entry(task->task.next,struct task_struct,tasks)

                            list_entry(task->task.prev,struct task_struct,tasks)

遍历进程:struct task_struct *task

          for_each_process(task){

                     printf(%s:[%d]/n,task->comm,task->pid);//打印每个进程的名称和PID

1.2      进程创建:

Fork()负责拷贝当前进程创建新进程。Exec()负责读取可执行文件载入地址空间运行。

1.2.1.写时拷贝

Fork()只有在需要写入时才进行资源的复制,在此之前子进程只读共享父进程资源。

注意:只调用fork时父子进程共享进程地址空间,只要谁对数据进行了写操作,就会新建地址空间,并且拷贝其他数据,至此,两个进程有各自独立的拷贝。如果立即调用了ecec族函数,就会立即创建地址空间,载入新的可执行文件,至此,两个进程有了各自独立的拷贝。

 

1.2.2.Fork()

Fork(),vfork(),__clone都是通过调用不同参数的clone()系统调用实现的,这些参数决定了父子进程之间需要共享的资源。然后通clone()调用do_fork(),do_fork()调用copy_process()完成大部分创建工作。

Copy_process()的过程:

1、  调用dup_task_struct()为新进程创建一个内核栈,尾加thread_info(),创建task_struct,值完全和父进程相同。注意这些东西都是要新建并复制的,并不能共享,所谓的写时拷贝不是指的它们。

2、  出错检查,看进程数目有没有超出限制

3、  区分父子进程,清理或者设置某些task_struct成员

4、  设当前进程为不可中断的睡眠状态

5、  调用copy_flag(),清零PF_SUPERPRIV表明新进程不再具有超级用户权限,设置PF_FORKNOEXEC表明新进程还未执行ecec()

6、  get_pid()获取一个PID

7、  根据clone的参数标志,复制不应共享的东西。例如要新建一个线程,clone就会带CLONE_VM CLONE_FS CLONE_FILES CLONE_SIGHAND标志指明要共享的东西,这些东西就不会被复制。创建普通fork实际上的标志是SIGCHLDvfork的标志是CLONE_VFORK CLONE_VM SIGCHLD.

8、  父子进程平分剩余时间片

9、  扫尾并返回指向子进程的指针

返回do_fork()有意让子进程先执行,因为很可能exec马上就会执行,这样就避免了父进程的写操作引发了写时拷贝。

1.2.3.vfork

所有都和fork()一样,除了不拷贝页表项,父进程一直阻塞,直到子进程退出或者调用exec,子进程不能向地址空间写入。

1.3      线程在linux中的实现

线程只是一个能和其他进程共享地址空间,文件系统信息,文件描述符,信号处理函数的特殊进程。而创建过程和普通进程创建过程类似,只是clone()的参数会表明它们能够共享的东西。

附共享标志表:

 

 

1.3.1.内核线程

内核在后台执行的操作,始终运行于内核空间。

由其他内核线程调用:

int kernel_thread(int (*fn)(void *),void *arg,unsigned log flags)

返回时父线程退出,*fn*arg参数开始执行。

通常它会一直执行,直到系统退出,需要时唤醒,完成时休眠。

1.4      进程终结

主动结束:显示或者隐式调用exit()

被动结束:信号或者异常。

无论如何都会调用do_exit()完成:

1、  设置task_struct中的PF_EXITING

2、  删除内核定时器,确保没有定时器在排队

3、  如果要进程记账,调用acct_process()

4、  调用_exit_mm()放弃mm_struct.【如果没有被共享就彻底放弃】

5、  调用_exit_sem()【如果进程在等待IPC信号,离开队列】

6、  调用_exit_files() _exit_fs() _exit_namespace() _exit_sighand()递减文件描述符,文件系统数据,进程命名空间,信号处理函数计数器。【如果已经为零,则可以释放】

7、  执行规定的退出动作或者置task_struct中的退出码给exit()

8、  调用exit_notify()通知父进程,该状态为TASK_ZOMBIE

9、  调用schedule()切换到其他进程

此时该进程只剩内核栈,thread_infotask_struct了,父进程处理后释放

1.4.1.删除进程描述符

父进程已经获取子进程结束信息或者通知内核不需要它们时,通过release_task()来释放task_struct

Release_task()完成的工作:

1.4.2.孤儿进程造成的进退维谷

如果父进程在子进程退出之前退出,则所有子进程将认同组的其他进程为父,如果没有,就认init为父

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值