Linux中TLS
TLS(Thread Local Storage)
线程局部存储。
在Linux操作系统中,TLS保存成GDT中描述的一个段。
1: /*
2: * This creates a new process as a copy of the old one,
3: * but does not actually start it yet.
4: *
5: * It copies the registers, and all the appropriate
6: * parts of the process environment (as per the clone
7: * flags). The actual kick-off is left to the caller.
8: */
9: static struct task_struct *copy_process(unsigned long clone_flags,
10: unsigned long stack_start,
11: struct pt_regs *regs,
12: unsigned long stack_size,
13: int __user *child_tidptr,
14: struct pid *pid,
15: int trace)
16: { 17: ...... 18: retval = copy_thread(clone_flags, stack_start, stack_size, p, regs); 19: ...... 20: }
1: int copy_thread(unsigned long clone_flags, unsigned long sp,
2: unsigned long unused,
3: struct task_struct *p, struct pt_regs *regs)
4: {5: struct pt_regs *childregs;
6: struct task_struct *tsk;
7: int err;
8: 9: childregs = task_pt_regs(p); 10: *childregs = *regs; 11: childregs->ax = 0; 12: childregs->sp = sp; 13: 14: p->thread.sp = (unsigned long) childregs;
15: p->thread.sp0 = (unsigned long) (childregs+1);
16: 17: p->thread.ip = (unsigned long) ret_from_fork;
18: 19: task_user_gs(p) = get_user_gs(regs); 20: 21: p->thread.io_bitmap_ptr = NULL; 22: tsk = current; 23: err = -ENOMEM; 24: 25: memset(p->thread.ptrace_bps, 0, sizeof(p->thread.ptrace_bps));
26: 27: if (unlikely(test_tsk_thread_flag(tsk, TIF_IO_BITMAP))) {
28: p->thread.io_bitmap_ptr = kmemdup(tsk->thread.io_bitmap_ptr, 29: IO_BITMAP_BYTES, GFP_KERNEL);30: if (!p->thread.io_bitmap_ptr) {
31: p->thread.io_bitmap_max = 0;32: return -ENOMEM;
33: } 34: set_tsk_thread_flag(p, TIF_IO_BITMAP); 35: } 36: 37: err = 0; 38: 39: /*
40: * Set a new TLS for the child thread?
41: */
42: if (clone_flags & CLONE_SETTLS)
43: err = do_set_thread_area(p, -1,44: (struct user_desc __user *)childregs->si, 0);
45: 46: if (err && p->thread.io_bitmap_ptr) {
47: kfree(p->thread.io_bitmap_ptr); 48: p->thread.io_bitmap_max = 0; 49: }50: return err;
51: }
1: /*
2: * Set a given TLS descriptor:
3: */
4: int do_set_thread_area(struct task_struct *p, int idx,
5: struct user_desc __user *u_info,
6: int can_allocate)
7: {8: struct user_desc info;
9: 10: if (copy_from_user(&info, u_info, sizeof(info)))
11: return -EFAULT;
12: 13: if (idx == -1)
14: idx = info.entry_number; 15: 16: /*
17: * index -1 means the kernel should try to find and
18: * allocate an empty descriptor:
19: */
20: if (idx == -1 && can_allocate) {
21: idx = get_free_idx();22: if (idx < 0)
23: return idx;
24: if (put_user(idx, &u_info->entry_number))
25: return -EFAULT;
26: } 27: 28: if (idx < GDT_ENTRY_TLS_MIN || idx > GDT_ENTRY_TLS_MAX)
29: return -EINVAL;
30: 31: set_tls_desc(p, idx, &info, 1); 32: 33: return 0;
34: }
1: static void set_tls_desc(struct task_struct *p, int idx,
2: const struct user_desc *info, int n)
3: {4: struct thread_struct *t = &p->thread;
5: struct desc_struct *desc = &t->tls_array[idx - GDT_ENTRY_TLS_MIN];
6: int cpu;
7: 8: /*
9: * We must not get preempted while modifying the TLS.
10: */
11: cpu = get_cpu(); 12: 13: while (n-- > 0) {
14: if (LDT_empty(info))
15: desc->a = desc->b = 0;16: else
17: fill_ldt(desc, info); 18: ++info; 19: ++desc; 20: } 21: 22: if (t == ¤t->thread)
23: load_TLS(t, cpu); 24: 25: put_cpu(); 26: }
1: static inline void fill_ldt(struct desc_struct *desc, const struct user_desc *info)
2: { 3: desc->limit0 = info->limit & 0x0ffff; 4: 5: desc->base0 = (info->base_addr & 0x0000ffff); 6: desc->base1 = (info->base_addr & 0x00ff0000) >> 16; 7: 8: desc->type = (info->read_exec_only ^ 1) << 1; 9: desc->type |= info->contents << 2; 10: 11: desc->s = 1; 12: desc->dpl = 0x3; 13: desc->p = info->seg_not_present ^ 1; 14: desc->limit = (info->limit & 0xf0000) >> 16; 15: desc->avl = info->useable; 16: desc->d = info->seg_32bit; 17: desc->g = info->limit_in_pages; 18: 19: desc->base2 = (info->base_addr & 0xff000000) >> 24;20: /*
21: * Don't allow setting of the lm bit. It is useless anyway
22: * because 64bit system calls require __USER_CS:
23: */
24: desc->l = 0; 25: }从上面的call_tree可以看到,在fork系统调用创建一个新的进程时,会为新的任务设置TLS。
参考:http://blog.csdn.net/dog250/article/details/7704898
fill_ldt设置GDT中第6个段描述符的基址和段限以及DPL等信息,这些信息都是从sys_set_thread_area系统调用的u_info参数中得来的。本质上,最终GDT的第6个段中描述的信息其实就是一块内存,这块内存用于存储TLS节,这块内存其实也是使用brk,mmap之类调用在主线程的堆空间申请的,只是后来调用sys_set_thread_area将其设置成了本线程的私有空间罢了,主线程或者其它线程如果愿意,也是可以通过其它手段访问到这块空间的。
因为TLS是一个对应于C/C++ Runtime库的概念,所以要深入了解TLS,需要结合glibc来理解。

浙公网安备 33010602011771号