精华内容
下载资源
问答
  • 进程栈与线程栈的关系

    千次阅读 2018-05-24 13:33:49
    本文转自: ...amp;tid=2018590&pid=24691312&page=1&extra=page%3D1#pid24691312 ...大家好,本人被下面这个问题困扰...这里和大家分享一下,可能对有相同困惑同学有点帮助,同时也请各位帮忙...

    本文转自:
    http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=2018590&pid=24691312&page=1&extra=page%3D1#pid24691312

    大家好,本人被下面这个问题困扰了一段时间,最近似乎找到了答案。
    这里和大家分享一下,可能对有相同困惑的同学有点帮助,同时也请各位帮忙看看错漏的地方。

    1================问题:
    在使用pthread库创建两个线程时clone()被调用了两次,可以用strace 看到:
    int
    main()
    {

    err=pthread_create(&tid, NULL, job, NULL);
    err=pthread_create(&tid1, NULL, job, NULL);

    }

    strace:
    clone(Thread is running.
    child_stack=0xb7efb4b4, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0xb7efbbd8, {entry_number:6, base_addr:0xb7efbb90, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}, child_tidptr=0xb7efbbd = 5104

    clone(child_stack=0xb76fa4b4, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0xb76fabd8, {entry_number:6, base_addr:0xb76fab90, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}, child_tidptr=0xb76fabd = 5105

    大家都知道clone()可以产生一个所谓的“轻量级进程”,也就是有独立的task_struct的,独立调度的东西。
    再看看flag:CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID
    这里对这些flag就不多说了对于我这个问题,最为重要的是CLONE_VM(共享内存描述符mm_struct和所有的页表)。
    这点可以在copy_mm()里面看得到(在clone()中被调用):
    static int copy_mm(unsigned long clone_flags, struct task_struct * tsk)
    {

    if (clone_flags & CLONE_VM) {
    atomic_inc(&oldmm->mm_users);
    mm = oldmm;
    goto good_mm;
    }

    }

    这样我这里两个pthread和原来的main进程就共享同一个mm_struct了,如图1:
    这里写图片描述
    pthread线程之间的栈必定是独立的,不然不可能被独立调度,我的问题就在这里,3个task_struct 共用一个mm_struct,那么他们的栈应该怎么办?难道在一个栈里面?

    2======================解答:
    问题主要在两个函数上面,一个是clone()系统调用,一个是pthread_create()
    man clone,看他的参数。参数列表中最为显眼的就是第二个参数void *child_stack,man里面是这样形容它的:
    The child_stack argument specifies the location of the stack used by the child process. Since the child
    and calling process may share memory, it is not possible for the child process to execute in the same
    stack as the calling process.
    调用clone()的时候是要自己提供子task的栈空间的,这个系统调用实在是太特别了。
    在看看pthread的库是怎样给这个参数赋值的:
    函数pthread_handle_create() (manager.c里面) 被调用来创建新线程,在这个函数里面调用了pthread_allocate_stack()
    来分配栈空间:
    static int pthread_allocate_stack(const pthread_attr_t *attr,
    pthread_descr default_new_thread,
    int pagesize,
    char ** out_new_thread,
    char ** out_new_thread_bottom,
    char ** out_guardaddr,
    size_t * out_guardsize,
    size_t * out_stacksize)
    {

    map_addr = mmap(NULL, stacksize + guardsize,
    PROT_READ | PROT_WRITE | PROT_EXEC,
    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (map_addr == MAP_FAILED)
    /* No more memory available. */
    return -1;

    }

    可以看到pthread库是通过调用mmap()来为新的线程创建栈空间的,可以在细看一下它的参数,它使用了flag
    MAP_ANONYMOUS和MAP_PRIVATE,而且fd参数中用了-1,这样调用的结果是在进程空间中创建一个匿名的
    线性区,这样就有了栈空间,而且这个空间也在原来的mm_struct里面,如图2:
    这里写图片描述
    光有空间不行,还要看看内核是怎样使用,我们来到copy_thread(),它在copy_process()中被调用(它们都在clone()里面被调用的):
    static struct task_struct *copy_process(unsigned long clone_flags,
    unsigned long stack_start,
    struct pt_regs *regs,
    unsigned long stack_size,
    int __user *child_tidptr,
    struct pid *pid)
    {

    retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs);

    }

    int copy_thread(int nr, unsigned long clone_flags, unsigned long sp,
    unsigned long unused,
    struct task_struct * p, struct pt_regs * regs)
    {
    struct pt_regs * childregs;
    struct task_struct *tsk;
    int err;

        childregs = task_pt_regs(p);
        *childregs = *regs;
        childregs->ax = 0;
        childregs->sp = sp;
    


    }

    这里可以清楚的看到,内核为这个用户线程初始化他未来的sp寄存器值,就是刚才mmap()返回的那个地址。所以结果
    如图3:
    这里写图片描述

    展开全文
  • 最近学习了下linux下进程和线程空间的分配原理,觉得有必要坐下总结, https://blog.csdn.net/elfprincexu/article/details/78779158 计算机是如何启动的 ... ...进程栈与线程栈的关系 https://blog.c...

    最近学习了下linux下进程和线程空间的分配原理,觉得有必要坐下总结,

    https://blog.csdn.net/elfprincexu/article/details/78779158

     

    计算机是如何启动的

    https://blog.csdn.net/elfprincexu/article/details/62891811

     

    进程栈与线程栈的关系

    https://blog.csdn.net/mxgsgtc/article/details/80433215

     

    汇编语言学习小结

    https://blog.csdn.net/elfprincexu/article/details/76200234

     

     

     

    关于进程栈和线程栈总结:

     

        (1)进程栈大小时执行时确定的,与编译链接无关

        (2)进程栈大小是随机确认的,至少比线程栈要大,但不会超过2倍

        (3)线程栈是固定大小的,可以使用ulimit -a 查看,使用ulimit -s 修改

        (4)一般默认情况下,线程栈是在进程的堆中分配栈空间,每个线程拥有独立的栈空间,为了避免线程之间的栈空间踩踏,线程栈之间还会有以小块guardsize用来隔离保护各自的栈空间,一旦另一个线程踏入到这个隔离区,就会引发段错误。

     

     

    下面是一个比较简单的多线程程序。程序如下,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    
     
    1. int main()

    2. {

    3. int ret = 0;

    4. int i = 0;

    5. /* int stack_size = 128 * 1024; */

    6.  
    7. pthread_attr_t attr;

    8. /* pthread_attr_setstacksize(&attr, stack_size); */

    9.  
    10. for (i = 0; i < 3; i++) {

    11. ret = pthread_create(&thread[i], NULL, thread_func, NULL);

    12. if (ret) {

    13. perror("create");

    14. return -1;

    15. }

    16. }

    17.  
    18. while (1) {

    19. ;

    20. }

    21.  
    22. return 0;

    23. }

    24.  

    上图是我的测试程序,我创建了3个线程。程序运行以后,我们可以通过/ proc/PID/task来看该程序有多少线程在运行: 

    然后我们来看一下进程的地址空间。 /proc/PID/maps就是进程的地址空间。如下所示: 
     
    可以看出,进程的地址空间从低到高依次是:进程代码段(标志含有x)、只读数据段、可读写数据段、堆、mmap区(文件映射和匿名映射,其中有文件名的行是文件映射),栈。

    线程18438的栈:(0xb7570000-0xb6d70000)的值恰好是8M,线程栈默认大小是8M。(0xb6d70000-0xb6d6f000)的值是4K,这4K是保护页。

    为什么这三个线程的栈都是8M?可以从ulimit命令来得出,这是进程的资源限制:

    使用ulimit -a命令可以看出,进程资源限制中栈大小的限制是8194K,即8M.

    那么,这个8M大小是不是可以更改的?以及后面会什么会有一个4K大小的保护页?这可以从glibc代码里面来获取答案:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    
     
    1. 1.

    2. __pthread_create_2_1

    3. /* 这里分配线程栈 */

    4. ALLOCATE_STACK

    5.  
    6. 2.

    7. /* allocate_stack就是具体的分配线程栈的函数: */

    8. allocate_stack

    9. /*如果没有设置线程栈大小,就使用默认值*/

    10. size = attr > stacksize ?: __default_stacksize;

    11. /* ... */

    12.    /* Try to get a stack from the cache. */

    13.     pd = get_cached_stack (&size, &mem);

    14.     /* 如果没有从cache申请到,就要mmap申请一块内存 */

    15.     if (pd == NULL){

    16. /* MAP_PRIVATE | MAP_ANONYMOUS: 私有匿名映射 */

    17.         mmap (NULL, size, prot, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);

    18.        /* 接着设置一个保护区,该区域的页表属性是PROT_NONE(Page can not be accessed) */

    19.        mprotect(guard, guardsize, PROT_NONE);

    20.  

    对于设置为PROT_NONE的页,是不能访问的,那么访问到这个保护区时就出现错误,linux是靠这种机制来实现栈溢出保护的。

    下面我们来调整线程栈:

    • 设置pthread_attr属性

    可以看到此时的线程栈大小是: (0xb758f000-0xb756f000) = 128K.

     

     

    测试进程栈和线程栈大小: 

     

     

    查看线程栈大小:

    ulimit

    可以看到默认情况下线程栈大小为8192(8MB),可以使用ulimit -s xxx修改线程默认栈大小

    (1)检查线程栈默认大小(8KB)

    stacksize

        线程执行2030次之后,出现段错误(2030*4K=8120K)

    stacksize_error

     

    (2)修改栈大小,使用pthread_attr_setstack()

    setattribute

        如上修改栈大小为16MB,其中线程栈的空间从堆中进行分配

    setattribute_error

       程序执行4063次后出现段错误(4063*4KB)

     

    (3)创建两个线程,使用默认栈大小执行

    twothread

        创建两个线程,默认单个线程栈大小为8M

    twothread1_error

        执行结果1:程序执行4009次之后段错误(4009*4KB)

    twothread2_error

        执行结果2:程序执行3380次之后段错误(3380*4KB)

    总结:

            两个线程时,两个线程栈的总和不是固定值,也不是线程栈的2倍

     

    (3)不使用任何线程

    nothread

    nothread1_error

    执行结果1:程序执行2538次后段错误(2538*4KB)

    nothread2_error

    执行结果2:程序执行2537次后段错误(2537*4KB)

    总结:

        进程的栈大小不是固定的,而是比线程栈大一些

     

    (4)线程栈从进程栈中分配

    getstacksize

    getthreadsize1_error

    执行结果1:   程序执行2536次后段错误(2536*4KB>8M)

    getstacksize2_error

        执行结果2:程序执行2537次后段错误(2537*4KB>8M)

    总结:

        线程从进程栈分配空间,大小并不是固定的,如果分配空间大于进程栈空间,那么直接运行时出现段错误。

     

    展开全文
  • 线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少资源(如程序计数器,一组寄存器和),但是它可同属一个进程的其他的线程共享进程所拥有全部资源.  一个线程可以创建和撤销另一个线程;同

    进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 
    线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源. 
    一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.

    进程在执行过程中拥有独立的内存单元,而该进程的多个线程共享内存,从而极大地提高了程序的运行效率。 
    每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。 
    从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

    在很多现代操作系统中,一个进程的(虚)地址空间大小为4G,分为系统(内核?)空间和用户空间两部分,系统空间为所有进程共享,而用户空间是独立的,一般WINDOWS进程的用户空间为2G。 
    一个进程中的所有线程共享该进程的地址空间,但它们有各自独立的(/私有的)栈(stack),Windows线程的缺省堆栈大小为1M。堆(heap)的分配与栈有所不同,一般是一个进程有一个C运行时堆,这个堆为本进程中所有线程共享,windows进程还有所谓进程默认堆,用户也可以创建自己的堆。 
    用操作系统术语,线程切换的时候实际上切换的是一个可以称之为线程控制块的结构(TCB?),里面保存所有将来用于恢复线程环境必须的信息,包括所有必须保存的寄存器集,线程的状态等。

     

    堆: 是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。

    栈:是个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是 thread safe的。操作系统在切换线程的时候会自动的切换栈,就是切换 SS/ESP寄存器。栈空间不需要在高级语言里面显式的分配和释放。

    展开全文
  • 进程线程与栈、堆的关系

    千次阅读 2012-09-16 17:11:28
    线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少资源(如程序计数器,一组寄存器和),但是它可同属一个进程的其他的线程共享进程所拥有全部资源. 一个线程可以创建和撤销另一个线程

    进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
    线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源
    .
    一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.

    进程在执行过程中拥有独立的内存单元,而该进程的多个线程共享内存,从而极大地提高了程序的运行效率。
    每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
    从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

    在很多现代操作系统中,一个进程的(虚)地址空间大小为4G,分为系统(内核?)空间和用户空间两部分,系统空间为所有进程共享,而用户空间是独立的,一般WINDOWS进程的用户空间为2G。 [见《windows核心编程第13章》]

    一个进程中的所有线程共享该进程的地址空间,但它们有各自独立的(/私有的)栈(stack),Windows线程的缺省堆栈大小为1M。堆(heap)的分配与栈有所不同,一般是一个进程有一个C运行时堆,这个堆为本进程中所有线程共享,windows进程还有所谓进程默认堆,用户也可以创建自己的堆。
    用操作系统术语,线程切换的时候实际上切换的是一个可以称之为线程控制块的结构(TCB?),里面保存所有将来用于恢复线程环境必须的信息,包括所有必须保存的寄存器集,线程的状态等。

     

    堆: 是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。

    栈:是个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是 thread safe的。操作系统在切换线程的时候会自动的切换栈,就是切换 SS/ESP寄存器。栈空间不需要在高级语言里面显式的分配和释放。

    展开全文
  • 线程栈与进程

    2019-09-13 06:19:38
    注:本文所涉及的环境为Linux, 下文讨论的栈跟内核栈,没有任何的关系,...这里有如下几个问题,线程栈的空间是开辟在那里的? 线程栈之间可以互访吗?为什么在使用pthread_attr_setstack函数时,需要设置栈的大...
  • 一个进程中各线程的堆和栈的关系

    千次阅读 2014-03-13 13:56:12
    在很多现代操作系统中,一个进程的(虚)地址空间大小为4G,分为系统空间和用户空间两部分,系统空间为所有进程共享,而...堆(heap)分配与栈有所不同,一般是一个进程有一个C运行时堆,这个堆为本进程中所有线程
  • 进程与线程的关系

    2021-03-10 09:26:58
    进程与线程的关系 1.进程是资源分配的基本单位,线程是CPU调度和分派的基本单位 2.线程是进程的一部分,一个线程只能属于一个进程,一个进程可以有多个线程,但至少有一个线程 3.每个进程都有独立的代码和数据...
  • 段,每一个线程有一个,如果进程有多个线程,则包含多个段 代码段,由于其只读,不会被修改,故其在整个系统内共享。比如说一个执行文件,在系统中同时存在多个进程,那么这些进程将共享其代码段所占用内存 ...
  • 线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少资源(如程序计数器,一组寄存器和),但是它可同属一个进程的其他的线程共享进程所拥有全部资源.2.关系一个线程可以创建和撤销另一个线程...
  • 一、进程与线程 进程 进程是操作系统资源分配单位 例:I/O资源,内存资源… 线程 线程是资源调度单位,真正执行指令 例:操作数据执行流 在Java内存模型中: 线程共享:方法区、堆区 线程私有:...
  • 进程与线程的关系

    2020-02-20 19:07:18
    线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立运行和程序计数器(PC),线程之间切换开销小。 3、所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序...
  • 线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少资源(如程序计数器,一组寄存器和),但是它可同属一个进程的其他的线程共享进程所拥有全部资源.2.关系一个线程可以创建和撤销另一个线程;
  • 1.定义 进程是具有一定独立功能...线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少资源(如程序计数器,一组寄存器和),但是它可同属一个进程的其他的线程共享进程所拥有全部资源. 2.关系
  • 是CPU调度和分派基本单位,它是比进程更小能独立运行基本单位,线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少资源(如程序计数器,一组寄存器和),但是它可以同属一个进程的其他线程共享...
  • 是CPU调度和分派基本单位,它是比进程更小能独立运行基本单位,线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少资源(如程序计数器,一组寄存器和),但是它可同属一个进程的其他进程共享...
  • 一个进程由一到多个线程组成,各线程共享进程的内存空间(代码,数据,堆)和一些进程资源(打开文件和信号)。进程有自己独立寄存器和线程私有是:局部变量,函数参数,TLS(Thread Local ...
  • 线程与进程关系

    2021-06-01 09:25:11
    为了保证线程局部变量不被别的线程访问到,虚拟机和本地方法线程私有。 2.程序计数器为什么是私有? 程序计数器私有主要是为了线程切换后能恢复到正确执行位置。 3.一句话简单了解堆和方法区 堆和...
  • 概念 进程(有时称为重量级进程)...线程(有时候称为轻量级进程)与进程类似,不过它们是在同一个进程下执行,并 共享相同上下文。可以将它们认为是在一个主进程或“主线程”中并行运行一些“迷 你进程”。 线程...
  • 今天偶然看待一个帖子,讨论线程与进程,于是突发奇想写个blog 1.定义 ...线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少资源(如程序计数器,一组寄存器和),但是它可同属一个进程
  • 1.定义 进程是具有一定独立功能程序关于某个...线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少资源(如程序计数器,一组寄存器和),但是它可同属一个进程的其他的线程共享进程所拥有全部资源.
  • 线程、进程与线程的关系 1. 进程是资源管理的最小单位,线程是程序执行的最小单位。 2. 每个进程都有自己的数据段、代码段和堆栈段。线程是轻量级的进程,它包含独立的和CPU寄存器状态,线程是进程的一条...
  • 1.定义 进程是具有一定独立功能...线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少资源(如程序计数器,一组寄存器和),但是它可同属一个进程的其他的线程共享进程所拥有全部资源. 2.关系

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 493
精华内容 197
热门标签
关键字:

进程栈与线程栈的关系