分享
 
 
 

Linux Kernel Internals(2)--Process and Interrupt Management

王朝system·作者佚名  2006-11-24
窄屏简体版  字體: |||超大  

Next Previous Contents

--------------------------------------------------------------------------------

2. 进程和中断管理

2.1 任务结构和进程表

Linux下每一个进程动态分配一个struct task_struct结构。可以建立的最大进程数只是由当前的物理内存数量所限制(参见 kernel/fork.c:fork_init()):

--------------------------------------------------------------------------------

/*

* The default maximum number of threads is set to a safe

* value: the thread structures can take up at most half

* of memory.

*/

max_threads = mempages / (THREAD_SIZE/PAGE_SIZE) / 2;

--------------------------------------------------------------------------------

在IA32体系结构中, 它基本上等于num_physpages/4. 例如,在一台具有512M的机器上,你可以建立32k个线程。对于老的核心(2.2和更早的版本)4k多一点的限制来说,这是一个很大的进步。而且,这还可以在运行时用KERN_MAX_THREADS sysctl(2)改变或是简单的通过procfs接口来调整:

--------------------------------------------------------------------------------

# cat /proc/sys/kernel/threads-max

32764

# echo 100000 > /proc/sys/kernel/threads-max

# cat /proc/sys/kernel/threads-max

100000

# gdb -q vmlinux /proc/kcore

Core was generated by `BOOT_IMAGE=240ac18 ro root=306 video=matrox:vesa:0x118'.

#0 0x0 in ?? ()

(gdb) p max_threads

$1 = 100000

--------------------------------------------------------------------------------

Linux系统的一组进程是通过许多的struct task_struct结构来表示的, 它们通过两种方法来链接在一起:

作为一个哈希表, 通过pid来建立

作为一个圆环,用p->next_task 和 p->prev_task指针建立的一个双向链表。

这个哈希表称为 pidhash[],在include/linux/sched.h中定义:

--------------------------------------------------------------------------------

/* PID hashing. (shouldnt this be dynamic?) */

#define PIDHASH_SZ (4096 >> 2)

extern struct task_struct *pidhash[PIDHASH_SZ];

#define pid_hashfn(x) ((((x) >> 8) ^ (x)) & (PIDHASH_SZ - 1))

--------------------------------------------------------------------------------

任务通过它们的pid值来建立哈希表,上面的哈希函数可以在它们的范围中(0 to PID_MAX-1)均一的分配元素。哈希表用include/linux/sched.h中的find_task_pid() 内联函数,可以快速查找一个给定pid的任务:

--------------------------------------------------------------------------------

static inline struct task_struct *find_task_by_pid(int pid)

{

struct task_struct *p, **htable = &pidhash[pid_hashfn(pid)];

for(p = *htable; p && p->pid != pid; p = p->pidhash_next)

;

return p;

}

--------------------------------------------------------------------------------

每个哈希列表中的任务(即散列成同样的值)是通过p->pidhash_next/pidhash_pprev 来链接的,hash_pid() 和 unhash_pid() 使用它们来插入和删除一个哈希表中指定的进程 。这些都是在被称为tasklist_lock的读写自旋锁的保护下完成的。

圆环双向链表使用p->next_task/prev_task 来进行维护,因此可以在系统中容易地遍历所有任务。这是通过include/linux/sched.h中的宏for_each_task() 来完成的:

--------------------------------------------------------------------------------

#define for_each_task(p)

for (p = &init_task ; (p = p->next_task) != &init_task ; )

--------------------------------------------------------------------------------

for_each_task()使用者应该采用tasklist_lock来读。注意for_each_task() 是用init_task来标记列表的开始和结束,这是安全的方法,因为空闲任务(pid=0)从来不存在。

进程哈希表或进程表链的修改,特别是 fork(), exit() 和 ptrace(), 必须使用 tasklist_lock 来写。更有趣的是写时还必须在本地CPU上关闭中断。原因很简单:send_sigio() 函数遍历任务列表从而采用tasklist_lock 来读,在中断上下文中,它还被 kill_fasync() 调用。这就是为什么要在写时禁止中断的原因了,而读取时却不需要。

现在我们已明白 task_struct 结构是如何相互链接在一起,让我们检查一下 task_struct的成员。它们松散地对应着 UNIX 'proc' 和 'user' 结构的组合。

UNIX 的其它版本将任务状态信息分为两部分,一部分内容一直保留在内存(称为 'proc structure' ,它包括进程状态,调度信息等等) ;另外一部分是只有当进程运行时才需要的(称为'u 区' ,包括文件描述符表,磁盘限额信息等等)。这么丑陋的设计唯一的原因就是内存时非常紧缺的资源。现代操作系统(当然,目前只有Linux而不是其它操作系统,比如FreeBSD似乎在这方面朝着Linux提高)不需要这样区分,从而在任何时候都是在核心内存驻留数据结构中维护进程状态。

task_struct 结构申明在include/linux/sched.h ,目前的大小为1680字节。

状态域申明为:

--------------------------------------------------------------------------------

volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */

#define TASK_RUNNING 0

#define TASK_INTERRUPTIBLE 1

#define TASK_UNINTERRUPTIBLE 2

#define TASK_ZOMBIE 4

#define TASK_STOPPED 8

#define TASK_EXCLUSIVE 32

--------------------------------------------------------------------------------

为什么TASK_EXCLUSIVE 定义为32而不是16?因为16已经被TASK_SWAPPING 使用,当我已经删除了TASK_SWAPPING (有时是在2.3.x中)所有的引用后,却忘记了将TASK_EXCLUSIVE 提前。

p->state 中的volatile 申明意味着它可以异步地修改(从中断处理器中):

TASK_RUNNING: 意味着此任务被假定是在运行队列。它可能还不在运行队列中的原因是标记一个任务为TASK_RUNNING和将它放置在运行队列并不是原子性的。为了查看运行队列,你需要持有runqueue_lock 读写自旋锁进行读操作。如果你那样做的话,你将看见任务队列中的每一个任务都处于TASK_RUNNING 状态。然而,反之却未必正确,原因上面已经解释了。同样地,驱动程序(准确地说是它们所运行的进程上下文)可以标记它们自己为TASK_INTERRUPTIBLE (或 TASK_UNINTERRUPTIBLE)然后调用schedule(),这样将会把它从运行队列中删除掉(除非有一个未决的信号,这样它会留在运行队列)。

TASK_INTERRUPTIBLE: 意味着进程正在睡眠,但可以通过一个信号或定时器超时来唤醒。

TASK_UNINTERRUPTIBLE: 跟TASK_INTERRUPTIBLE一样,但它不能被唤醒。

TASK_ZOMBIE: 任务已经终止,但它的状态还没有被父进程(本身的或是收养的)收集(通过wait()调用)

TASK_STOPPED: 任务停止,要么是由于任务控制信号或是因为调用ptrace(2)。

TASK_EXCLUSIVE: 这不是一个单独的状态,它可以与TASK_INTERRUPTIBLE 或 TASK_UNINTERRUPTIBLE中的任一个相或。这意味着此任务与其它许多任务睡眠在等待队列中时,它将被单独唤醒而不是引起一个"雷鸣般的牧群"问题,唤醒队列中的所有任务。

任务标记包含不是互斥的进程状态信息:

--------------------------------------------------------------------------------

unsigned long flags; /* per process flags, defined below */

/*

* Per process flags

*/

#define PF_ALIGNWARN 0x00000001 /* Print alignment warning msgs */

/* Not implemented yet, only for 486*/

#define PF_STARTING 0x00000002 /* being created */

#define PF_EXITING 0x00000004 /* getting shut down */

#define PF_FORKNOEXEC 0x00000040 /* forked but didn't exec */

#define PF_SUPERPRIV 0x00000100 /* used super-user privileges */

#define PF_DUMPCORE 0x00000200 /* dumped core */

#define PF_SIGNALED 0x00000400 /* killed by a signal */

#define PF_MEMALLOC 0x00000800 /* Allocating memory */

#define PF_VFORK 0x00001000 /* Wake up parent in mm_release */

#define PF_USEDFPU 0x00100000 /* task used FPU this quantum (SMP) */

--------------------------------------------------------------------------------

p->has_cpu, p->processor, p->counter, p->priority, p->policy 和 p->rt_priority 域与调度器相关,后面我们将会看到。

域p->mm 和 p->active_mm 分别指向进程的地址空间和活动地址空间(如果没有一个真正的进程,如核心线程),它是由mm_struct 结构描述。 这在当任务被调度出去交换地址空间时可以最小化TLB表。因此,如果我们正调度进核心线程(它没有p->mm) ,于是它的next->active_mm将被设置成调度出去的任务的prev->active_mm ,如果prev->mm != NULL,它将与prev->mm一致。如果CLONE_VM标记被传给clone(2) 系统调用或vfork(2)系统调用,那么地址空间就可以在线程之间共享。

域p->exec_domain 和 p->personality跟任务的特征相关,比如说为了仿真UNIX的外来有趣"特征"的某些系统调用行为方法。

域p->fs 包含文件系统信息,Linux下代表三种信息:

根目录项和安装点,

预备根目录项和安装点,

当前工作目录项和安装点。

这个结构还包括引用记数,因为当CLONE_FS标记传送给clone(2)系统调用时它要在克隆的任务之间共享。

域p->files 包含文件描述符表。这也可以在任务之间共享,只要在clone(2)系统调用中指定了CLONE_FILES标记。

域p->sig包含信号处理器,可以通过CLONE_SIGHAND在克隆的任务之间共享。

2.2 任务和核心线程的建立和终止

操作系统类不同的书定义"进程"有不同的方法,但都是以"程序执行的事例"开始,以"通过clone(2)或fork(2)系统调用来产生"结束。在Linux下,有3种进程:

空闲线程,

核心线程,

用户任务。

空闲线程是在编译时为第一个CPU建立的;然后通过特定体系结构arch/i386/kernel/smpboot.c中的fork_by_hand()(在某些体系结构中它是fork(2)系统调用的手工展开)来为每一个CPU“手工的”建立一个。空闲任务有一个私有的TSS结构,它们在每个CPU数组init_tss中,但共享一个init_task结构。 所有空闲任务的pid = 0 ,没有其它的任务能够共享此pid,即使在clone(2)时使用了CLONE_PID。

核心线程是在核心模式下使用kernel_thread()函数执行clone(2)系统调用来建立的。核心线程通常没有用户地址空间,即p->mm = NULL,因为它们显式地调用exit_mm(),如通过daemonize()函数。核心线程总是直接地访问核心地址空间。它们在很低的区间分配pid号。运行在处理器的第0环意味着核心线程享有所有的I/O特权,并且不能够被调度器抢占。

用户任务是通过clone(2) 或 fork(2) 系统调用来建立的,它们内部都是执行kernel/fork.c:do_fork()。

让我们看看当一个用户进程运行fork(2)系统调用时会发生什么。尽管fork(2)是与体系结构相关的,因为传递给用户栈和寄存器有不同方式,但真正基本的函数do_fork()完成那些工作并且是可移植的。它位于kernel/fork.c。

它完成下面的步骤:

局部变量retval设置成-ENOMEM,如果fork(2)未能分配一个新的任务结构的话errno应该设置成这个值。

如果在clone_flags中设置了CLONE_PID,就返回错误(-EPERM), 除非调用者是空闲线程(仅在启动时)。于是,普通用户线程不能传递CLONE_PID 给clone(2) 并期望能成功。对于fork(2)来说,由于clone_flags 被设置成 SIFCHLD,这并不相关 - 它只有当do_fork() 从 sys_clone()执行时才相关,它从需要的用户空间请求的值传递给clone_flags。

current->vfork_sem 初始化(它在子进程中清除)。sys_vfork() (vfork(2) 系统调用,对应于clone_flags = CLONE_VFORK|CLONE_VM|SIGCHLD) 使父进程睡眠时会用到,直到子进程执行mm_release(), 例如作为exec()执行另外的程序或exit(2)的结果。

使用体系结构相关的alloc_task_struct()宏分配一个新的任务结构。在x86中,它就是用GFP_KERNEL优先级获取一个空闲页。这就是为什么fork(2)系统调用可能睡眠的第一个原因。如果分配失败,我们返回-ENOMEM。

使用赋值语句*p = *current,当前进程的任务结构所有的值都拷贝到新的结构中。 也许这应该用memset来替换?稍后,不应该被子进程继承的值设置成准确的值。

余下的代码采用大核心锁,它们是不可重入的。

如果父进程有用户资源(UID思想,Linux 很灵活,它是提出问题而不是制造事实),然后检验用户是否超出了RLIMIT_NPROC软限制 - 如果是的话,返回-EAGAIN,如果没有超出,就增加由给定uid进程记数p->user->count。

如果系统中任务数量超出可调的max_threads值,返回错误-EAGAIN。

如果正在执行的二进制代码属于模块化的执行域,增加相应的模块引用记数。

如果正在执行的二进制代码属于模块化的二进制格式,增加相应的模块引用记数。

子进程标记为'还未执行' (p->did_exec = 0)

子进程标记为'不可交换' (p->swappable = 0)

将子进程置为'不可中断睡眠'状态,即p->state = TASK_UNINTERRUPTIBLE (TODO: 为什么要这样做? 我认为它是不需要的 - 去掉它,Linus 确认它是不需要的)

根据clone_flags的值设置子进程的p->flags;对于简单的fork(2),它将是p->flags = PF_FORKNOEXEC。

子进程的pid p->pid在kernel/fork.c:get_pid()里用快速算法来设置 (TODO: lastpid_lock 自旋锁显得多余了,因为get_pid() 总是在大核心锁下从do_fork()中调用,同样去掉get_pid()的标记参数,在2000-06-20将补丁送给了Alan - 后来又发了一次)。

do_fork()中剩下的代码初始化子进程其余的任务结构。在最后,子进程的任务结构被散列进pidhash哈希表,子进程被唤醒 (TODO: wake_up_process(p) 设置 p->state = TASK_RUNNING 并且增加这个进程到运行队列,因此我们do_fork())之前多半不需要设置 p->state 为 TASK_RUNNING 。感兴趣的部分是设置p->exit_signal 为 clone_flags & CSIGNAL, 这对于 fork(2) 就意味着SIGCHLD ,设 p->pdeath_signal 为 0。当一个进程‘忘记'了原始的父进程(由于死了),就会用到pdeath_signal,它可以通过prctl(2)系统调用中的PR_GET/SET_PDEATHSIG命令来设置/获取(你也许会认为通过在prctl(2)用户空间的指针参数来返回pdeath_signal值这种方法很笨 - 是我的过失,在Andries Brouwer更新手册页之后已经太迟了还没有更正;)

这样任务就建立了。有几种原因使得任务终止:

执行exit(2) 系统调用;

接收到一个信号,缺省处理就是死亡;

在某些异常条件下被迫死亡;

通过func == 1调用bdflush(2)(这是Linux专用的,由于和在/etc/inittab中仍然有‘update'行的老的发布兼容的目的 - 现在更新的工作是通过核心线程kupdate来完成的)。

在Linux下实现系统调

[1] [2] [3] 下一页

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有