linux内核 进程如何销毁

linux内核 进程如何销毁,第1张

要了解进程的终止,你必须先了解进程结构体task_struct,期中有一些与进程终止相关的非常重要的域,task_struct在内核源代码的linux/sched.h中定义。

进程终止有多种方式,无论何种方式,都会调用内核函数sys_exit()函数,而最终调用do_exit()函数(在kernel/exit.c中定义)在do_exit()函数中首先会做一些检测,包括确保要终止的进程ID不是0或者1,再保证该进程不是处在中断处理程序之中,之后将要终止进程的task_struct结构体中的flag域设置为PF_EXITING表示该进程已经结束,然后就是比较正式的销毁过程,释放进程占用的资源。比如释放其占用的的分配内存(exit_mm()),释放进程间通信的信号量(exit_sem()),释放进程创建或者打开的文件描述符(exit_files()),释放文件系统数据(exit_fs()),释放线程(exit_thread())等等,最后向其父进程发送SIGCHILD信号,将进程状态设置为终止,调用schedule(),将CPU控制权交给其他进程。

这是大概的销毁过程的,更详细的过程请参考linux

kernel

primer这本书以及linux内核源代码。

您好,很高兴为您解答。

1.进程的堆栈

  内核在创建进程的时候,在创建task_struct的同事,会为进程创建相应的堆栈。每个进程会有两个栈,一个用户栈,存在于用户空间,一个内核栈,存在于内核空间。当进程在用户空间运行时,cpu堆栈指针寄存器里面的内容是用户堆栈地址,使用用户栈;当进程在内核空间时,cpu堆栈指针寄存器里面的内容是内核栈空间地址,使用内核栈。

2.进程用户栈和内核栈的切换

  当进程因为中断或者系统调用而陷入内核态之行时,进程所使用的堆栈也要从用户栈转到内核栈。

  进程陷入内核态后,先把用户态堆栈的地址保存在内核栈之中,然后设置堆栈指针寄存器的内容为内核栈的地址,这样就完成了用户栈向内核栈的转换;当进程从内核态恢复到用户态之行时,在内核态之行的最后将保存在内核栈里面的用户栈的地址恢复到堆栈指针寄存器即可。这样就实现了内核栈和用户栈的互转。

  那么,我们知道从内核转到用户态时用户栈的地址是在陷入内核的时候保存在内核栈里面的,但是在陷入内核的时候,我们是如何知道内核栈的地址的呢?

  关键在进程从用户态转到内核态的时候,进程的内核栈总是空的。这是因为,当进程在用户态运行时,使用的是用户栈,当进程陷入到内核态时,内核栈保存进程在内核态运行的相关信心,但是一旦进程返回到用户态后,内核栈中保存的信息无效,会全部恢复,因此每次进程从用户态陷入内核的时候得到的内核栈都是空的。所以在进程陷入内核的时候,直接把内核栈的栈顶地址给堆栈指针寄存器就可以了。

3.内核栈的实现

      内核栈在kernel-2.4和kernel-2.6里面的实现方式是不一样的。

在kernel-2.4内核里面,内核栈的实现是:

 Union task_union {

                  Struct task_struct task

                  Unsigned long stack[INIT_STACK_SIZE/sizeof(long)]

 }

其中,INIT_STACK_SIZE的大小只能是8K。

  内核为每个进程分配task_struct结构体的时候,实际上分配两个连续的物理页面,底部用作task_struct结构体,结构上面的用作堆栈。使用current()宏能够访问当前正在运行的进程描述符。

注意:这个时候task_struct结构是在内核栈里面的,内核栈的实际能用大小大概有7K。

内核栈在kernel-2.6里面的实现是(kernel-2.6.32):

Union thread_union {

                  Struct thread_info thread_info;

                  Unsigned long stack[THREAD_SIZE/sizeof(long)]

 }

其中THREAD_SIZE的大小可以是4K,也可以是8K,thread_info占52bytes。

  当内核栈为8K时,Thread_info在这块内存的起始地址,内核栈从堆栈末端向下增长。所以此时,kernel-2.6中的current宏是需要更改的。要通过thread_info结构体中的task_struct域来获得于thread_info相关联的task。更详细的参考相应的current宏的实现。

 struct thread_info {

                  struct task_struct *task;

                  struct exec_domain *exec_domain;

                  __u32 flags;

        __u32 status

                  __u32 cpu

                  …  ..

 }

注意:此时的task_struct结构体已经不在内核栈空间里面了。

如若满意,请点击右侧【采纳答案】,如若还有问题,请点击【追问】

希望我的回答对您有所帮助,望采纳!

                                                                                                                            ~ O(∩_∩)O~

Linux内核栈溢出(stack overflow)问题

最近一段时间在设计和开发一个Linux内核模块,进入到最后的正确性测试与稳定性测试阶段。在这个阶段发现了一个非常有意思的问题,堆栈溢出(stack overflow)。Linux内核堆栈溢出之后直接导致了系统kernel Panic。由于导致stack overflow的原因是递归调用导致的,所以,最后通过调试串口导出的kernel panic信息很快就定位问题所在了,否则这样的问题还真是很难调试和发现。通过这次bug,我们应该记住的是:Linux内核stack资源是有限的,而递归调用将大量消耗stack资源,因此在内核编程中尽量少用递归算法,否则将会导致出乎意料的一些问题。依次类推,为了减少stack资源的消耗,程序的局部变量定义的不要太大,否则也将会消耗大量stack资源,从而导致内核程序的不稳定。

为了解决递归调用导致的问题,我将递归算法改写成了非递归算法,解决了stack overflow的问题。在此介绍一下递归算法改写成非递归算法的一些思想。在项目实现过程中,需要对IO请求进行按顺序排队,因此采用了效率较高并且实现简单的快速排序算法,该算法是一种分治算法,即将排序队列进行切分,分解成一系列的小问题进行求解,针对这种问题,很容易采用递归的办法进行实现,伪代码描述如下:

/* qs_sort实现从小到大的排序 */

Struct bio qs_sort(struct bio_list *list_head, struct bio *bio_tail) {

Struct bio_list *less_list, *large_list

Struct bio *middle_bio

/* 递归调用结束点,小问题求解完毕,直接返回最后一个元素 */

If (!list_head) {

Return bio_tail

}

/* 对队列进行切分,选择一个middle_bio,并且按照middle_bio将其切分成less_list队列和large_list队列 */

Split_list(list_head, less_list, large_list, &middle_bio)

/* 采用递归的方法实现大队列的排序 *** 作 */

Middle_bio->bi_next = qs_sort(large_list, bio_tail)

/* 采用递归的方法实现小队列的排序 *** 作 */

Return qs_sort(less_list, middle_bio)

}


欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/yw/7920544.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-04-11
下一篇2023-04-11

发表评论

登录后才能评论

评论列表(0条)

    保存