task
kernel能调度执行的最小单位是task. kernel自身是init_task,或者叫pid=0的task.
一个task使用的内存情况记录在task->mm, mm->mmap记录了task的address space的使用情况, mm->pgd指向了page table, 记录着物理内存的使用情况.
task->fs记录着task的运行的根目录(root)和当前工作目录(pwd).
task->files记录着task打开的文件.
task->nsproxy->mnt_namespace记录着task使用的文件系统信息.
task->stack记录着task在kernel space运行时使用的栈.它比较特殊,要对齐到8K上.显然这个是硬性限制,知道内存大小后,就能确定下来最多可运行多少个task.(这里先不考虑swap的情况)
kernel启动到后来时,调用 copy_process(CLONE_FS | CLONE_SIGHAND | CLONE_VM | CLONE_UNTRACED) 将init_task复制出来一份,产生了pid=1的task.
task是有数量限制的,非root每次复制一个task出来后,都会检查是否超出限制,如果超出,那就free掉这个task.然后这个copy process就失败了.
整个系统可创建的task数量也是有限制的,这个限制是根据可用内存的量计算出来的. @see fork_init()
然后再次调用copy_process,将init_task复制出来第二份,产生了pid=2的task kthreadd.
init_task会向kthread_create_list里添加任务,而kthreadd会从这个list里取出任务,创建新的task. 像之前ps看到的pid=3,4的应该就是kthreadd创建的.
pid=1的task就是我们常说的init process,它在最后接手了系统的控制权.
process
当copy_process时,如果没有指明CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND,那么新copy出来的task我们叫它为process.
没有指明这4个flag的情况下,copy_process在复制出新的task struct后,继续把task->mm, task->fs, task->files, task->sighand都复制出一份.
thread
当copy_process时,如果指明了CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND,那么新copy出来的task我们叫它为thread.
也就是说,如果几个task共用一份mm,fs,files,sighand,那么就称它们为threads.
man pthreads
当前linux的实现是NPTL. 我们用的glibc版本是2.15.
man 7 futex
man 2 futex
sys_futex定义在 kernel/futex.c#2648
kernel在futex的实现上并没有什么锁的概念,你在一个process里告诉kernel在内存地址A上有一个值A',kernel收到请求后,检查一下,如果地址A上的值还是A',那就把这个process挂起,加到一个和地址A关联的wait queue里.
然后你在另一个process里告诉kernel,把地址A关联的wait queue里的进程,选择几个让它们接着执行.
具体到pthread mutex, 设A初始值为1.
mutex_lock时,把A的值减1后,如果发现值为0,那就lock住了,如果值不为0,那就将其设为-1,调用futex挂起.再有其它process也要lock时,同样减1,此时值为-2,再设回-1,也挂起.这里可以看到,mutex的值始终都是-1.
mutex_unlock时,把A的值加1后,如果发现值为0,那就把值设为1,然后调用futex wakeup其它process.
来看看glibc的实现:
static inline void __generic_mutex_lock (int *mutex)
{
unsigned int v;
/* Bit 31 was clear, we got the mutex. (this is the fastpath). */
if (atomic_bit_test_set (mutex, 31) == 0)
return;
atomic_increment (mutex);
while (1) {
if (atomic_bit_test_set (mutex, 31) == 0) {
atomic_decrement (mutex);
return;
}
/* We have to wait now. First make sure the futex value we are monitoring is truly negative (i.e. locked). */
v = *mutex;
if (v >= 0)
continue;
lll_futex_wait (mutex, v, LLL_SHARED);
}
}
static inline void __generic_mutex_unlock (int *mutex)
{
/* Adding 0x80000000 to the counter results in 0 if and only if
there are not other interested threads - we can return (this is
the fastpath). */
if (atomic_add_zero (mutex, 0x80000000))
return;
/* There are other threads waiting for this mutex, wake one of them up. */
lll_futex_wake (mutex, 1, LLL_SHARED);
}
// mutex的31位标记着是否lock住了.这也是个符号位.也就是说,只要mutex < 0, 那就是lock住了.
// 模拟执行一下:
// thread-1 lock, mutex=0x80000000
// thread-2 lock, mutex=0x80000001, futex_wait
// thread-3 lock, mutex=0x80000010, futex_wait
// thread-1 unlock, mutex=0x10, futex_wake
// thread-2 wakeup, mutex=0x80000010, mutex=0x80000001
// thread-2 unlock, mutex=0x1, futex_wake
// thread-3 wakeup, mutex=0x80000001, mutex=0x80000000
// thread-3 unlock, mutex=0
// 这个实现会导致最多只能有0x7fffffff个thread lock?
top(virt-res-shr)
top命令在软件包procps里,procps还提供了kill,ps,uptime,vmstat等命令.
在top.c里调用task_show()方法显示出每个进程的信息,task_show的关键在于proc_t这个数据结构.
proc_t定义在proc/readproc.h里,注释里说的很明白,proc_t里存储的信息都是读取 /proc/#/stat 获得的.
man proc可以看到 proc/#/stat 定义在 fs/proc/array.c#do_task_stat() 里
cat /proc/#/stat 出来的信息分别是:
/bin/cat /proc/1/stat
1 pid
(init) task->comm
S task->state
0 ppid
0 pgid
0 sid
0 tty_nr
-1 tty_pgrp
4202752(0x402100) task->flags
158 min_flt
612 cmin_flt
0 maj_flt
0 cmaj_flt
2 utime
4554 stime
21 cutime
58 cstime
20 priority
0 nice
1 num_threads
0 itrealvalue
284 start_time
4259840(0x410000) vsize = task_vsize(mm) = PAGE_SIZE * mm->total_vm
97 get_mm_rss(mm) = get_mm_counter(mm, MM_FILEPAGES) + get_mm_counter(mm, MM_ANONPAGES)
= mm->rss_stat.count[MM_FILEPAGES] + mm->rss_stat.count[MM_ANONPAGES]
1 rsslim // man getrlimit, 好像没用了这个字段
// 下边的又有些对不上,不看了
/bin/cat /proc/1/maps
address perms offset dev inode pathname
00400000-00401000 r-xp 00000000 00:01 4354 /init
00601000-00602000 r--p 00001000 00:01 4354 /init
00602000-00603000 rw-p 00002000 00:01 4354 /init
7f24ca190000-7f24ca344000 r-xp 00000000 00:01 4183 /lib/libc.so.6
7f24ca344000-7f24ca543000 ---p 001b4000 00:01 4183 /lib/libc.so.6
7f24ca543000-7f24ca547000 r--p 001b3000 00:01 4183 /lib/libc.so.6
7f24ca547000-7f24ca549000 rw-p 001b7000 00:01 4183 /lib/libc.so.6
7f24ca549000-7f24ca54e000 rw-p 00000000 00:00 0
7f24ca54e000-7f24ca570000 r-xp 00000000 00:01 4408 /lib64/ld-linux-x86-64.so.2
7f24ca769000-7f24ca770000 rw-p 00000000 00:00 0
7f24ca770000-7f24ca771000 r--p 00022000 00:01 4408 /lib64/ld-linux-x86-64.so.2
7f24ca771000-7f24ca773000 rw-p 00023000 00:01 4408 /lib64/ld-linux-x86-64.so.2
7fffe7d73000-7fffe7d94000 rw-p 00000000 00:00 0 [stack]
7fffe7db8000-7fffe7db9000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
计算一下可知,这些maps共有 0x410 个page, 对应着 /proc/1/stat 里的vsize.
/bin/cat /proc/1/statm
1040(0x410) vsize
97 rss
77 shared(MM_FILEPAGES)
1 text
0 library (unused in Linux 2.6)
47 data + stack
0 dirty pages (unused in Linux 2.6)
// @see fs/proc/task_mmu.c#0071
*shared = get_mm_counter(mm, MM_FILEPAGES);
*text = (PAGE_ALIGN(mm->end_code) - (mm->start_code & PAGE_MASK)) >> PAGE_SHIFT;
*data = mm->total_vm - mm->shared_vm;
*resident = *shared + get_mm_counter(mm, MM_ANONPAGES);