Linux 内核学习(二)- 进程管理

进程描述符

1、简介

具体进程的信息保存在进程描述符 task_struct 中,在32位系统中有大约1.7KB,结构定义在 <linux/sched.h> 中,包含的进程信息有:进程状态(volatile long state),优先级(int prio),调度实体(struct sched_entity se),进程地址空间(struct mm_struct *mm, *active_mm),进程退出状态,进程号(pid_t pid),线程组号(pid_t tgid)①,时间记账,打开的文件描述符,信号等等。

不同进程描述符使用双向链表(list_head)组织。Linux通过slab分配 task_struct 结构。

2、thread_info

x86下,Linux在内核栈尾端(内核栈最低内存地址)分配 thread_info 结构②,用于更快的查找进程描述符并节省内核栈空间:

struct thread_info {
    struct task_struct * task;
    ...
};

其中的task属性指向该进程的进程描述符,Linux使用 current_thread_info() 函数获得当前运行进程的 thread_info 对象,使用 current 宏(调用 current_thread_info() 函数)获得当前运行进程的进程描述符。

x86下 current_thread_info() 核心代码为 

return (struct thread_info *)(sp & ~(THREAD_SIZE - 1));

其中sp为内核栈指针,一般Linux为内核栈分配1页的空间(早期有可能是2页),THREAD_SIZE为内核栈大小,栈最低地址必定是栈大小的整数倍,‘与’操作即可返回栈最低地址 。在其他的一些有较多寄存器的体系结构中,thread_info 可以保存在寄存器里,这样访问更快。

3、进程状态

进程包含若干种状态: 

  • TASK_RUNNING:进程正在运行或者已经就绪(在调度队列中等待CPU调度)
  • TASK_INTERRUPTIBLE:进程睡眠,等待某些条件达成(收到信号、中断)
  • TASK_UNINTERUPTIBLE:同上,唯一区别是进程不会被信号唤醒,比较罕见,说明该进程正在进行重要的操作
  • __TASK_TRACED:调试进程时的状态
  • __TASK_STOPPED:进程停止运行,如收到SIGSTOP信号

在执行 ps 命令后,R代表正在运行或就绪的进程,S代表可中断睡眠的进程,D代表不可中断睡眠的进程

内核通过 set_task_state(task, state) 改变进程的状态

4、进程树

Linux的进程存在明显的继承关系,每个进程有一个父进程,有0个或多个子进程

可以通过 task_struct 结构中的 parent 属性获得父进程的进程描述符,init进程的进程描述符是 init_task,遍历子进程方法如下:

struct task_struct *task;
struct list_head *list;
3
list_for_each(list, &current->children) {
    task = list_entry(list, struct task_struct, sibling);
    /* task 指向某个子进程 */
}

进程关系图

进程创建

Linux使用 fork() 函数通过拷贝当前进程创建子进程,调用 exec() 函数读入可执行文件并载入到地址空间运行。由于存在写时拷贝,fork 的实际开销只有复制父进程页表和创建子进程的进程描述符。

fork系列函数(vfork,clone等)都会调用do_fork(),do_fork()定义在 kernel/fork.c 中,do_fork会调用 copy_process() 函数,此函数真正进行复制操作,具体流程如下:

  1.  调用 dup_task_struct() 返回一个新的进程描述符,创建新的内核栈、thread_info结构
  2.  检测进程数量是否超过最大进程数
  3.  初始化子进程和兄弟进程链表,时间记账等等
  4.  复制进程信息,如文件描述符、进程地址空间等等
  5.  调用alloc_pid()分配有效的pid
  6.  做扫尾工作,返回子进程进程描述符指针

线程创建

线程创建与进程创建类似,只是传给clone的参数不一样,共享地址空间、文件系统资源、文件描述符、信号处理程序的几个进程即被称作线程

进程终结

当一个进程终结时(正常调用exit()退出或者出现异常),内核会调用 do_exit() 函数释放进程资源并通知父进程,具体流程如下:

  1.  设置进程标志(tsk->flags)为 PF_EXITING:exit_signals(tsk);  
  2.  设置退出码: tsk->exit_code = code
  3.  释放进程的地址空间: exit_mm(tsk);
  4.  释放进程占用的文件、文件系统资源
  5.  为子进程找到新的父亲(同一线程组的其他线程或者init进程)
  6.  发送信号通知父进程
  7.  调用 schedule() 切换到其他进程

踩到的坑

1、可以通过调用 vfork 系统调用来产生一个 TASK_UNINTERUPTIBLE 的进程。调用该系统调用后,父进程会等待子进程运行结束或调用了exec后再运行,此时父进程即处于不可中断睡眠状态。但我们仍然可以通过给父进程发送信号来终止父进程。原因是linux 2.6.25引入了新状态TASK_KILLABLE③。

参考资料

①  pid和tgid的区别

② How does the kernel know what is the current thread?

③ TASK_KILLABLE:Linux 中的新进程状态

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值