linux线程栈的若干思考

来源:互联网 发布:nba2k17帅气捏脸数据 编辑:程序博客网 时间:2024/06/03 19:41

        线程包含了表示其行环境必需的信息,其中包括进程中标示线程的线程ID,一组寄存器值,栈,调度优先级和策略, 信号屏蔽字,errno变量以及线程私有数据。进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本,程序的全局内存和堆内存,栈以及文件描述符,所以线程的mm_struct *mm指针变量和所属进程的mm指针变量相同。我们知道在linux系统中,线程栈的默认大小为8M,这可以通过ulimit -s 命令查看,这个线程的大小也是可以更改的。下图的8192B就是8M.

下面让我们来在<<unix环境高级编程>>中涉及的几个关于线程栈的函数:

样例1:

#include <limits.h>  #include <string.h>  #include <pthread.h>  #include <stdlib.h>  #include <stdio.h>#include<iostream> #include <unistd.h>using namespace std;void *thread_fun(void* args){size_t stacksize;void *addr;int *p=(int *)args; pthread_attr_t thread_attr;pthread_attr_getstacksize(&thread_attr,&stacksize);cout<<"the size of stack is: "<<stacksize<<endl;return (void*)0;}int main(){pthread_t pid[3];int i=0;int a[3]={1,2,3};for(i=0;i<3;i++){pthread_create(&pid[i],NULL,thread_fun,(void *)&a[i]);cout<<"pthread id is: "<<pid[i]<<endl;}for(i=0;i<3;i++){pthread_join(pid[i],NULL);}return 0;}
输出结果:

我们可以看到其中有一个线程的堆栈大小为4001536字节约等于3.8M的大小,而其它两个大小为默认值8M,为什么会出现不想等的情况?????

样例2:

#include <limits.h>  #include <string.h>  #include <pthread.h>  #include <stdlib.h>  #include <stdio.h>#include<iostream>#include <unistd.h>using namespace std;int main(){   pthread_t thread;   size_t stacksize;   pthread_attr_t thread_attr;   int ret;   void *addr;   pthread_attr_init(&thread_attr);   int new_size = 20480;   ret =  pthread_attr_getstack(&thread_attr,&addr,&stacksize);   if(ret != 0){       cout << "emError" << endl;       return -1;   }   cout << "stacksize=" << stacksize << endl<<"addr is: "<<addr<<endl;   cout << PTHREAD_STACK_MIN << endl;   ret = pthread_attr_setstacksize(&thread_attr,new_size);   if(ret != 0)return -1;   ret =  pthread_attr_getstack(&thread_attr,&addr,&stacksize);   if(ret != 0){       cout << "emError" << endl;       return -1;   }   cout << "after set stacksize=" << stacksize << endl;   cout<<"the address is: "<<addr<<endl;   ret = pthread_attr_destroy(&thread_attr);   if(ret != 0)return -1;   return 0;}
测试结果:

从上面的例子可以看出,当我们使用pthread_attr_getstack的时候获得的线程栈的大小为0,且地址也为0,不明白???

当我们做如下修改的时候(也就是将pthread_attr_getstack变成pthread_attr_getsize):

int main(){   pthread_t thread;   size_t stacksize;   pthread_attr_t thread_attr;   int ret;   void *addr=NULL;   pthread_attr_init(&thread_attr);   int new_size = 20480;   ret =  pthread_attr_getstacksize(&thread_attr,&stacksize);   if(ret != 0){       cout << "emError" << endl;       return -1;   }   cout << "stacksize=" << stacksize << endl<<"addr is: "<<addr<<endl;   cout << PTHREAD_STACK_MIN << endl;   ret = pthread_attr_setstacksize(&thread_attr,new_size);   if(ret != 0)return -1;   ret =  pthread_attr_getstack(&thread_attr,&addr,&stacksize);   if(ret != 0){       cout << "emError" << endl;       return -1;   }   cout << "after set stacksize=" << stacksize << endl;   cout<<"the address is: "<<addr<<endl;   ret = pthread_attr_destroy(&thread_attr);   if(ret != 0)return -1;   return 0;}

当我们更改函数的时候就可以得到线程栈的大小。当我们修改线程栈的大小之后再调用pthread_attr_getstack时,就可以获得线程栈的地址,但是我们可以看到,此时线程栈的地址在内核空间,这是为什么?????

样例3:

#include <limits.h>  #include <string.h>  #include <pthread.h>  #include <stdlib.h>  #include <stdio.h>#include <iostream>#include <unistd.h>#include <errno.h>using namespace std;int main(void){void *stackaddr;unsigned int stacksize;unsigned int guardsize;pthread_attr_t attr;if (pthread_attr_init(&attr) != 0){    printf("init attr failed.\n");    return -1;    }if (pthread_attr_getstacksize(&attr,&stacksize) == 0){    printf("default stacksize:0x%x\n",stacksize);    }if (pthread_attr_getstack(&attr,&stackaddr,&stacksize) == 0){    printf("default stacksize:0x%x,stack addr:%p\n",stacksize,stackaddr);    }    stacksize = 16*1024;if (pthread_attr_setstacksize(&attr,stacksize) != 0){    printf("set thread stack size failed,error:%s\n",strerror(errno));    return -1;    }if (pthread_attr_getstack(&attr,&stackaddr,&stacksize) == 0){    printf("after set stacksize,stacksize:0x%x,stack addr:%p\n",stacksize,stackaddr);    }if (pthread_attr_setstack(&attr,(void *)0x80000,stacksize) !=  0){    printf("set stack error:%s\n",strerror(errno));    }if (pthread_attr_getstack(&attr,&stackaddr,&stacksize) == 0){    printf("after setstack,stacksize:0x%x,stack addr:%p\n",stacksize,stackaddr);    }return 0;}
输出结果:

在样例3中我们指定了线程栈的大小和地址,所以输出的结果是0x4000和0x80000;这这个例子中,我们调用pthread_attr_getstack同样获得线程栈的大小为nil,也就是空。

所以总结上面的例子我的疑问有:

(1)为什么一开始调用pthread_attr_getstack不能获得线程栈的大小和线程栈的地址.

(2)在对线程栈进行设置之后(这里的设置仅仅限定栈的大小),利用pthread_attr_getstack能够输出线程栈的大小和线程栈的地址,但此时线程栈的地址在内核空间????

针对以上问题还希望大牛给出指导!!!!!!!!

补充知识:

用户栈和内核栈的切换:

  当进程由于中断或系统调用从用户态转换到内核态时,进程所使用的栈也要从用户栈切换到内核栈。系统调用实质就是通过指令产生中断,称为软中断。进程因为中断(软中断或硬件产生中断),使得CPU切换到特权工作模式,此时进程陷入内核态,进程进入内核态后,首先把用户态的堆栈地址保存在内核堆栈中,然后设置堆
栈指针寄存器的地址为内核栈地址,这样就完成了用户栈向内核栈的切换。
当进程从内核态切换到用户态时,最后把保存在内核栈中的用户栈地址恢复到CPU栈指针寄存器即可,这样就完成了内核栈向用户栈的切换。这里要理解一下内核堆栈。前面我们讲到,进程从用户态进入内核态时,需要在内核栈中保存用户栈的地址。那么进入内核态时,从哪里获得内核栈的栈指针呢?要解决这个问题,先要理解从用户态刚切换到内核态以后,进程的内核栈总是空的。这点很好理解,当进程在用户空间运行时,使用的是用户 栈;当进程在内核态运行时,内核栈中保存进程在内核态运行的相关信息,但是当进程完成了内核态的运行,重新回到用户态时,此时内核栈中保存的信息全部恢 复,也就是说,进程在内核态中的代码执行完成回到用户态时,内核栈是空的。理解了从用户态刚切换到内核态以后,进程的内核栈总是空的,那刚才这个问题就很好理解了,因为内核栈是空的,那当进程从用户态切换到内核态后,把内核栈的栈顶地址设置给CPU的栈指针寄存器就可以了。
在kernel-2.4内核里面,内核栈的实现是:
 Union task_union {
                   Struct task_struct task;
                   Unsigned long stack[INIT_STACK_SIZE/sizeof(long)];
 };
 其中,INIT_STACK_SIZE的大小只能是8K

在kernel-2.6内核里面,内核栈的实现是:
union thread_union {
 struct thread_info thread_info;
 unsigned long stack[THREAD_SIZE/sizeof(long)];
};
其中,THREAD_SIZE的值取8192时,stack数组的大小为2048;THREAD_SIZE的值取4096时,stack数组的大小为1024。现在我们应该思考,为何要将内核栈和thread_info(其实也就相当于task_struct,只不过使用thread_info结构更节省空间)紧密的放在一起?最主要的原因就是内核可以很容易的通过esp寄存器的值获得当前正在运行进程的thread_info结构的地址,进而获得当前进程描述符的地址;

struct thread_info {           struct task_struct  *task;      /* main task structure */           struct exec_domain  *exec_domain;   /* execution domain */           unsigned long       flags;      /* low level flags */           unsigned long       status;     /* thread-synchronous flags */           __u32           cpu;        /* current CPU */           int         preempt_count;  /* 0 => preemptable, <0 => BUG */              mm_segment_t        addr_limit; // thread address space: 0-0xBFFFFFFF for user-thead 0-0xFFFFFFFF for kernel-      //thread*/           void            *sysenter_return;           struct restart_block    restart_block;           unsigned long           previous_esp;   /* ESP of the previous stack in  case of nested (IRQ) stacks */           __u8            supervisor_stack[0];       }; 
获取thread_info的地址(以下是内核栈的内存分配):

从上面的内存分布来看,我们知道内核栈的大小为两个页面的大小(8KB),esp指向的是内核堆栈的结尾,由于堆栈是向下增长的,esp和thread_info位于同一个8KB或者4KB的块当中。也就是thread_union的长度了。如果是8KB,屏蔽esp的低13位就可以得到thread_info的地址,也就是8KB块的开始位置。4KB的话,就屏蔽低12位.

下面是linux内核获得thread_info结构体地址的函数:

static inline struct thread_info *current_thread_info(void){  return (struct thread_info *)(current_stack_pointer & ~(THREAD_SIZE - 1));  } 
这个函数直接返回thread_info指针,当前的堆栈指针current_stack_pointer,也就是esp,THREAD_SIZE为块的字节大小8192或者是4096,这里假设值8192,十六进制的表示是0x00002000,二进制的表示是00000000000000000010000000000000~(THREAD_SIZE-1)的结果刚好为1111111111111111111 0000000000000,第十三位是全为零,也就是刚好屏蔽了esp的低十三位,最终得到的是thread_info的地址。


0 0
原创粉丝点击