一、进程和程序
1.进程的基本概念
所谓进程是由正文段用户数据段以及系统数据段共同组成的一个执行环境,是一个动态实体。
2.程序的基本概念
程序只是一个普通文件,是一个机器代码指令和数据的集合,这些指令和数据存储在磁盘上的一个可执行映像中,所以,程序是一个静态的实体。
3.进程的组成部分
(1)正文段:存放被执行的机器指令。这个段是只读的(所以,在这里不能写自己能修改的代码),它允许系统中正在运行的两个或多个进程之间能够共享这一代码。
(2)用户数据段:存放进程在执行时直接进行操作的所有数据,包括进程使用的全部变量在内。显然,这里包含的信息可以被改变。虽然进程之间可以共享正文段,但是每个进程需要有它自己的专用用户数据段。
(3)系统数据段:该段有效地存放程序运行的环境。事实上,这正是程序和进程的区别所在。作为动态事物,进程是正文段、用户数据段和系统数据段的信息的交叉综合体,其中系统数据段是进程实体最重要的一部分,之所以说它有效地存放程序运行的环境,是因为这一部分存放有进程的控制信息。Linux为每个进程建立了task_struct数据结构来容纳这些控制信息。
4.进程和程序总结
程序装入内存后就可以运行了:在指令指针寄存器的控制下,不断地将指令取至CPU运行。这些指令控制的对象不外乎各种存储器(内存、外存和各种CPU寄存器等),这些存储器中保存有待运行的指令和待处理的数据,当然,指令只有到CPU才能发挥其作用。
Linux是一个多任务操作系统,也就是说,可以有多个程序同时装入内存并运行,操作系统为每个程序建立一个运行环境即创建进程,每个进程拥有自己的虚拟地址空间,它们之间互不干扰,即使要相互作用(例如多个进程合作完成某个工作),也要通过内核提供的进程间通信机制(IPC)
二、task_struct结构描述
1.Linux中的每个进程由一个task_struct数据结构来描述,task_struct其实就是通常所说的“进程控制块”即PCB。task_struct容纳了一个进程的所有信息,是系统对进程进行控制的唯一手段,也是最有效的手段。
2.task_struct数据结构所有域如下:
*进程状态(State);
僵死状态:进程虽然已经终止,但由于某种原因,父进程还没有执行wait()系统调用,终止进程的
信息也还没有回收。
*进程调度信息(Scheduling Information);
调度策略
只有root用户能通过sched_setscheduler()系统调用来改变调度策略。
*各种标识符(Identifiers);
*进程通信有关信息(IPC,Inter_Process Communication);
Linux 支持多种不同形式的通信机制。它支持典型的UNIX通信机制(IPC Mechanisms):信号、管道,也支持System V通信机制:共享内存、信号量和消息队列。
*时间和定时器信息(Times and Timers);
一个进程从创建到终止叫做该进程的生存期(lifetime)。进程在其生存期内使用 CPU的时间,内核都要进行记录,以便进行统计、计费等有关操作。进程耗费CPU的时间由两部分组成:一是在用户模式(或称为用户态)下耗费的时间、一是在系统模式(或称为系统态)下耗费的时间。每个时钟滴答,也就是每个时钟中断,内核都要更新当前进程耗费CPU的时间信息。
建立了“时间”的概念,“定时”就是轻而易举的了,无非是判断系统时间是否到达某个时刻,然后执行相关的操作而已。Linux提供了许多种定时方式,用户可以灵活使用这些方式来为自己的程序定时。
*进程链接信息(Links);
程序创建的进程具有父/子关系。因为一个进程能创建几个子进程,而子进程之间有兄弟关系,在 task_struct结构中有几个域来表示这种关系。
在Linux系统中,除了初始化进程init,其他进程都有一个父进程或称为双亲进程。 可以通过 fork()或 clone()系统调用来创建子进程, 除了进程标识符(PID)等要的信息外,子进程的task_struct结构中的绝大部分的信息都是从父进程中拷贝,或说“克隆”过来的。系统有必要记录这种“亲属”关系,使进程之间的协作更加方便,例如父进程给子进程发送杀死(kill)信号、父子进程通信等,就可以用这种关系很方便地实现。
*文件系统信息(File System);
进程可以打开或关闭文件,文件属于系统资源,Linux内核要对进程使用文件的情况进行记录。task_struct结构中有两个数据结构用于描述进程与文件相关的信息。其中,fs_struct中描述了两个 VFS索引节点,这两个索引节点叫做root和pwd,分别指向进程的可执行映像所对应的根目录和当前目录或工作目录。file_struct结构用来记录了进程打开的文件的描述符。
在文件系统中,每个VFS索引节点唯一描述一个文件或目录,同时该节点也是向更低层的文件系统提供的统一的接口。
*虚拟内存信息(Virtual Memory);
*页面管理信息(page);
*对称多处理器(SMP)信息;
*和处理器相关的环境(上下文)信息(Processor Specific Context);
*其他信息。
三、task_struct 结构在内存中的存放
内核栈:每个进程都有自己的内核栈。当进程从用户态进入内核态时,CPU就自动地设置该进程的内核栈。
从这个结构可以看出,内核栈占8KB 的内存区。实际上,进程的task_struct结构所占的内存是由内核动态分配的,更确切地说,内核根本不给task_struct分配内存,而仅仅给内核栈分配8KB的内存,并把其中的一部分给task_struct使用。
task_struct结构大约占1K字节左右,其具体数字与内核版本有关,因为不同的版本其域稍有不同。因此,内核栈的大小不能超过7KB,否则,内核栈会覆盖task_struct结构,从而导致内核崩溃。不过,7KB大小对内核栈已足够。
把task_struct结构与内核栈放在一起具有以下好处:
*内核可以方便而快速地找到这个结构,用伪代码描述如下:
task_struct = (struct task_struct *) STACK_POINTER & 0xffffe000(8K)
*避免在创建进程时动态分配额外的内存。
*task_struct 结构的起始地址总是开始于页大小(PAGE_SIZE)的边界。
四、进程的组织方式
1.哈希表
哈希表是进行快速查找的一种有效的组织方式。Linux在进程中引入的哈希表叫做pidhash,在include/linux/sched.h 中定义。
2.双向循环链表
哈希表的主要作用是根据进程的pid可以快速地找到对应的进程,但它没有反映进程创建的顺序,也无法反映进程之间的亲属关系,因此引入双向循环链表。每个进程task_struct结构中的prev_task和next_task域用来实现这种链表。
3.运行队列
当内核要寻找一个新的进程在CPU上运行时,必须只考虑处于可运行状态的进程(即在TASK_RUNNING 状态的进程) ,因为扫描整个进程链表是相当低效的,所以引入了可运行状态进程的双向循环链表,也叫运行队列(runqueue)。
4.进程的运行队列链表
该队列通过task_struct结构中的两个指针run_list链表来维持。队列的标志有两个:一个是“空进程”idle_task,一个是队列的长度。
空进程是个比较特殊的进程,只有系统中没有进程可运行时它才会被执行,Linux将它看作运行队列的头,当调度程序遍历运行队列,是从idle_task开始、至idle_task结束的,在调度程序运行过程中,允许队列中加入新出现的可运行进程,新出现的可运行进程插入到队尾,这样的好处是不会影响到调度程序所要遍历的队列成员,可见,idle_task是运行队列很重要的标志。另一个重要标志是队列长度,也就是系统中处于可运行状态(TASK_RUNNING)的进程数目,用全局整型变量nr_running表示,在/kernel/fork.c 中定义。
5.等待队列
顾名思义,就是等待执行的进程所组成的队列。等待队列在内核中有很多用途,尤其对中断处理、进程同步及定时用处更大。
五、内核线程
内核线程或叫守护进程,在操作系统中占据相当大的比例,你可以用“ps”命令查看系统中的进程,这时会发现很多以“d”结尾的进程名,这些进程就是内核线程。内核线程是由kernel_thread ( )函数在内核态下创建。
内核线程也可以叫内核任务,它们周期性地执行,例如,磁盘高速缓存的刷新,网络连接的维护,页面的换入换出等。在Linux中,内核线程与普通进程有一些本质的区别,从以下几个方面可以看出二者之间的差异。
*内核线程执行的是内核中的函数,而普通进程只有通过系统调用才能执行内核中的函数。
*内核线程只运行在内核态,而普通进程既可以运行在用户态,也可以运行在内核态。
*因为内核线程指只运行在内核态,因此,它只能使用大于PAGE_OFFSET(3G)的地址空间。另一方面,不管在用户态还是内核态,普通进程可以使用4GB的地址空间。
由于kernel_thread包含大部分汇编代码,大致和下面代码等价:
int kernel_thread(int(*fn)(void *),void * arg,unsigned long flags){
pid_t p ;
p = clone(0, flags | CLONE_VM);
if(p) /* parent */
return p;
else{ /* child */
fn(arg) ;
exit( ) ;
}
}
六、进程的权能
Linux 用“权能(capability)”表示一进程所具有的权力。一种权能仅仅是一个标志,它表明是否允许进程执行一个特定的操作或一组特定的操作。这个模型不同于传统的“超级用户对普通户”模型,在后一种模型中,一个进程要么能做任何事情,要么什么也不能做,这取决于它的有效 UID。
任何时候,每个进程只需要有限种权能,这是其主要优势。因此,即使一位有恶意的用户使用有潜在错误程序,他也只能非法地执行有限个操作类型。
具体权能列表在博客:http://blog.csdn.net/tq08g2z/article/details/77311787?locationNum=3&fps=1
本文出自 “三天不读书,智商不如猪!” 博客,谢绝转载!