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 的相關資訊,詳細的程式碼如下:
接下來要慢慢研究這個 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);
- 要找到所有的 sibling,我們會先回到 parent 再來找所有的 children,這樣可以避免一些無謂的判斷。
- 在 Linux 裡面有定義一個 init_task,請注意,它的 pid 是 0,名稱叫做 swapper(聽說叫做歷史的包袱)
- 雖然我們可以透過這種 DFS 的方式來找到所有的 tasks,但相信我,Kernel 內部絕對不是利用這個方式來找尋特定 pid 的 task!!事實上,Kernel 內部是使用 Hash Table 的機制來快速找到指定 pid 的 tasks。這是之後要來看的範圍,先休息囉~
感謝 很詳細的說明:)
回覆刪除