linux进程管理分析.doc_第1页
linux进程管理分析.doc_第2页
linux进程管理分析.doc_第3页
linux进程管理分析.doc_第4页
linux进程管理分析.doc_第5页
已阅读5页,还剩38页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Linux 作者:陈颖邮箱: 博客: /目录 一 进程管理原理分析概述1二 进程创建21 进程管理相关结构22 进程与线程之间的关系43 进程创建5三 进程调度141进程调度相关结构142 优先级与负荷权重162.1优先级的计算162.2 负荷权重的计算173核心调度器183.1 周期性调度器183.2 主调度器194 完全公平调度类224.1 数据结构224.2 完全公平调度类操作方法224.2.1 虚拟时间计算224.2.2 延迟计算234.2.3 完全公平调度类操作方法实现 时间更新2 创建新进程时的处理2 将进程插入就绪队列295 实时调度类315.1 数据结构315.2 实时调度类326 负载均衡34四 程序的运行371 可执行文件372 程序的运行38 linux进程管理分析 陈颖一 进程管理原理分析概述进程创建之后将被插入就绪队列接受调度投入运行,新进程运行之初,它的地址空间与其父进程相同,为运行新的程序它需要调用exec函数将新的可执行程序加载到进程空间,从而运行不同于父进程的程序。进程创建:用户空间的进程创建是通过调用系统调用fork、clone、vfork来实现的,对于这些系统调用内核都是通过函数do_fork来处理的,它们的不同在于它们传入的参数clone_flags不一样,它决定了新进程与父进程共享那些资源。在函数do_fork中,为新进程分配了一个进程描述结构task_struct,并且为新进程分配了两页的栈空间,在栈空间的底部存放着一个thread_info结构,该结构保存了特定于CPU的一些信息。然后再clone_flags中标志的位共享或是重新分配一些资源,初始化一些调度相关的信息。最后将新进程插入就绪队列等到调度。进程调度:进程可能处于以下几种状态:运行,等待,睡眠。运行状态即是此刻正在执行;等待状态就是正出于就绪队列中等待调度;睡眠状态不在就绪队列中,在没被唤醒之前不能得到调度。就绪队列是管理等待调度进程的结构。一个CPU只有一个就绪队列rq,然而这个就绪队列并不直接管理进程,它又包含子就绪队列,例如完全公平就绪队列、实时就绪队列。进程的调度主要基于两个调度器:周期性调度器和主调度器。周期性调度器是在内核频率HZ中断中统计一些进程时间信息,并判断进程是否需要调度,如果需要就发起调度请求;主调度器是进程主动放弃CPU控制权,切换到其他进程运行,在住调度器中做了很多进程上下文切换的工作。上层调度器并不直接对进程进行操作,而是通过进程所属调度类对进城进行操作。调度类主要有两种:完全公平调度类、实时调度类。内核中用0到139来表示进程优先级,0到99供实时进程使用,100到139供普通进程使用,在用户空间对应-20到19的nice值。完全公平调度类提供类一些方法,这些方法对完全公平队列上的进程进行插入、移除、时间统计等操作。在完全公平队列上进程是按虚拟时间大小通过红黑树排序的,虚拟时间越小的进程排在红黑树越靠左边,得到运行的可能就越大。虚拟时间跟进程权重紧密相关,而权重又跟进程优先级相对应。随着时间的推移,权重越大的进程虚拟时间增加越慢,向右移动的速度就越慢,得到调度的机会就越多,所以优先级越高的进程得到运行的机会就越大。实时进程类与完全公平调度类大体相同,不同的是在实时就绪队列上,有100个链表头,分别挂接100个不同优先级的实时进程。选择下一个进程运行时,从小到大扫描链表找出第一个进程运行。实时进程包含SCHED_RR和SCHED_FIFO,它们在同优先级队列中都是先进先出,不过SCHED_RR有时间片,时间片用完后重新分配时间片将进程放入优先级队列尾,然后就调度其他进程运行。SCHED_FIFO进程一旦调度运行就可以一直运行下去,除非主动放弃CPU控制权。在SMP多处理器系统中还要实现负载均衡,将忙碌CPU队列上的进程移动到空闲CPU上。运行新程序:新进程运行之初,它的地址空间与其父进程相同,为运行新的程序它需要调用exec函数将新的可执行程序加载到进程空间,从而运行不同于父进程的程序。二 进程创建1 进程管理相关结构task_struct是一个庞大的结构体,它包含了进程管理的全部信息。这些信息主要包含以下几个方面:状态和执行信息,(如带决信号、使用的二进制格式、进程ID、指向父进程以及其他有关进程的指针、优先级和程序执行有关的时间信息);虚拟内存信息;用户ID、组ID以及权限;进程处理的文件和对应的文件系统信息;线程相关的信息;进程间通信相关信息;进程所用的信号处理程序;struct task_struct volatile long state; 进程状态 void *stack; 指向thread_info结构 atomic_t usage; unsigned int flags; unsigned int ptrace; 进程跟踪相关. int on_rq; 被插入进程调度队列中时置1 int prio, static_prio, normal_prio; 进程优先级、静态优先级、普通优先级 unsigned int rt_priority; 事实进程优先级 const struct sched_class *sched_class; 指向调度类指针 struct sched_entity se; 每个普通进程或是调度组都抽象成一个调度实体se struct sched_rt_entity rt; 实时进程调度实体. unsigned int policy; 调度策略SCHED_NORMAL、SCHED_RR和SCHED_FIFO等 int nr_cpus_allowed; cpumask_t cpus_allowed;在多处理器上,用于限制进程可以在哪些CPU上运行. struct mm_struct *mm, *active_mm; 进程虚拟存储空间. int exit_state; 进程退出状态 int exit_code, exit_signal; 进程退出时发送的信号 int pdeath_signal; 父进程终止时发送的信号. pid_t pid; 进程全局ID(对应的有PID命名空间中的局部ID) pid_t tgid; 线程组ID. struct task_struct _rcu *real_parent; 指向真实的父进程(如果父进程终止了,该进程被寄养到其他进程,它的当前父进程由parent指向) struct task_struct _rcu *parent;进程退出时信号SIGCHLD所发送的进程。下面几个字段都成了进程的关系图 struct list_head children; 本进程的所有子进程 struct list_head sibling; 兄弟进程 struct task_struct *group_leader; 指向进程组的组长进程. 这个 task_struct可能是一个普通进程,也可能是个组长进程或是会话首进程。 struct pid_link pidsPIDTYPE_MAX; struct list_head thread_group; struct completion *vfork_done; 在有vfork创建进程时该完成量保证了子进程先运行. unsigned long nvcsw, nivcsw; 自愿/非自愿上下文切换计数 struct timespec start_time; 进程创建时间 struct timespec real_start_time; /* boot based time */. const struct cred _rcu *real_cred; 进程权能相关 const struct cred _rcu *cred; char commTASK_COMM_LEN; 可执行文件名. struct thread_struct thread; 处理器特有数据 struct fs_struct *fs; 文件系统信息 struct files_struct *files; 打开的文件信息 struct nsproxy *nsproxy; 命名空间 struct signal_struct *signal; 进程信号描述符 struct sighand_struct *sighand; 进程信号处理函数 sigset_t blocked, real_blocked; 被阻塞信号的掩码,real_blocked表示临时掩码 sigset_t saved_sigmask; /* restored if set_restore_sigmask() was used */ struct sigpending pending; 存放挂起信号.;无论是进程还是线程它们都有一个task_struct结构和1页或2页的栈空间,在栈空间的底部有一个结构体thread_info,叫做线程描述符,其各成员含义说明如下:struct thread_info unsigned long flags; int preempt_count; 为0的时候内核可执行抢占否则不能 mm_segment_t addr_limit; 指定了进程可以使用的虚拟地址的上限 struct task_struct *task; 指向进程的task_struct结构 struct exec_domain *exec_domain;执行域,用于在一种计算机上实现多种ABI.;进程描述符,堆栈和线程描述符的关系图如下:2 进程与线程之间的关系进程和线程的区别就是进程有用户空间而线程没有,但是站在内核角度来看它们都是一样的。多线程的实现其实就是将多个进程关联起来,让它们共享同一内存地址空间,同一打开文件描述符集等。这些关联起来的线程就构成了线程组,在线程组内,每个线程都使用此线程组内第一个线程(thread group leader)的pid,并将此值存入tgid,当我们使用getpid()函数得到进程ID时,其实操作系统返回的是task_struct的tgid字段。线程有分为用户线程和内核线程:用户线程是通过系统调用clone创建的。他指定了几个标志,CLONE_PARENT_SETTID将生成线程的PID复制到clone调用指定的用户控件中的某个地址parent_tidptr,CLONE_CHILD_SETTID首先会将另一个传递到clone的用户空间指针child_tidptr保存在新进程的task_struct中,在新进程执行时内核会调用函数schedule_tail将当前PID复制到该地址。上面两个标志对应这两个用户空间的指针,这两个指针指向的地址都用来保存新进程的PID。它们的不同之处在于前一个指向父进程用户空间,后一个指向子进程用户空间。这样就将pid同时传入了父进程和子进程的用户空间。3 进程创建系统调用fork、clone、vfork都是由函数do_fork来处理的,该函数所需参数定义如下:clone_flags:低字节指定进程结束时发送到父进程的信号编码,通常是SIGCHLD信号。剩余三个字节给clone标志组用于编码。stack_start:为子进程分配的用户空间堆栈。regs:指向通用寄存器的指针,通用寄存器的值是在从用户态切换到内核态时被保存到内核态堆栈中的。stack_size:未使用,总是被设置为0parent_tidptr:父进程用户态变量地址,该父进程具有与新轻量级进程有相同的PID。只有在CLONE_PARENT_SETTID标志被设置时才有意义。child_tidptr:表示新轻量级进程的用户态变量地址,该进程具有这一类进程的PID。只有在CLONE_CHILD_SETTID标志被设置时才有意义。long do_fork(unsigned long clone_flags, unsigned long stack_start, struct pt_regs *regs, unsigned long stack_size, int _user *parent_tidptr, int _user *child_tidptr) struct task_struct *p; int trace = 0; long nr;如果创建新的user namespace就需要具备管理员权限或者CAP_SETUID或者CAP_SETGID。 if (clone_flags & CLONE_NEWUSER) if (clone_flags & CLONE_THREAD) return -EINVAL; if (!capable(CAP_SYS_ADMIN) | !capable(CAP_SETUID) | !capable(CAP_SETGID) return -EPERM; 检查是否跟踪子进程,如果是,就根据不同的跟踪行为发送不同的跟踪事件。PTRACE_EVENT_VFORK: 使子进程下次调用 vfork() 时停止其执行,并自动跟踪开始执行时就已设置 SIGSTOP 信号的新进程。PTRACE_EVENT_CLONE: 使子进程下次调用 clone() 时停止其执行,并自动跟踪开始执行时就已设置 SIGSTOP 信号的新进程。PTRACE_EVENT_FORK: 使子进程下次调用 fork() 时停止其执行,并自动跟踪开始执行时就已设置 SIGSTOP 信号的新进程。 if (likely(user_mode(regs) & !(clone_flags & CLONE_UNTRACED) if (clone_flags & CLONE_VFORK) trace = PTRACE_EVENT_VFORK; else if (clone_flags & CSIGNAL) != SIGCHLD) trace = PTRACE_EVENT_CLONE; else trace = PTRACE_EVENT_FORK; if (likely(!ptrace_event_enabled(current, trace) trace = 0; 分配进程描述符task_struct和内核堆栈空间,通过父进程的相关信息初始化一些资源。这个函数在进程创建中做了很多重要工作后面将详细讲述。 p = copy_process(clone_flags, stack_start, regs, stack_size,child_tidptr, NULL, trace); if (!IS_ERR(p) struct completion vfork;获取在当前进程的PID命名空间中的id号。 nr = task_pid_vnr(p); if (clone_flags & CLONE_PARENT_SETTID) put_user(nr, parent_tidptr);如果是通过vfork创建进程则初始化完成量vfork,它保证了子进程先运行。 if (clone_flags & CLONE_VFORK) p-vfork_done = &vfork; init_completion(&vfork); get_task_struct(p); 将新进程插入调度队列,等待调度运行。后面详细分析。 wake_up_new_task(p);发送跟踪事件 if (unlikely(trace) ptrace_event(trace, nr);如果是通过vfork创建进程则挂起当前进程等待子进程运行结束后再运行。 if (clone_flags & CLONE_VFORK) if (!wait_for_vfork_done(p, &vfork) ptrace_event(PTRACE_EVENT_VFORK_DONE, nr); else nr = PTR_ERR(p); 返回到发出创建新进程系统调用的进程中。如fork系统调用会两次返回,一次返回到父进程,返回值为子进程的进程id,另一次返回到子进程,后面会讲到返回到子进程的情况。 return nr;上面有一些函数需要进一步分析如下:【do_fork-copy_process】static struct task_struct *copy_process(unsigned long clone_flags,unsigned long stack_start,struct pt_regs *regs,unsigned long stack_size, int _user *child_tidptr,struct pid *pid, int trace) int retval; struct task_struct *p; int cgroup_callbacks_done = 0;如果创建新的命名空间就不能同时又拷贝父进程的文件系统信息。 if (clone_flags & (CLONE_NEWNS|CLONE_FS) = (CLONE_NEWNS|CLONE_FS) return ERR_PTR(-EINVAL);同一线程组中的进程必须共享信号 if (clone_flags & CLONE_THREAD) & !(clone_flags & CLONE_SIGHAND) return ERR_PTR(-EINVAL);共享信号处理方法就必须共享用户虚拟空间 if (clone_flags & CLONE_SIGHAND) & !(clone_flags & CLONE_VM) return ERR_PTR(-EINVAL); if (clone_flags & CLONE_PARENT) & current-signal-flags & SIGNAL_UNKILLABLE) return ERR_PTR(-EINVAL);执行附加的安全检查。 retval = security_task_create(clone_flags); if (retval) goto fork_out; retval = -ENOMEM;函数dup_task_struct要做的就是下面几件事:tsk = alloc_task_struct_node(node); 为新进程分配一个task_struct结构ti = alloc_thread_info_node(tsk, node); 分配2页的内核栈空间并返回页地址err = arch_dup_task_struct(tsk, orig); 将父进程的进程描述符结构task_struct的内容拷贝到子进程的进程描述符结构task_struct中tsk-stack = ti;线程描述符结构thread_info存放在上面分配的两页栈空间的开始出,让tsk-stack指向线程描述符结构。setup_thread_stack(tsk, orig);将父进程的线程描述符结构内容拷贝到子进程中。 p = dup_task_struct(current); if (!p) goto fork_out;. retval = -EAGAIN;每个进程都属于某个用户,每个用户都有它的资源限制,所以一个用户所能创建的进程数不能超过他的限制,除非他具有管理员权限。 if (atomic_read(&p-real_cred-user-processes) = task_rlimit(p, RLIMIT_NPROC) if (!capable(CAP_SYS_ADMIN) & !capable(CAP_SYS_RESOURCE) & p-real_cred-user != INIT_USER) goto bad_fork_free; current-flags &= PF_NPROC_EXCEEDED;结构体struct cred包含了uid、gid、user等安全权能相关的东西。在函数copy_creds中先判断是不是创建线程CLONE_THREAD,如果是就共享父进程的cred结构,如果不是就重新分配一个cred结构,并用父进程中cred的内容拷贝到新分配的cred中。 retval = copy_creds(p, clone_flags); if (retval = max_threads) goto bad_fork_cleanup_count;如果实现新的进程的执行脚本执行域和可执行格式的内核函数都包含在内核模块中,则递增它们的使用计数器。 if (!try_module_get(task_thread_info(p)-exec_domain-module) goto bad_fork_cleanup_count;did_exec记录了进程发出execve调用的次数 p-did_exec = 0; .函数sched_fork中初始化了调度实体结构struct sched_entity se;,设置了进程优先级,设置了进程调度类,该函数后面详细讲解。 sched_fork(p); retval = perf_event_init_task(p); if (retval) goto bad_fork_cleanup_policy; retval = audit_alloc(p); if (retval) goto bad_fork_cleanup_policy; 如果设置了COPY_SYSVSEM,则使用父进程的System V信号量。 retval = copy_semundo(clone_flags, p); if (retval) goto bad_fork_cleanup_audit;如果CLONE_FILES置位,则使用父进程的文件描述符列表,否则创建新的文件描述符列表,并将父进程的文件描述符列表拷到新的列表中。 retval = copy_files(clone_flags, p); if (retval) goto bad_fork_cleanup_semundo;如果CLONE_FS置位,则使用父进程的文件系统上下文,否者创建自己的 struct fs_struct结构并用父进程的 struct fs_struct初始化新的 struct fs_struct。该结构中包含根目录、当前工作目录等。 retval = copy_fs(clone_flags, p); if (retval) goto bad_fork_cleanup_files; retval = copy_sighand(clone_flags, p); if (retval) goto bad_fork_cleanup_fs;如果CLONE_SIGHAND置位,则使用父进程的信号处理程序,否则创建新的sighand_struct,并将父进程的信号处理结构内容拷到新的列信号处理结构中。 retval = copy_signal(clone_flags, p); if (retval) goto bad_fork_cleanup_sighand;如果CLONE_VM置位,则共享父进程的地址空间,两个进程共用同一mm_struct结构,否者创建一个父进程的页表副本但不复制页的实际内容,而是使用写时复制机制。 retval = copy_mm(clone_flags, p); if (retval) goto bad_fork_cleanup_signal;共享父进程的命名空间或是建立新的命名空间。下面对命名空间的做一点说明:将子系统的全局属性封装到命名空间中,每个进程关联到一个选定的命名空间。每个可以感知命名空间的子系统都必须提供一个数据结构,将所有通过命名空间形式提供的对象集中起来。struct nsproxy 用于汇集指向特定于子系统的命名空间包装的指针:struct nsproxy atomic_t count; struct uts_namespace *uts_ns; 包含内核的名称、版本、底层体系结构类型等信息 struct ipc_namespace *ipc_ns;进程间通信的IPC有关信息。 struct mnt_namespace *mnt_ns;已经装载的文件系统的视图。 struct pid_namespace *pid_ns;有关进程ID的信息 struct net *net_ns;半酣所有网络相关的命名空间参数;与上面命名空间相对应的几个标志:CLONE_NEWUTS:创建新的utsname组 CLONE_NEWIPC :创建新的IPC命名空间CLONE_NEWUSER:创建新的用户命名空间 CLONE_NEWPID:创建新的PID命名空间CLONE_NEWNET:创建新的网络命名空间 retval = copy_namespaces(clone_flags, p); if (retval) goto bad_fork_cleanup_mm; retval = copy_io(clone_flags, p); if (retval) goto bad_fork_cleanup_namespaces;复制进程中特定于线程的数据并且让CPU上下文中的pc字段指向函数ret_from_fork,该函数用汇编实现,fork调用就是通过它返回到子进程中的。 retval = copy_thread(clone_flags, stack_start, stack_size, p, regs); if (retval) goto bad_fork_cleanup_io;为进城分配pid,下面函数做了很多处理,将在后面分析。 if (pid != &init_struct_pid) retval = -ENOMEM; pid = alloc_pid(p-nsproxy-pid_ns); if (!pid) goto bad_fork_cleanup_io; 字段p-pid中保存的是进程全局pid,所以它的pid应当从最顶层命名空间中获取,nr = pid-numbers0.nr;(每一个进程在它之上的每一级命名空间中都有一个id)。 p-pid = pid_nr(pid); p-tgid = p-pid;如果创建的是线程,则它的线程组id于当前进程的线程组id相同 if (clone_flags & CLONE_THREAD) p-tgid = current-tgid; p-set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? child_tidptr : NULL; p-clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? child_tidptr : NULL;. if (clone_flags & (CLONE_VM|CLONE_VFORK) = CLONE_VM). 如果创建线程就将 p-exit_signal设为-1,因为只有当线程组的最后一个成员死亡才会产生一个信号已通知线程组首领进程的父进程。 if (clone_flags & CLONE_THREAD) p-exit_signal = -1; else if (clone_flags & CLONE_PARENT) p-exit_signal = current-group_leader-exit_signal; else p-exit_signal = (clone_flags & CSIGNAL);. p-group_leader = p; INIT_LIST_HEAD(&p-thread_group); INIT_HLIST_HEAD(&p-task_works); cgroup_fork_callbacks(p); cgroup_callbacks_done = 1; write_lock_irq(&tasklist_lock);如果创建线程,新进程与当前进程有相同的父进程,否则当前进程就是新进程的父进程。 if (clone_flags & (CLONE_PARENT|CLONE_THREAD) p-real_parent = current-real_parent; p-parent_exec_id = current-parent_exec_id; else p-real_parent = current; p-parent_exec_id = current-self_exec_id; spin_lock(¤t-sighand-siglock); recalc_sigpending();如果当前进程还有未决信号就错误返回。 if (signal_pending(current) spin_unlock(¤t-sighand-siglock); write_unlock_irq(&tasklist_lock); retval = -ERESTARTNOINTR; goto bad_fork_free_pid; 如果创建进程就将线程数递增1,并且让新进程与当前进程有相同的组长进程 if (clone_flags & CLONE_THREAD) current-signal-nr_threads+; atomic_inc(¤t-signal-live); atomic_inc(¤t-signal-sigcnt); p-group_leader = current-group_leader; list_add_tail_rcu(&p-thread_group, &p-group_leader-thread_group); if (likely(p-pid) ptrace_init_task(p, (clone_flags & CLONE_PTRACE) | trace);如果新进程是线程组组长进程,就进入下面分支 if (thread_group_leader(p) if (is_child_reaper(pid) p-nsproxy-pid_ns-child_reaper = p;将当前进程组进程的组id和会话id挂到新进程哈希数组task-pids中对应位置。 p-signal-leader_pid = pid; p-signal-tty = tty_kref_get(current-signal-tty); attach_pid(p, PIDTYPE_PGID, task_pgrp(current); attach_pid(p, PIDTYPE_SID, task_session(current); list_add_tail(&p-sibling, &p-real_parent-children); list_add_tail_rcu(&p-tasks, &init_task.tasks); _this_cpu_inc(process_counts); 将新的pid结构挂到新进程哈希数组task-pids中对应位置。 attach_pid(p, PIDTYPE_PID, pid);新进程加入进程集合,递增nr_threads。 nr_threads+; 变量total_forks记录被创建进程的数量 total_forks+; return p;.【do_fork-copy_process-sched_fork】void sched_fork(struct task_struct *p) unsigned long flags; int cpu = get_cpu();初始化进程调度实体se的相关成员 _sched_fork(p);将进程状态置于运行状态 p-state = TASK_RUNNING;新进程的优先级继承于父进程的普通优先级。对于优先级做一下说明,进程有三个优先级:static_prio:静态优先级是进程启动时分配的优先级。它可以用nice系统调用修改。normal_prio:基于进程的静态优先级和调度策略计算出来的优先级,调度策略也可以通过系统调用改变,所以即使静态优先级相同,普通优先级也可能不同。prio:在某些情况下内核需要暂时提高进程的优先级,所以就需要第三个成员来表示。调度器算法中要考虑的优先级就保存在prio。 p-prio = current-normal_prio;sched_reset_on_fork用于判断是否恢复默认的优先级或调度策略。 if (unlikely(p-sch

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论