Linux进程管理
2011-12-23 17:42
253 查看
32位的进程描述符都是task_struct类型结构,它的域包含了与一个进程相关的所有信息。
其中包含指向tty_struct、fs_struct、files_struct、mm_struct、signal_struct等
在Linux中,任务(task)和进程(process)是两个相同的术语,task_struct其实就是通常所说的"进程控制块"即PCB。
进程的状态:
可运行状态(TASK_RUNNING)、可中断的等待状态(TASK_INTERRUPTIBLE)、不可中断的等待状态(TASK_UNINTERRUPTIBLE)、暂停状态(TASK_STOPPED)、僵死状态(TASK_ZOMBIE)
任何类UNIX操作系统允许用户使用一个叫做进程标识符(Process ID,PID)的数来标识进程。PID是32位的无符号整数,存放在进程描述符的pid域中。
进程必须能同时处理很多进程。Linux能处理多达NR_TASKS个进程。内核在它自己的地址空间保存了一个全局静态数组task,其大小为NR_TASKS。数组中的元素就是进程描述符指针,空指针表示数组项中没有进程描述符。
Task数组仅仅包含了进程描述符的指针,而不是描述符本身。因为进程是动态实体,进程描述符被存放在动态内存中,而不是存放在永久性分配给内核的内存区。Linux把每个进程的两个不同的数据结构存放在一个单独8KB的内存区:进程描述符和内核态的进程栈。
进程链表:
内核建立了几个进程链表。每个进程链表由指向进程描述符的指针组成。进程描述符的数据结构中包含了一个链表指针(即每个进程用来指向下一个进程的域)。
一个双向循环链表把所有现有的进程联系起来,我们叫它为进程链表(process list)。每个进程的prev_task和next_task域来实现链表。链表的头是init_task描述符,由task数组的第一个元素指向,他是所有进程的祖先
运行队列(runqueue):扫描整个进程链表是低效的,引入了可运行状态进程的双向循环链表。
进程切换:为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行。或者叫做任务切换,或者上下文切换。
Switch_to宏执行进程切换。它利用了prev和next两个参数:第一个参数是挂起进程的进程描述符指针,而第二个参数是在CPU上要执行的进程的进程描述符指针。Schedule()函数调用这个宏以调度一个新的进程在CPU上运行。
当前进程(current宏)
当一个进程在某个CPU上正在执行时,内核如何获得指向它的task_struct的指针?上面所提到的存储方式为达到这一目的提供了方便。在linux/include/ i386/current.h 中定义了current宏,这是一段与体系结构相关的代码:
实际上,这段代码相当于如下一组汇编指令(设p是指向当前进程task_struc结构的指针):
换句话说,仅仅只需检查栈指针的值,而根本无需存取内存,内核就可以导出task_struct结构的地址。
http://gpg119.blog.163.com/blog/static/9153415320089744136869/
http://www.kerneltravel.net/kernel-book/%E7%AC%AC%E4%BA%94%E7%AB%A0%E8%BF%9B%E7%A8%8B%E8%B0%83%E5%BA%A6/5.3.5.htm
5.4.2 进程切换
前面所介绍的schedule()中调用了switch_to宏,这个宏实现了进程之间的真正切换,其代码存放于include/ i386/system.h:
switch_to宏是用嵌入式汇编写成,比较难理解,为描述方便起见,我们给代码编了行号,在此我们给出具体的解释:
· thread的类型为前面介绍的thread_struct结构。
· 输出参数有三个,表示这段代码执行后有三项数据会有变化,它们与变量及寄存器的对应关系如下:
0%与prev->thread.esp对应,1%与prev->thread.eip对应,这两个参数都存放在内存,而2%与ebx寄存器对应,同时说明last参数存放在ebx寄存器中。
· 输入参数有五个,其对应关系如下:
3%与next->thread.esp对应,4%与next->thread.eip对应,这两个参数都存放在内存,而5%,6%和7%分别与eax,edx及ebx相对应,同时说明prev,next以及prev三个参数分别放在这三个寄存器中。表5.1列出了这几种对应关系:
表5.1
· 第2~4行就是在当前进程prev的内核栈中保存esi,edi及ebp寄存器的内容。
· 第5行将prev的内核堆栈指针ebp存入prev->thread.esp中。
· 第6行把将要运行进程next的内核栈指针next->thread.esp置入esp寄存器中。从现在开始,内核对next的内核栈进行操作,因此,这条指令执行从prev到next真正的上下文切换,因为进程描述符的地址与其内核栈的地址紧紧地联系在一起(参见第四章),因此,改变内核栈就意味着改变当前进程。如果此处引用current的话,那就已经指向next的task_struct结构了。从这个意义上说,进程的切换在这一行指令执行完以后就已经完成。但是,构成一个进程的另一个要素是程序的执行,这方面的切换尚未完成。
· 第7行将标号“1”所在的地址,也就是第一条popl指令(第11行)所在的地址保存在prev->thread.eip中,这个地址就是prev下一次被调度运行而切入时的“返回”地址。
· 第8行将next->thread.eip压入next的内核栈。那么,next->thread.eip究竟指向那个地址?实际上,它就是 next上一次被调离时通过第7行保存的地址,也就是第11行popl指令的地址。因为,每个进程被调离时都要执行这里的第7行,这就决定了每个进程(除了新创建的进程)在受到调度而恢复执行时都从这里的第11行开始。
· 第9行通过jump指令(而不是 call指令)转入一个函数__switch_to()。这个函数的具体实现将在下面介绍。当CPU执行到__switch_to()函数的ret指令时,最后进入堆栈的next->thread.eip就变成了返回地址,这就是标号“1”的地址。
· 第11~13行恢复next上次被调离时推进堆栈的内容。从现在开始,next进程就成为当前进程而真正开始执行。
下面我们来讨论__switch_to()函数。
在调用__switch_to()函数之前,对其定义了fastcall :
extern void FASTCALL(__switch_to(struct task_struct *prev, struct task_struct *next));
fastcall对函数的调用不同于一般函数的调用,因为__switch_to()从寄存器(如表5.1)取参数,而不像一般函数那样从堆栈取参数,也就是说,通过寄存器eax和edx把prev和next 参数传递给__switch_to()函数。
从上面的描述我们看到,尽管Intel本身为操作系统中的进程(任务)切换提供了硬件支持,但是Linux内核的设计者并没有完全采用这种思想,而是用软件实现了进程切换,而且,软件实现比硬件实现的效率更高,灵活性更大。
http://www.kerneltravel.net/kernel-book/%E7%AC%AC%E4%BA%94%E7%AB%A0%E8%BF%9B%E7%A8%8B%E8%B0%83%E5%BA%A6/5.4.2.htm
对进程中的TSS进行深入研究。
遍历进程链表
Makefile文件:
具体参考:
http://edsionte.com/techblog/archives/1841
http://edsionte.com/techblog/archives/1940 进程用户空间的代码描述
fork系统调用分析:
http://edsionte.com/techblog/archives/2103
http://edsionte.com/techblog/archives/2131
http://edsionte.com/techblog/archives/2141
进程描述符的处理
http://edsionte.com/techblog/archives/2198
强大的僵尸进程处理
http://edsionte.com/techblog/archives/2952
Linux2.6进程调度分析
http://edsionte.com/techblog/archives/2838
http://edsionte.com/techblog/archives/2851
http://edsionte.com/techblog/archives/2870
http://edsionte.com/techblog/archives/3254
http://www.ibm.com/developerworks/cn/linux/l-linux-process-management/index.html Tim关于进程管理的文章
其中包含指向tty_struct、fs_struct、files_struct、mm_struct、signal_struct等
在Linux中,任务(task)和进程(process)是两个相同的术语,task_struct其实就是通常所说的"进程控制块"即PCB。
进程的状态:
可运行状态(TASK_RUNNING)、可中断的等待状态(TASK_INTERRUPTIBLE)、不可中断的等待状态(TASK_UNINTERRUPTIBLE)、暂停状态(TASK_STOPPED)、僵死状态(TASK_ZOMBIE)
任何类UNIX操作系统允许用户使用一个叫做进程标识符(Process ID,PID)的数来标识进程。PID是32位的无符号整数,存放在进程描述符的pid域中。
进程必须能同时处理很多进程。Linux能处理多达NR_TASKS个进程。内核在它自己的地址空间保存了一个全局静态数组task,其大小为NR_TASKS。数组中的元素就是进程描述符指针,空指针表示数组项中没有进程描述符。
Task数组仅仅包含了进程描述符的指针,而不是描述符本身。因为进程是动态实体,进程描述符被存放在动态内存中,而不是存放在永久性分配给内核的内存区。Linux把每个进程的两个不同的数据结构存放在一个单独8KB的内存区:进程描述符和内核态的进程栈。
进程链表:
内核建立了几个进程链表。每个进程链表由指向进程描述符的指针组成。进程描述符的数据结构中包含了一个链表指针(即每个进程用来指向下一个进程的域)。
一个双向循环链表把所有现有的进程联系起来,我们叫它为进程链表(process list)。每个进程的prev_task和next_task域来实现链表。链表的头是init_task描述符,由task数组的第一个元素指向,他是所有进程的祖先
运行队列(runqueue):扫描整个进程链表是低效的,引入了可运行状态进程的双向循环链表。
进程切换:为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行。或者叫做任务切换,或者上下文切换。
Switch_to宏执行进程切换。它利用了prev和next两个参数:第一个参数是挂起进程的进程描述符指针,而第二个参数是在CPU上要执行的进程的进程描述符指针。Schedule()函数调用这个宏以调度一个新的进程在CPU上运行。
当前进程(current宏)
当一个进程在某个CPU上正在执行时,内核如何获得指向它的task_struct的指针?上面所提到的存储方式为达到这一目的提供了方便。在linux/include/ i386/current.h 中定义了current宏,这是一段与体系结构相关的代码:
static inline struct task_struct * get_current(void) { struct task_struct *current; __asm__("andl %%esp,%0; ":"=r" (current) : "0" (~8191UL)); return current; }
实际上,这段代码相当于如下一组汇编指令(设p是指向当前进程task_struc结构的指针):
movl $0xffffe000, %ecx andl %esp, %ecx movl %ecx, p
换句话说,仅仅只需检查栈指针的值,而根本无需存取内存,内核就可以导出task_struct结构的地址。
http://gpg119.blog.163.com/blog/static/9153415320089744136869/
http://www.kerneltravel.net/kernel-book/%E7%AC%AC%E4%BA%94%E7%AB%A0%E8%BF%9B%E7%A8%8B%E8%B0%83%E5%BA%A6/5.3.5.htm
5.4.2 进程切换
前面所介绍的schedule()中调用了switch_to宏,这个宏实现了进程之间的真正切换,其代码存放于include/ i386/system.h:
1 #define switch_to(prev,next,last) do { \ 2 asm volatile("pushl %%esi\n\t" \ 3 "pushl %%edi\n\t" \ 4 "pushl %%ebp\n\t" \ 5 "movl %%esp,%0\n\t" /* save ESP */ \ 6 "movl %3,%%esp\n\t" /* restore ESP */ \ 7 "movl $1f,%1\n\t" /* save EIP */ \ 8 "pushl %4\n\t" /* restore EIP */ \ 9 "jmp __switch_to\n" \ 10 "1:\t" \ 11 "popl %%ebp\n\t" \ 12 "popl %%edi\n\t" \ 13 "popl %%esi\n\t" \ 14 :"=m" (prev->thread.esp),"=m" (prev->thread.eip), \ 15 "=b" (last) \ 16 :"m" (next->thread.esp),"m" (next->thread.eip), \ 17 "a" (prev), "d" (next), \ 18 "b" (prev)); \ 19 } while (0)
switch_to宏是用嵌入式汇编写成,比较难理解,为描述方便起见,我们给代码编了行号,在此我们给出具体的解释:
· thread的类型为前面介绍的thread_struct结构。
· 输出参数有三个,表示这段代码执行后有三项数据会有变化,它们与变量及寄存器的对应关系如下:
0%与prev->thread.esp对应,1%与prev->thread.eip对应,这两个参数都存放在内存,而2%与ebx寄存器对应,同时说明last参数存放在ebx寄存器中。
· 输入参数有五个,其对应关系如下:
3%与next->thread.esp对应,4%与next->thread.eip对应,这两个参数都存放在内存,而5%,6%和7%分别与eax,edx及ebx相对应,同时说明prev,next以及prev三个参数分别放在这三个寄存器中。表5.1列出了这几种对应关系:
表5.1
参数类型 | 参数名 | 内存变量 | 寄存器 | 函数参数 |
输出参数 | 0% | prev->thread.esp | ||
1% | prev->thread.eip | |||
2% | ebx | last | ||
输入参数 | 3% | next->thread.esp | ||
4% | next->thread.eip | |||
5% | eax | prev | ||
6% | edx | next | ||
7% | ebx | prev |
· 第5行将prev的内核堆栈指针ebp存入prev->thread.esp中。
· 第6行把将要运行进程next的内核栈指针next->thread.esp置入esp寄存器中。从现在开始,内核对next的内核栈进行操作,因此,这条指令执行从prev到next真正的上下文切换,因为进程描述符的地址与其内核栈的地址紧紧地联系在一起(参见第四章),因此,改变内核栈就意味着改变当前进程。如果此处引用current的话,那就已经指向next的task_struct结构了。从这个意义上说,进程的切换在这一行指令执行完以后就已经完成。但是,构成一个进程的另一个要素是程序的执行,这方面的切换尚未完成。
· 第7行将标号“1”所在的地址,也就是第一条popl指令(第11行)所在的地址保存在prev->thread.eip中,这个地址就是prev下一次被调度运行而切入时的“返回”地址。
· 第8行将next->thread.eip压入next的内核栈。那么,next->thread.eip究竟指向那个地址?实际上,它就是 next上一次被调离时通过第7行保存的地址,也就是第11行popl指令的地址。因为,每个进程被调离时都要执行这里的第7行,这就决定了每个进程(除了新创建的进程)在受到调度而恢复执行时都从这里的第11行开始。
· 第9行通过jump指令(而不是 call指令)转入一个函数__switch_to()。这个函数的具体实现将在下面介绍。当CPU执行到__switch_to()函数的ret指令时,最后进入堆栈的next->thread.eip就变成了返回地址,这就是标号“1”的地址。
· 第11~13行恢复next上次被调离时推进堆栈的内容。从现在开始,next进程就成为当前进程而真正开始执行。
下面我们来讨论__switch_to()函数。
在调用__switch_to()函数之前,对其定义了fastcall :
extern void FASTCALL(__switch_to(struct task_struct *prev, struct task_struct *next));
fastcall对函数的调用不同于一般函数的调用,因为__switch_to()从寄存器(如表5.1)取参数,而不像一般函数那样从堆栈取参数,也就是说,通过寄存器eax和edx把prev和next 参数传递给__switch_to()函数。
void __switch_to(struct task_struct *prev_p, struct task_struct *next_p) { struct thread_struct *prev = &prev_p->thread, *next = &next_p->thread; struct tss_struct *tss = init_tss + smp_processor_id(); unlazy_fpu(prev_p);/* 如果数学处理器工作,则保存其寄存器的值*/ /* 将TSS中的内核级(0级)堆栈指针换成next->esp0,这就是next 进程在内核 栈的指针 tss->esp0 = next->esp0; /* 保存fs和gs,但无需保存es和ds,因为当处于内核时,内核段 总是保持不变*/ asm volatile("movl %%fs,%0":"=m" (*(int *)&prev->fs)); asm volatile("movl %%gs,%0":"=m" (*(int *)&prev->gs)); /*恢复next进程的fs和gs */ loadsegment(fs, next->fs); loadsegment(gs, next->gs); /* 如果next挂起时使用了调试寄存器,则装载0~7个寄存器中的6个寄存器,其中第4、5个寄存器没有使用 */ if (next->debugreg[7]){ loaddebug(next, 0); loaddebug(next, 1); loaddebug(next, 2); loaddebug(next, 3); /* no 4 and 5 */ loaddebug(next, 6); loaddebug(next, 7); } if (prev->ioperm || next->ioperm) { if (next->ioperm) { /*把next进程的I/O操作权限位图拷贝到TSS中 */ memcpy(tss->io_bitmap, next->io_bitmap, IO_BITMAP_SIZE*sizeof(unsigned long)); /* 把io_bitmap在tss中的偏移量赋给tss->bitmap */ tss->bitmap = IO_BITMAP_OFFSET; } else /*如果一个进程要使用I/O指令,但是,若位图的偏移量超出TSS的范围, * 就会产生一个可控制的SIGSEGV信号。第一次对sys_ioperm()的调用会 * 建立起适当的位图 */ tss->bitmap = INVALID_IO_BITMAP_OFFSET; } }
从上面的描述我们看到,尽管Intel本身为操作系统中的进程(任务)切换提供了硬件支持,但是Linux内核的设计者并没有完全采用这种思想,而是用软件实现了进程切换,而且,软件实现比硬件实现的效率更高,灵活性更大。
http://www.kerneltravel.net/kernel-book/%E7%AC%AC%E4%BA%94%E7%AB%A0%E8%BF%9B%E7%A8%8B%E8%B0%83%E5%BA%A6/5.4.2.htm
对进程中的TSS进行深入研究。
遍历进程链表
#include <linux/init.h> #include <linux/module.h> #include <linux/sched.h> #include <linux/sem.h> #include <linux/list.h> static int __init traverse_init(void) { struct task_struct *pos; struct list_head *current_head; int count=0; printk("Traversal module is working..\n"); current_head=&(current->tasks); list_for_each_entry(pos,current_head,tasks) { count++; printk("[process %d]:%s\'s pid is %d\n",count,pos->comm,pos->pid); } printk(KERN_ALERT"The number of process is:%d\n",count); return 0; } static void __exit traverse_exit(void) { printk("hello world exit\n"); } module_init(traverse_init); module_exit(traverse_exit); MODULE_LICENSE("GPL");
Makefile文件:
obj-m:=tasks.o PWD:=$(shell pwd) CUR_PATH:=$(shell uname -r) KERNELPATH:=/lib/modules/$(shell uname -r)/build all: make -C $(KERNELPATH) M=$(PWD) modules clean: make -C $(KERNELPATH) M=$(PWD) clean
具体参考:
http://edsionte.com/techblog/archives/1841
http://edsionte.com/techblog/archives/1940 进程用户空间的代码描述
fork系统调用分析:
http://edsionte.com/techblog/archives/2103
http://edsionte.com/techblog/archives/2131
http://edsionte.com/techblog/archives/2141
进程描述符的处理
http://edsionte.com/techblog/archives/2198
强大的僵尸进程处理
http://edsionte.com/techblog/archives/2952
Linux2.6进程调度分析
http://edsionte.com/techblog/archives/2838
http://edsionte.com/techblog/archives/2851
http://edsionte.com/techblog/archives/2870
http://edsionte.com/techblog/archives/3254
http://www.ibm.com/developerworks/cn/linux/l-linux-process-management/index.html Tim关于进程管理的文章
相关文章推荐
- Linux进程核心调度器之主调度器schedule--Linux进程的管理与调度(十九)【转】
- linux那点事儿(六)----进程管理详解(推荐)
- linux进程管理(2)---进程的组织结构
- Linux内核线程kernel thread详解--Linux进程的管理与调度(十)
- 8 个用于有效地管理进程的 Linux 命令
- linux进程管理、任务管理
- linux 进程管理学习笔记
- LINUX内核设计思想之进程管理
- 嵌入式成长轨迹27 【Linux应用编程强化】【中嵌第二阶段】【进程管理】
- Linux - 进程管理,ps与top
- 2017.7.21 linux下进程管理工具supervisord的安装与使用
- linux管理进程和计划任务
- Linux进程管理之“四大名捕”
- Linux进程管理—信号、定时器
- 工作管理、进程管理-(Linux笔记)
- Linux系统进程管理命令
- Linux内核—进程管理
- Linux进程地址管理之mm_struct
- 轻松学习Linux之进程监视与管理
- 轻松学习Linux之进程监视与管理