linux 进程栈 shell查看进程

各位老铁们,大家好,今天由我来为大家分享linux 进程栈,以及shell查看进程的相关问题知识,希望对大家有所帮助。如果可以帮助到大家,还望关注收藏下本站,您的支持是我们最大的动力,谢谢大家了哈,下面我们开始吧!

进程内核栈,用户栈及 Linux 进程栈和线程栈的区别

总结:线程栈的空间开辟在所属进程的堆区,线程与其所属的进程共享进程的用户空间,所以线程栈之间可以互访。线程栈的起始地址和大小存放在pthread_attr_t中,栈的大小并不是用来判断栈是否越界,而是用来初始化避免栈溢出的缓冲区的大小(或者说安全间隙的大小)

进程内核栈、用户栈

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结构体已经不在内核栈空间里面了。

Linux进程虚拟地址空间的分布,以及堆和栈的区别

一、具体分布如图所示:

二、关于堆和栈

(1)分配方式:

栈:由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

堆:一般由程序员分配释放,它的分配方式类似于链表。

(2)申请后系统的响应:

栈:只要所申请的空间小于栈的剩余空间,则系统为程序分配内存,否则栈溢出。

堆:操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,遍历该链表,找出第一个大于所申请空间的节点,然后将其从链表中删除并分配,如果没用完,则系统会把多余的重新放回到链表中。

(3)申请大小的限制:

栈:栈是高地址向低地址扩展的连续内存,栈的大小一般是2M;

堆:堆是低地址向高地址扩展的不连续内存,堆的大小与计算机有效的虚拟内存有关系。

(4)申请效率:

栈:由系统自动分配,速度较快;

堆:速度慢,容易产生内存碎片;

关于Linux命令的介绍,看看《linux就该这么学》,具体关于这一章地址3w(dot)linuxprobe/chapter-02(dot)html.

Linux下0号进程的前世今生

Linux中的特殊进程世界里,0号进程有着不平凡的旅程。首先,让我们探寻idle进程的起源。当内核初始化开始,系统生成的第一个进程,即pid=0的idle进程,其存在并非通过fork(),而是系统固有的产物。在SMP系统中,每个处理器都有独立的运行队列,每个队列上都驻留着一个idle进程,数量与处理器数量一致。

idle进程的标志性存在是init_task,它是所有进程和线程task_struct的原型,由内核静态定义并命名为init_task。它的进程描述符init_task_desc通过INIT_TASK宏初始化,其comm字段为swapper。在进程生命周期中,init_task先创建kernel_init进程,然后自己退化为cpu_idle进程,负责在无其他进程时占用CPU资源。

init_task的内存管理复杂,它使用init_thread_union作为堆栈空间,并与thread_info共享内存。进程堆栈和虚拟地址空间的初始化同样由内核控制,如arm体系结构下的定义。随着系统启动,rest_init函数启动了进程创建的进程,包括init进程(PID=1)和kthread进程(PID=2)。

在创建了init进程后,0号进程(pid=0)通过调用cpu_idle(),进入idle进程状态,init_task随之变成idle。在这个过程中,init_task被置于idle调度类,其行为主要在cpu_idle_loop中,通过检查need_resched来节能,或者使用hlt指令进入暂停状态。

在调度策略上,当rt和cfs调度器没有任务时,idle进程才有机会运行。idle进程的调度是由cfs公平调度器和idle_sched_class协作完成的。在系统空闲时,idle进程会通过pick_next_task函数被调度。

总结来说,0号进程,无论是作为init_task还是idle,都是Linux进程结构的基石。它通过一系列的初始化和演变,不仅启动了系统进程,还为后续的进程创建和调度奠定了基础。在系统中,它既是所有进程的祖先,也是CPU空闲时的守护者,维护着系统的稳定运行。

阅读剩余
THE END