Linux Process 1 ~ Tasks Lists

Process,在一般作業系統的教科書給它的定義是「an instance of a program 」。在 Linux 裡面,對 Process 的 Descriptor 是定義在 include/linux/sched.h 裡面的 task_struct。要注意的是, Linux 裡面是採用 lightweight process 的實作概念,換言之,每個 thread 都有一個 task descriptor,而且每個 task 的 pid 都是獨一的。可是,在 POSIX Thread 的標準裡面規定,一個 process 裡面所有 threads 的 pid 都必須相同,所以在 task_struct 裡面多導入了一個變數,叫做 tgid( task group ID ),在同一個 process 裡面不同的 threads 都有不同的 pid,但他們的 tgid 是相同的(這個事實也告訴我們,當我們在應用程式裡面呼叫 getpid() 的時候,其實回傳值是 tgid 而不是 pid 喔)。

接下來要慢慢研究這個 structure。這個 structure 有點複雜,我們先集中在 Task List 的部分。首先我們來看下面的 Macro~

#define next_task(p) list_entry(rcu_dereference((p)->tasks.next), struct task_struct, tasks)
#define for_each_process(p) \
for (p = &init_task ; (p = next_task(p)) != &init_task ; )

顧名思義,這個 Macro 會將所有的 Process 掃過一遍。每一個存在的 Porcess 都是透過 structure 內部的 tasks( struct list_head )所彼此串起來的。可是有一件很神奇的是,當我們執行 multi-thread 的程式時,我們發現無法透過這個 Macro 來找出除了主線程以外的其他 thread 資訊。對作業系統來說,一個 Process 開再多的 threads 也還是一個 Process,所以在那隻 Macro 裡面是找不到的。那那些被創造出來的 thread descriptor 到底到哪去了呢?答案是他們被串到了 Master thread 的 sibling 裡面。那如果是 fork 的話呢?因為 fork 是 create 一個新的 Process,所以也會被加入到 tasks 的 list 裡面,而且他也會被放入 Parent Process 的 Children list 中。關於 task_struct 彼此的關係圖,請看下面:



在這裡有一件事要特別注意,假設有二個 task A 和 task B,他們是父子關係,那 B 會串在 A 的 children list 裡面,但如果 A 又產生一個 child process,那 C 會被串到 B 的 sibling list 中,而 C 自己的 sibling 則會串回 A 的 children,這一點是上圖很值得注意的地方。

下面附上一隻小程式,可以用來找出所有的 tasks,就是根據上面所描述的 list 關係圖來加以完成的。這個程式會在 /proc 底下開一個新的 node,叫做 my_pid,將所要查詢的 pid 餵給他,可以找到該 task 的相關資訊,詳細的程式碼如下:

#include linux/init.h
#include linux/kernel.h
#include linux/module.h
#include linux/sched.h
#include linux/mm.h
#include asm/uaccess.h
#include linux/proc_fs.h

#define MODULE_NAME "my_pid"
#ifdef MODULE_LICENSE
MODULE_LICENSE("GPL");
#endif

#define for_each_children(pos,head) \
list_for_each_entry(pos, &(head -> children), sibling)
#define for_each_sibling(pos,head) \
for_each_children(pos, (head->parent) )

//Begin of /proc/xxx

static struct proc_dir_entry *pid_dir_entry = NULL;

static void print_task( struct task_struct *task )
{
struct task_struct *task2;

printk("%s(pid:%d)(tgid:%d)\n", task->comm, task->pid, task->tgid);
printk(" Parent:%s(pid:%d)(tgid:%d)\n", task->parent->comm, task->parent->pid, task->parent->tgid);
printk(" Sibling:\n");

for_each_sibling( task2 , task )
{
if( task2 != task )
{
printk(" %s(pid:%d)(tgid:%d)\n", task2->comm, task2->pid, task2->tgid );
}
}

printk(" Children:\n");
for_each_children( task2 , task )
{
printk(" %s(pid:%d)(tgid:%d)\n", task2->comm, task2->pid, task2->tgid);
}

return;
}

static struct task_struct * check_task_and_children( struct task_struct *task, int pid )
{
struct task_struct *task2 = NULL;
struct task_struct *taskRet = NULL;

if ( task -> pid == pid )
{
return task;
}

for_each_children( task2 , task )
{
taskRet = check_task_and_children( task2, pid );

if( taskRet != NULL )
{
return taskRet;
}
}

return NULL;
}

static int proc_pid_read(char *page, char **start, off_t off, int count, int *eof, void *data)
{
*eof = 1;
return 0;
}

static int proc_pid_write(struct file *file, const char *buffer, unsigned long count, void *data)
{
struct task_struct *init;
struct task_struct *task2 = NULL;
int pid;
char buf[16];
int len = 0;

if (count > 16)
{
len = 16;
}
else
{
len = count;
}

// get data from user space via write operation
if ( copy_from_user(buf, buffer, len) )
{
printk("[%s] proc write fail to copy\n", MODULE_NAME);
return -EFAULT;
}
buf[len] = 0;


pid = simple_strtol(buf, NULL, 10);
printk("\n[%s] got the process id to look up as %d\n", MODULE_NAME, pid);

init = &init_task;
printk("\ninit_task: %s(pid:%d)(tgid:%d)\n\n", init->comm, init->pid, init->tgid);

task2 = check_task_and_children( init, pid );

if( task2 != NULL )
{
print_task( task2 );
return len;
}

printk("\n[%s] Task (pid:%d) not found\n", MODULE_NAME, pid);

return len;
}

static int __init my_pid_load(void)
{
pid_dir_entry = create_proc_entry(MODULE_NAME, 0666, NULL);
if (pid_dir_entry == NULL)
{
printk("[%s] create proc entry fail\n", MODULE_NAME);
return -ENOMEM;
}

// set proc_dir_entry struct fields
pid_dir_entry->read_proc = &proc_pid_read;
pid_dir_entry->write_proc = &proc_pid_write;

printk("[%s] is loaded and initialized\n", MODULE_NAME);
printk("\n");
return 0;
}

static void __exit my_pid_unload(void)
{
remove_proc_entry(MODULE_NAME, NULL);
printk("[%s] is being removed now\n", MODULE_NAME);
printk("\n");
}

module_init(my_pid_load);
module_exit(my_pid_unload);

  1. 要找到所有的 sibling,我們會先回到 parent 再來找所有的 children,這樣可以避免一些無謂的判斷。
  2. 在 Linux 裡面有定義一個 init_task,請注意,它的 pid 是 0,名稱叫做 swapper(聽說叫做歷史的包袱)
  3. 雖然我們可以透過這種 DFS 的方式來找到所有的 tasks,但相信我,Kernel 內部絕對不是利用這個方式來找尋特定 pid 的 task!!事實上,Kernel 內部是使用 Hash Table 的機制來快速找到指定 pid 的 tasks。這是之後要來看的範圍,先休息囉~

留言

張貼留言

這個網誌中的熱門文章

如何將Linux打造成OpenFlow Switch:Openvswitch

我弟家的新居感恩禮拜分享:善頌善禱

Linux Virtual Interface: TUN/TAP