task-process-thread-top(virt-res-shr)

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);