您的位置 首页 电子

学会Linux进程办理的办法

学会Linux进程管理的方法-Linux 是一种动态系统,能够适应不断变化的计算需求。Linux 计算需求的表现是以进程 的通用抽象为中心的。进程可以是短期的(从命令行执行的一个命令),也可以是长期的(一种网络服务)。因此,对进程及其调度进行一般管理就显得极为重要。

Linux® 的用户空间进程的创立和办理所触及的原理与 UNIX® 有许多共同点,但也有一些特定于 Linux 的共同之处。在本文中,了解 Linux 进程的生命周期,探究用户进程创立、内存办理、调度和毁掉的内核内情。

Linux 是一种动态体系,能够习惯不断改变的核算需求。Linux 核算需求的体现是以进程 的通用笼统为中心的。进程可所以短期的(从指令行履行的一个指令),也可所以长时刻的(一种网络服务)。因而,对进程及其调度进行一般办理就显得极为重要。

在用户空间,进程是由进程标识符(PID)标明的。从用户的视点来看,一个 PID 是一个数字值,可专一标识一个进程。一个 PID 在进程的整个生命期间不会更改,但 PID 能够在进程毁掉后被从头运用,所以对它们进行缓存并不见得总是抱负的。

在用户空间,创立进程能够选用几种办法。能够履行一个程序(这会导致新进程的创立),也能够在程序内,调用一个 fork 或 exec 体系调用。fork 调用会导致创立一个子进程,而 exec 调用则会用新程序替代当时进程上下文。接下来,我将对这几种办法进行评论以便您能很好地了解它们的作业原理。

在本文中,我将依照下面的次序打开对进程的介绍,首要展现进程的内核标明以及它们是怎么在内核内被办理的,然后来看看进程创立和调度的各种办法(在一个或多个处理器上),终究介绍进程的毁掉。

进程标明

在 Linux 内核内,进程是由相当大的一个称为 task_struct 的结构标明的。此结构包含一切标明此进程所必需的数据,此外,还包含了许多的其他数据用来核算(accounting)和保护与其他进程的联系(父和子)。对 task_struct 的完好介绍超出了本文的规模,清单 1 给出了 task_struct 的一小部分。这些代码包含了本文所要探究的这些特定元素。task_struct 坐落 ./linux/include/linux/sched.h。


清单 1. task_struct 的一小部分

struct task_struct {volaTIle long state;void *stack;unsigned int flags;int prio, staTIc_prio;struct list_head tasks;struct mm_struct *mm, *acTIve_mm;pid_t pid;pid_t tgid;struct task_struct *real_parent;char comm[TASK_COMM_LEN];struct thread_struct thread;struct files_struct *files;…};

在清单 1 中,能够看到几个预料之中的项,比方履行的状况、仓库、一组标志、父进程、履行的线程(能够有许多)以及敞开文件。我稍后会对其进行详细阐明,这儿只简略加以介绍。state 变量是一些标明使命状况的比特位。最常见的状况有:TASK_RUNNING 标明进程正在运转,或是排在运转行列中正要运转;TASK_INTERRUPTIBLE 标明进程正在休眠、TASK_UNINTERRUPTIBLE 标明进程正在休眠但不能叫醒;TASK_STOPPED 标明进程中止等等。这些标志的完好列表能够在 ./linux/include/linux/sched.h 内找到。

flags 界说了许多指示符,标明进程是否正在被创立(PF_STARTING)或退出(PF_EXITING),或是进程当时是否在分配内存(PF_MEMALLOC)。可履行程序的称号(不包含途径)占用 comm(指令)字段。

每个进程都会被赋予优先级(称为 static_prio),但进程的实践优先级是依据加载以及其他几个要素动态决议的。优先级值越低,实践的优先级越高。

tasks 字段供给了链接列表的才能。它包含一个 prev 指针(指向前一个使命)和一个 next 指针(指向下一个使命)。

进程的地址空间由 mm 和 active_mm 字段标明。mm 代表的是进程的内存描述符,而 active_mm 则是前一个进程的内存描述符(为改善上下文切换时刻的一种优化)。

thread_struct 则用来标识进程的存储状况。此元素依赖于 Linux 在其上运转的特定架构,在 ./linux/include/asm-i386/processor.h 内有这样的一个比方。在此结构内,能够找到该进程自履行上下文切换后的存储(硬件注册表、程序计数器等)。

进程办理

最大进程数

在 Linux 内尽管进程都是动态分配的,但仍是需求考虑最大进程数。在内核内最大进程数是由一个称为 max_threads 的符号标明的,它能够在 ./linux/kernel/fork.c 内找到。能够经过 /proc/sys/kernel/threads-max 的 proc 文件体系从用户空间更改此值。

现在,让我们来看看怎么在 Linux 内办理进程。在许多状况下,进程都是动态创立并由一个动态分配的 task_struct 标明。一个破例是 init 进程自身,它总是存在并由一个静态分配的 task_struct 标明。在 ./linux/arch/i386/kernel/init_task.c 内能够找到这样的一个比方。

Linux 内一切进程的分配有两种办法。第一种办法是经过一个哈希表,由 PID 值进行哈希核算得到;第二种办法是经过双链循环表。循环表十分适合于对使命列表进行迭代。由于列表是循环的,没有头或尾;可是由于 init_task 总是存在,所以能够将其用作持续向前迭代的一个锚点。让我们来看一个遍历当时使命集的比方。

使命列表无法从用户空间拜访,但该问题很简略处理,办法是以模块方式向内核内刺进代码。清单 2 中所示的是一个很简略的程序,它会迭代使命列表并会供给有关每个使命的少数信息(name、pid 和 parent 名)。留意,在这儿,此模块运用 printk 来宣布成果。要查看详细的成果,能够经过 cat 实用工具(或实时的 tail -f /var/log/messages)查看 /var/log/messages 文件。next_task 函数是 sched.h 内的一个宏,它简化了使命列表的迭代(回来下一个使命的 task_struct 引证)。


清单 2. 宣布使命信息的简略内核模块(procsview.c)

#include #include #include int init_module( void ){ /* Set up the anchor point */ struct task_struct *task = &init_task; /* Walk through the task list, until we hit the init_task again */ do { printk( KERN_INFO “*** %s [%d] parent %s\n”,task->comm, task->pid, task->parent->comm ); } while ( (task = next_task(task)) != &init_task ); return 0;}void cleanup_module( void ){ return;}

能够用清单 3 所示的 Makefile 编译此模块。在编译时,能够用 insmod procsview.ko 刺进模块目标,也能够用 rmmod procsview 删去它。


清单 3. 用来构建内核模块的 Makefile

obj-m += procsview.oKDIR := /lib/modules/$(shell uname -r)/buildPWD := $(shell pwd)default:$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules

刺进后,/var/log/messages 可显现输出,如下所示。从中能够看到,这儿有一个闲暇使命(称为 swapper)和 init 使命(pid 1)。

Nov 12 22:19:51 mtj-desktop kernel: [8503.873310] *** swapper [0] parent swapperNov 12 22:19:51 mtj-desktop kernel: [8503.904182] *** init [1] parent swapperNov 12 22:19:51 mtj-desktop kernel: [8503.904215] *** kthreadd [2] parent swapperNov 12 22:19:51 mtj-desktop kernel: [8503.904233] *** migration/0 [3] parent kthreadd…

留意,还能够标识当时正在运转的使命。Linux 保护一个称为 current 的符号,代表的是当时运转的进程(类型是 task_struct)。假如在 init_module 的尾部刺进如下这行代码:

printk( KERN_INFO, “Current task is %s [%d], current->comm, current->pid );

会看到:

Nov 12 22:48:45 mtj-desktop kernel: [10233.323662] Current task is insmod [6538]

留意到,当时的使命是 insmod,这是由于 init_module 函数是在 insmod 指令履行的上下文运转的。current 符号实践指的是一个函数(get_current)并可在一个与 arch 有关的头部中找到(比方 ./linux/include/asm-i386/current.h 内找到)。

进程创立

体系调用函数

您或许现已看到过体系调用的形式了。在许多状况下,体系调用都被命名为 sys_* 并供给某些初始功能以完结调用(例如过错查看或用户空间的行为)。实践的作业常常会委派给其他一个名为 do_* 的函数。

让我们无妨亲身看看怎么从用户空间创立一个进程。用户空间使命和内核使命的底层机制是共同的,由于二者终究都会依赖于一个名为 do_fork 的函数来创立新进程。在创立内核线程时,内核会调用一个名为 kernel_thread 的函数(拜见 ./linux/arch/i386/kernel/process.c),此函数履行某些初始化后会调用 do_fork。

创立用户空间进程的状况与此相似。在用户空间,一个程序会调用 fork,这会导致对名为 sys_fork 的内核函数的体系调用(拜见 ./linux/arch/i386/kernel/process.c)。函数联系如图 1 所示。


图 1. 担任创立进程的函数的层次结构
 

从图 1 中,能够看到 do_fork 是进程创立的根底。能够在 ./linux/kernel/fork.c 内找到 do_fork 函数(以及协作函数 copy_process)。

do_fork 函数首要调用 alloc_pidmap,该调用会分配一个新的 PID。接下来,do_fork 查看调试器是否在盯梢父进程。假如是,在 clone_flags 内设置 CLONE_PTRACE 标志以做好履行 fork 操作的预备。之后 do_fork 函数还会调用 copy_process,向其传递这些标志、仓库、注册表、父进程以及最新分配的 PID。

新的进程在 copy_process 函数内作为父进程的一个副本创立。此函数能履行除发动进程之外的一切操作,发动进程在之后进行处理。copy_process 内的第一步是验证 CLONE 标志以保证这些标志是共同的。假如不共同,就会回来 EINVAL 过错。接下来,问询 Linux Security Module (LSM) 看当时使命是否能够创立一个新使命。

接下来,调用 dup_task_struct 函数(在 ./linux/kernel/fork.c 内),这会分配一个新 task_struct 并将当时进程的描述符仿制到其内。在新的线程仓库设置好后,一些状况信息也会被初始化,而且会将操控回来给 copy_process。操控回到 copy_process 后,除了其他几个约束和安全查看之外,还会履行一些惯例办理,包含在新 task_struct 上的各种初始化。之后,会调用一系列仿制函数来仿制此进程的各个方面,比方仿制敞开文件描述符(copy_files)、仿制符号信息(copy_sighand 和 copy_signal)、仿制进程内存(copy_mm)以及终究仿制线程(copy_thread)。

之后,这个新使命会被指定给一个处理程序,一起对答应履行进程的处理程序进行额定的查看(cpus_allowed)。新进程的优先级从父进程的优先级承继后,履行一小部分额定的惯例办理,而且操控也会被回来给 do_fork。在此刻,新进程存在但没有运转。do_fork 函数经过调用 wake_up_new_task 来修正此问题。此函数(可在 ./linux/kernel/sched.c 内找到)初始化某些调度程序的惯例办理信息,将新进程放置在运转行列之内,然后将其唤醒以便履行。终究,一旦回来至 do_fork,此 PID 值即被回来给调用程序,进程完结。

进程调度

存在于 Linux 的进程也可经过 Linux 调度程序被调度。尽管调度程序超出了本文的评论规模,但 Linux 调度程序保护了针对每个优先级其他一组列表,其间保存了 task_struct 引证。使命经过 schedule 函数(在 ./linux/kernel/sched.c 内)调用,它依据加载及进程履行前史决议最佳进程。

进程毁掉

进程毁掉能够经过几个事情驱动 — 经过正常的进程完毕、经过信号或是经过对 exit 函数的调用。不论进程怎么退出,进程的完毕都要凭借对内核函数 do_exit(在 ./linux/kernel/exit.c 内)的调用。此进程如图 2 所示。


图 2. 完结进程毁掉的函数的层次结构
 

do_exit 的意图是将一切对当时进程的引证从操作体系删去(针对一切没有同享的资源)。毁掉的进程先要经过设置 PF_EXITING 标志来标明进程正在退出。内核的其他方面会利用它来防止在进程被删去时还企图处理此进程。将进程从它在其生命期间获得的各种资源别离开来是经过一系列调用完结的,比方 exit_mm(删去内存页)和 exit_keys(开释线程会话和进程安全键)。do_exit 函数履行开释进程所需的各种核算,这之后,经过调用 exit_notify 履行一系列告诉(比方,奉告父进程其子进程正在退出)。终究,进程状况被更改为 PF_DEAD,而且还会调用 schedule 函数来挑选一个即将履行的新进程。请留意,假如对父进程的告诉是必需的(或进程正在被盯梢),那么使命将不会完全消失。假如无需任何告诉,就能够调用 release_task 来实践回收由进程运用的那部分内存。

完毕语

Linux 还在不断演进,其间一个有待进一步创新和优化的范畴便是进程办理。在坚持 UNIX 原理的一起,Linux 也在不断打破。新的处理器架构、对称多处理(SMP)以及虚拟化都将促进在内核范畴内获得新进展。其间的一个比方便是 Linux 版别 2.6 中引进的新的 O(1) 调度程序,它为具有许多使命的体系供给了可伸缩性。其他一个比方便是运用 Native POSIX Thread Library (NPTL) 更新了的线程模型,与之前的 LinuxThreads 模型比较,它带来了更为有用的线程处理。

 

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/qiche/dianzi/99811.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部