绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
Linux 2.4 内核说明文档(进程与中断管理篇)(1)
2020-05-21 14:13:18

[list=]
本文档是《Linux2.4 内核说明文档》中的第二部分。以下是整个文档大致目录:
1,启动 (http://bbs.chinaunix.net/forum/viewtopic.php?t=557946 )
2,进程和中断管理
3,虚拟文件系统
4,Linux 页缓冲
5,IPC机制

本篇文档的目录为:
2.1. Tack结构和进程表
2.2. 创建和中止任务与内核线程
2.3. 调度程序
2.4. Linux执行链表
2.5. 等待队列
2.6. 内核时钟
2.7. Bottom Halves
2.8. 任务队列
2.9. I386体系中系统调用实现
2.10. 原子操作
2.11. 旋转锁、读写旋转锁和Big-Reader旋转锁
2.12. 信号灯和读写信号灯
2.13. 装载模块的内核支持

一下是正文:
2. 进程和中断管理

2.1. Tack结构和进程表
linux下的每个进程都是动态分配一个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体系结构上主要意味着大数为物理内存页数/4,例如:在一个512M内存机器上你可以创建32K线程。这对于旧版本(2.2或者更老)内核的4K限制是一个可观的改进,而且这可以在运行时使用系统调用sysctl(2)修改KERN_MAX_THREADS,或者简单使用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系统上进程的关联表现为一个以下两个方式链接的task_struct结构的集合:
1) 以pid为键值的hash表。
2) 通过p->;next_task和p->;prev_task指针连接的双向链表。
这个hash表名为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) >;>; ^ (x)) & (PIDHASH_SZ . 1))

所有的任务以他们的pid为键值存放到hash表中,并假定均匀地从(0 to PID_MAX-1)分布。这个hash表用来通过指定的pid快速的找到task结构,搜索函数find_task_pid()定义在include/linux/sched.h中:

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;
}

在每个hash链上的所有任务都通过p->;pidhash_next和p->;pidhash_pprev连接起来,这在hash_pid函数和unhash_pid函数将指定进程插入hash表或者移出hash表时使用。所有的操作都受到tasklist_lock写同步锁保护。
而双向链表则为系统遍历所有的任务提供了方便,这个操作由定义在include/linux/sched.h中的for_each_tack()宏来实现。

#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) 是不在链表里的。进程hash表和进程链表的修改操作,特别是fork函数,exit函数和ptrace函数,必须调用tasklist_lock写同步锁。更有趣的是,所有的写操作还必须屏蔽当前CPU的中断,这个原因是显而易见的:send_sigio函数遍历了进程表,这样需要调用tasklist_lock读同步锁,并且该函数是kill_fasync函数在中断环境下调用的。
现在我们已经知道task_struct结构是怎样链接到一起的,现在让我们分析一下task_struct结构的成员。这些成员是UNIX系统的proc结构和user结构松散组合到一起的。
其他UNIX版本总是将进程状态信息作为单独一部分常驻内存,其他部分则作为进程运行时所需信息,如此简陋的设计仅仅因为内存时非常宝贵的资源。现代操作系统(如Linux或者FreeBSD)并不做如此区分,而是在内核常驻内存的数据结构中维护进程状态。
include/linux/sched.h中定义了task_struct结构,并且通常大写为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时没有把TASK_EXCLUSIVE的值上调。
可变量p->;state的定义意味着它自身可以被中断处理者异步修改。
1) TASK_RUNNING:含义是假定任务已经处于运行队列中。至于不是已经处于运行队列的原因是由于将一个任务标识为TASK_RUNNING和将该任务移动到运行队列不是一个原子操作。从运行队列角度考虑,操作时需要保持runqueue_lock读同步锁。如果这样操作,你将发现在运行队列的每个任务都处于TASK_RUNNING状态。然后,反过来却不一定。同样地,驱动程序可以标识他们自身状态为TASK_INTERRUPTIBLE,然后调用schedule()函数,这个函数将从运行队列移出它自己(除非当时有一个导致它滞留在运行队列的未处理信号)。
2) TASK_INTERRUPTIBLE:含义是任务处于休眠状态但可以通过一个信号或者休眠中止时钟唤醒。
3) TASK_UNINTERRUPTIBLE:含义类似于TASK_INTERRUPTIBL,但任务不能被唤醒。
4) TASK_ZOMBIE:含义是任务已经被中止但它的状态还没被父进程获取。
5) TASK_STOPPED:含义是由于任务控制信号或者ptrace系统调用,任务已经被停止。
6) TASK_EXCLUSIVE:含义是这不是一个单独状态,但能够与TASK_INTERRUPTIBLE或者TASK_UNINTERRUPTIBLE状态并存(OR操作)。这意味着当任务与其他在等待队列休眠时,它可以单独被唤醒而不需要唤醒整个等待队列的任务。
任务标记包含了关于非互相排斥的进程状态信息。

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 and p->;rt_priority字段和调度程序关联,并将在后面描述。
p->;mm 和p->;active_mm字段分别指向mm_struct结构描述的进程地址空间和有效地址空间(如果这个进程不是内核进程的话)。这使得当任务被调度离开时TLB能够在地址空间自由切换。所以,如果当前正在执行内核任务(没有p->;mm),则它的next->;active_mm将被设置为已经被调度离开的任务的prev->;active_mm,如果pre-mm != NULL,则这个地址将和prev->;mm相同。如果CLONE_VM标识传递到了clone系统调用或者依靠vfork系统调用,则地址空间可以在任务之间共享。
p->;exec_domain和p->;personality字段与任务的特性相关,也就是为了模仿UNIX特性的系统调用。
p->;fs字段包含了文件系统信息,在linux下有三个方面的含义:
1) root目录实体结构和挂载点;
2) 预备的root目录实体和挂载点;
3) 当前工作目录实体和挂载点;
这个结构同样包含了一个引用计数,因为当进行带CLONE_FS标识的clone系统调用时,它是共享的。
p->;files字段包含了文件句柄表,这在进行带CLONE_FILES标识clone系统调用时也是多任务共享的。
p->;sig字段包含了信号处理函数入口,以CLONE_SIGHAND参数执行clone操作后页可以在进程间共享。

文章来源CU社区:Linux 2.4 内核说明文档(进程与中断管理篇)


分享好友

分享这个小栈给你的朋友们,一起进步吧。

内核源码
创建时间:2020-05-18 13:36:55
内核源码精华帖内容汇总
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

技术专家

查看更多
  • 飘絮絮絮丶
    专家
戳我,来吐槽~