在进程创建前我们必须明白一个概念什么进程?
进程的概念:我们知道操作系统最核心的概念就是进程。其实进程简单来说就是在操作系统中运行的程序,它是操作系统资源管理的最小单位。但是进程是一个动态的实体,它是程序的一次执行过程。进程和程序的区别在于:进程是动态的,程序是静态的,进程是运行中的程序,而程序是一些保存在硬盘上的可执行代码。
在Linux下面,可以通过命令ps或pstree查看当前系统中的进程。
好,有了进程的笼统概念后那我们接下来要明白的是进程它到底拥有些什么,上面不是说它是一个动态的实体么,意思是一旦启动它时时刻刻在运行对吧(当然是在一定的时间内),那么它是怎么运行的需要些什么额外的资源呢,这就要我们明白进程的结构了。
Linux进程结构:可由三部分组成:代码段、数据段、堆栈段。也就是程序、数据、进程控制块PCB(Process Control Block)组成。进程控制块是进程存在的惟一标识,系统通过PCB的存在而感知进程的存在。
代码段存放程序的可执行代码。数据段存放程序的全局变量、常量、静态变量。堆栈段中的堆用于存放动态分配的内存变量,堆栈段中的栈用于函数调用,它存放着函数的参数、函数内部定义的局部变量。
系统通过PCB对进程进行管理和调度。PCB包括创建进程、执行程序、退出进程以及改变进程的优先级等。而进程中的PCB用一个名为task_struct的结构体来表示,定义在include/linux/sched.h中,每当创建一新进程时,便在内存中申请一个空的task_struct结构,填入所需信息,同时,指向该结构的指针也被加入到task数组中,所有进程控制块都存储在task[]数组中。
其中的进程描述表如下所示:
- include/linux/sched.h
- 384 struct task_struct {
- 385 volatile long state;
- 386 struct thread_info *thread_info;
- 387 atomic_t usage;
- 388 unsigned long flags;
- 389 unsigned long ptrace;
- 390
- 391 int lock_depth;
- 392
- 393 int prio, static_prio;
- 394 struct list_head run_list;
- 395 prio_array_t *array;
- 396
- 397 unsigned long sleep_avg;
- 398 long interactive_credit;
- 399 unsigned long long timestamp;
- 400 int activated;
- 401
- 302 unsigned long policy;
- 403 cpumask_t cpus_allowed;
- 404 unsigned int time_slice, first_time_slice;
- 405
- 406 struct list_head tasks;
- 407 struct list_head ptrace_children;
- 408 struct list_head ptrace_list;
- 409
- 410 struct mm_struct *mm, *active_mm;
- ...
- 413 struct linux_binfmt *binfmt;
- 414 int exit_code, exit_signal;
- 415 int pdeath_signal;
- ...
- 419 pid_t pid;
- 420 pid_t tgid;
- ...
- 426 struct task_struct *real_parent;
- 427 struct task_struct *parent;
- 428 struct list_head children;
- 429 struct list_head sibling;
- 430 struct task_struct *group_leader;
- ...
- 433 struct pid_link pids[PIDTYPE_MAX];
- 434
- 435 wait_queue_head_t wait_chldexit;
- 436 struct completion *vfork_done;
- 437 int __user *set_child_tid;
- 438 int __user *clear_child_tid;
- 439
- 440 unsigned long rt_priority;
- 441 unsigned long it_real_value, it_prof_value, it_virt_value;
- 442 unsigned long it_real_incr, it_prof_incr, it_virt_incr;
- 443 struct timer_list real_timer;
- 444 unsigned long utime, stime, cutime, cstime;
- 445 unsigned long nvcsw, nivcsw, cnvcsw, cnivcsw;
- 446 u64 start_time;
- ...
- 450 uid_t uid,euid,suid,fsuid;
- 451 gid_t gid,egid,sgid,fsgid;
- 452 struct group_info *group_info;
- 453 kernel_cap_t cap_effective, cap_inheritable, cap_permitted;
- 454 int keep_capabilities:1;
- 455 struct user_struct *user;
- ...
- 457 struct rlimit rlim[RLIM_NLIMITS];
- 458 unsigned short used_math;
- 459 char comm[16];
- ...
- 461 int link_count, total_link_count;
- ...
- 467 struct fs_struct *fs;
- ...
- 469 struct files_struct *files;
- ...
- 509 unsigned long ptrace_message;
- 510 siginfo_t *last_siginfo;
- ...
- 516 };
- The following categories describe some of the types of things a process descriptor must keep track of during a process' lifespan: 进程运行中必须保存的一些信息类别
- Process attributes进程属性
- Process relationships进程间关系
- Process memory space进程内存空间
- File management文件管理
- Signal management信号管理
- Process credentials进程信任状态
- Resource limits资源限制
- Scheduling related fields调度相关的字段
task_struct结构体比较复杂,总共有80多个数据成员,下面我将其中的主要数据成员按功能划分成几个部门,同时对每个部分中的重要成员作详细的说明和解释;
调度数据成员:
1) vo latile long state
表示进程的当前状态。进程运行时, 它会根据具体情况改变状态。进程状态总共有TASK RUNN ING ( 可运行状态)、TASK INTERRUPT IBLE ( 可中断的等待状态)、TASK UNINTERRUPT IBLE(不可中断的等待状态)、TASK ZOMB IE( 僵死状态)、TASK STOPPED(暂停状态) 等5种。
2) long pr io rity进程优先级, prio rity 的值给出了进程每次获取CPU 后, 可使用的时间片长度( 单位是jiffies)。
3) unsigned long rt_priority rt_priority 的值给出了实时进程的优先级, rt_priority
1000给出进程每次获取CPU 后, 可使用的时间片长度(单位是jiffies)。
4) long counter在轮转法调度时counter表示当前进程还可运行多久。在进程开始时被赋为priority的值, 以后每隔一个时钟中断递减1,减到0时引起新一轮调度。
5) unsigned long policy表示该进程的进程调度策略。调度策略有:
SCH ED_OTHER 0, 非实时进程, 用基于优先权的轮转法。
SCH ED_FIFO 1, 实时进程, 用先进先出算法。
SCH ED_RR 2, 实时进程, 用基于优先权的轮转法
进程队列指针:
1) struc t task_struct* next_task, * prev_task
在Linux 中所有进程(以PCB 的形式)组成一个双向链表,
next_task和prev_task是链表的前后向指针。
2) struct task_struct* p_opptr, * p_pptr
struct task_struc t* p_cptr, * p_ysptr, * p_osptr
以上分别是指向该进程的原始父进程、父进程、子进程和新
老兄弟进程的指针。
3) struct task_struct* pidhash_next
struct task_struct** pidhash_pprev
用于链入进程hash表的前后指针。系统进程除了链入双
向链表外, 还被加到hash表中。
进程标识:
uid_t uid gid_t gid uid和gid分别是运行进程的用
户标识和用户组标识。
pid_t pid pid_t pgrp pid和pgrp分别是运行进程的
进程标识号和进程组标识号
时间数据成员:
long per_cpu_utime [ NR_CPUS ] per_cpu_stime
[ NR_CPUS]
per_cpu_utime 是用户态进程运行的时间, per_cpu_
stime是内核态进程运行的时间
进程创建时间unsigned long start_time
文件系统数据成员:
struct fs_struct* fs
fs保存了进程本身与VFS( 虚拟文件系统)的关系信息。
struct fs_struct
{
atom ic_t count;
rw lock_t lock;
int umask;
struct dentry* root, * pwd, * altroot;
struct vfsm ount* rootmnt, * pwdmnt, * altrootmnt;
}
其中root、rootm nt是根目录的dentry 和其mount点的vfsmount。
pwd、pwdmnt 是当前工作目录的dentry 和其mount 点的vfs..
mount。altroot、altrootmnt是保存根节点被替换之后原来根目标
的dentry和其mount点的vfsmount。
内存数据成员:
1) struct mm_struct* mm
在Linux 中, 采用按需分页的策略解决进程的内存需求。
task_struct的数据成员mm 指向关于存储管理的mm_truc t结
构。
2) struct mm_struct* active_mm
active_mm 指向活动地址空间。
3) mm_segm ent_t addr_ lim it
表示线程空间地址。
用户线程空间地址: 0..0xBFFFFFFF。
内核线程空间地址: 0..0xFFFFFFFF
4) spinlock_t alloc_ lock
用于申请空间时用的自旋锁。自旋锁的主要功能是临界区
保护。
页面管理:
1) int swappable: 1
进程占用的页面是否可换出。swappab le为1表示可换出。
2) unsigned long min_flt, maj_ flt
该进程累计minor缺页次数和major缺页次数。
3) unsigned long nswap
该进程累计换出页面数。
4) unsigned long swap_cnt
下一次循环最多可换出的页数。
以上这些便是我挑选的task_struct结构体中的一些重要的成员。
有了对进程的控制块PCB中的结构体描述表了解以后,我们还要明白一个概念,既然进程是一个程序的执行过程,那么程序又是怎么转化为进程的呢?
一般情况下Linux下C程序的生成可分为4个阶段:预编译、编译、汇编、链接。编译器gcc经过预编译、编译、汇编3个步骤将源程序文件转换为目标文件。如果程序有多个目标文件或者程序使用了库函数,编译器还要将所有的目标文件或所需要的库链接起来,最后形成可执行程序。当程序执行时,操作系统将可执行程序复制到内存中。一般程序转换为进程分一下几个步骤:
1>内核将程序读入内存,为程序分配内存空间
2>内核为该进程分配进程标识符(PID)和其他所需资源
3>内核为进程保存PID及相应的状态信息,吧进程放到运行队列中等待执行。程序转化为进程后就可以被操作系统的调度程序调度执行了。
还要明白一个概念:进程的内存映像
指内核在内存中如何存放可执行程序文件。在将程序转换为进程的过程中,操作系统将可执行程序由硬盘复制到内存中。
程序的内存映像一般布局为:从低地址到髙地址一次为:
代码段:即二进制机器代码,代码段是只读的,可被多个进程共享,如果一个进程创建了一个子进程,父子进程共享代码段,此外子进程还获得父进程数据段、堆、栈的复制。
数据段:存储已被初始化的变量,包括全局变量和已被初始化的静态变量。
未被初始化数据段:存储未被初始化的静态变量,它也被称为bss段。
堆:用于存放程序运行中动态分配的变量。
栈:用于函数调用,保存函数的返回地址、函数参数、函数内部定义的局部变量。
另外髙地址还存储了命令行参数和环境变量
可执行程序和内存映像的区别在于:可执行程序位于磁盘中而内存映像位于内存中,可执行程序没有堆栈,因为程序被加载到内存中才会分配堆栈,可执行程序虽然也有未初始化数据段但它并不被存储在位于硬盘中的可执行文件中,可执行程序是动态的、不变的,而内存映像随着程序的执行时在动态变化的,例如,数据段随着程序的执行要存储新的变量值,栈在函数调用时也是不断在变化的。
阅读(647) | 评论(0) | 转发(0) |