内核调度的对象是线程,它是进程中的活动对象,拥有一个独立的程序计数器、进程栈和一组进程寄存器。
Linux对线程和进程并不特别区分
通过调用fork()系统调用---->clone()系统调用,fork从内核返回两次,一次回到父进程,一次回到新产生的子进程。
task_list双向循环链表,每一项都是task_struct结构,称为进程描述符(process descriptor)
各个进程的task_struct存放在它们内核栈的末端,主要是通过栈指针存取方便,避免使用额外的寄存器。
pid 默认最大是32768,存放在各自的进程描述符(PC)中,但是可以修改/proc/sys/kernel/pid_max来修改上限。
访问PC是一个频繁的操作,各体系结构实现不一样,PowerPC使用了专门的寄存器r2存放。
进程各状态:TASK_RUNNING,TASK_INTERRUPTIBLE,TASK_UNINTERRUPTIBLE,__TASK_TRACED,__TASK_STOPPED
通常用set_task_state(task, state)调整某进程状态
系统调用和异常处理程序是对内核明确定义的接口,进程只有通过这些接口才能陷入内核执行。在中断上下文中,执行的是中断处理程序,
不会有进程上下文。因为中断处理程序不代表进程上下文。
所有进程都是PID为1的init进程的后代。
产生(spawn)进程机制:在新地址空间里创建进程,读入可执行文件,最后开始执行。
Unix:fork(),拷贝当前进程创建子进程;exec(),读取可执行文件载入地址空间开始执行。
Linux:fork()使用写时拷贝(copy-on-write),是一种推迟甚至免拷贝数据的技术。
fork调用链,fork()/vfork()/__clone()---->clone()---->kenel/fork.c::do_fork(),内核有意选择子进程首先执行,这样子进程都会马上调用
exec(),可以避免写时拷贝额外开销。
Linux把所有线程都当作进程来实现,没有特别的调度算法和数据结构表征线程,线程仅视为一个与其他进程共享某些资源的进程。
Windows和Solaris内核提供了专门支持线程的机制。
clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0 )//创建线程的实现
clone(SIGCHLD, 0)//普通的fork实现
内核线程没有独立的地址空间(其指向地址空间的mm指针被置为NULL),它们只运行与内核空间,可被调度和抢占。
<linux/kthread.h>
struct task_struct *kthread_create(int (*threadfn) (void *data),
void *data,
const char namefmt[],
...)
新建进程处于不可运行状态,必须通过调用wake_up_process()明确唤醒,不然不会主动运行。
进程析构一般由自身引起,显示或隐式的发生在进程调用exit()---->kernel/exit.c::do_exit()系统调用时;其子进程另找父进程,一般是进程组的其他进程或init进程,然后内核释放它所占资源并通知其父进程。
调用do_exit()后,线程不能运行了,但其进程描述符还保留着让系统可以在子进程终结后能获取它的信息。父进程获取已终结的子进程的信息后,
或通知内核它并不关注那些信息后,子进程的task_struct结构才被释放。