进程类别 ======== 虽然我们区别linux进程类别,但是我还是想说linux下只有一种类型的进程,那就是task_struct,其实linux也没有线程的概念,只是将那些也与其他进程共享资源的进程 称之为线程 1) 一个进程由于其运行空间的不同,从而有内核线程和用户进程的区分,内核线程运行在内核空间,之所以称之为线程时因为它没有虚拟地址空间,只能访问内核的代码和数据,而用户进程则运行在用户空间,但是可以通过中断,系统调用等方式从用户态陷入内核态. 2) 用户进程运行在用户空间上,而一些通过共享资源实现的一组进程我们称之为线程组 因此linux进程分3种,内核线程(或者叫核心进程)、用户进程、用户线程 进程与线程 ----------- 进程时一个具有独立功能的程序关于某个数据集合的一次运行活动,它可以申请和用于系统资源,是一个动态的概念,是一个活动的实体.它不只是程序的代码,还包括当前的活动.进程是一个执行中的程序 通常在一个进程中可以包含若干个线程,他们可以利用进程所拥有的资源,在引入线程的操作系统中,通常把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。由于线程比进程 小,基本上不拥有系统资源,故对他的调度所付出的开销就会小的多,能更高效的提高系统内多个程序间并发执行的程度 线程和进程的区别在于,子进程和父进程有不同的代码和数据空间,而多个线程则共享数据空间,每个线程都有自己的执行堆栈和程序计数器为其执行上下文,多线程主要是为了节约CPU时间 内核线程 -------- 内核线程就是内核的分身 1) 内核线程只运行在内核态,不受用户上下文的拖累 2) 处理器竞争,可以在全系统范围内竞争处理器资源 3) 使用资源;唯一使用的资源内核栈和上下文切换时保存寄存器的空间 4) 调度:调度的开销可能和进程自身差不多昂贵 5) 同步效率: 资源的同步和数据共享比整个进程的数据同步和共享要低一些 进程的创建流程 -------------- 进程的复制fork和加载execve ^^^^^^^^^^^^^^^^^^^^^^^^^^ 我们在linux下进行编程,往往都是通过fork出来一个新的程序,fork从字面理解就是分叉,这其实就意味着我们的fork进程并不是真正从无到有被创建出来的 一个进程,包括代码、数据和分配给进程的资源,它其实是从现有的进程(父进程)复制出的一个副本(子进程),fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程, 也就是两个进程可以做完全相同的事,然后如果我们通过execve为子进程加载新的程序后,那么新的进程将开始执行新的应用.简单来说,新的进程通过fork和execve创建的,首先 通过fork从父进程分叉处一个基本一致的副本,然后通过execve来加载新的应用程序镜像 写时复制技术 ^^^^^^^^^^^^^ 如果在fork出一个子进程时完全将父进程的所有资源全部拷贝,这样会导致效率很低.现在的linux采用一种方法解决这个问题,我们称之为写时复制(copy on write).这种思想比较简单, 父进程与子进程共享页帧而不是复制也帧,只要页帧被共享,就被赋予了只读属性,即页帧被保护,无论是父进程还是子进程试图写一个共享的页帧,就会产生一个异常,这时内核就把这个页 复制到一个新的页中并标记为可写.原来的页仍然是写保护的,当其他进程试图写入时,内核检查写进程是否是这个页帧的唯一属主. linux提供do_wp_page()函数去处理子进程或者父进程访问共享页的写操作时引发的异常(page_fault int 114) 线程的实现机制 ^^^^^^^^^^^^^^ 从内核的角度来说,它并没有线程这个概念,linux把所有线程都当作进程来实现,内核中没有准备特别的调度算法或者定义特别的数据结构来表示线程.相反线程仅仅被视为一个与其他进程共享 某些资源的进程,每个线程都拥有唯一隶属于自己的task_struct所以在内核看来,线程就是一个普通的进程 进程task_struct中pid存储的时内核对该进程的唯一标识,即进程则标示进程号,对线程来说就是其线程号,一个线程组的所有线程与领头线程具有相同的进程号,存入tgid字段.因此getpid()返回 当前进程的进程号,返回的是tgid而不是Pid,对于用户空间来说同组的线程拥有相同的进程号即tgid,而对于内核来说,pid是唯一区分每个进程的标示 内核线程与普通进程的异同 ^^^^^^^^^^^^^^^^^^^^^^^^^ 1) 跟普通进程一样,内核线程也有优先级和被调度.当和用户进程拥有相同的static_prio时,内核线程有机会得到更多的cpu资源 2) 内核线程的bug直接影响内核,可能搞死整个系统,但是用户进程在内核的管理下其bug最严重的情况也只会把自己整崩溃 3) 内核线程没有自己的地址空间,所以他们的current->mm都是空的 4) 内核线程只能在内核空间操作,不能与用户空间交互 内核线程是拥有核心堆栈的,事实上这个核心堆栈跟task_struct的thread_info共享8k的空间 内核线程创建 ^^^^^^^^^^^^ 有两种方法创建内核线程,一种是kernel_thread()接口,另一种是kthread_create()接口 - kernel_thread :: int kernel_thread(int (*fn)(void *), void *arg, unsigned long flags); //fn为线程函数,arg为线程参数,flags为标记 void daemonize(const char * name, ...); //name内核线程的名字 使用kernel_thread接口创建的线程,必须在线程中调用daemonize()函数,这是因为只有当线程的父进程指向 ``kthreadadd`` 时,该线程才算是内核线程. 而daemonize()函数的主要工作就是将该线程的父进程改为kthreadadd内核线程.默认情况下,调用daemonize后会阻塞所有信号,如果想操作某个信号,可以 调用 ``allow_signal`` 函数 - kthread_create :: struct task_struct *kthread_create(int (*threadfn)(void *data), void *data, const char namefmt[], ...); //threadfn为线程函数,data为线程函数参数,namefmt为线程名称,可被格式化的,类似printf一样传入某种格式的线程名 调用该接口后创建的线程,不会马上运行,而是需要将kthread_create()返回的task_struct指针传给wake_up_process(),然后通过此函数运行线程 - kthread_run :: struct task_struct *kthread_run (int (*threadfn)(void *data), void *data, const char *namefmt, ...) 线程一旦启动起来会一直运行,除非该线程主动调用 ``do_exit()`` 函数,或者其他进程调用 ``kthread_stop`` 函数结束线程的运行 :: int kthread_stop(struct task_struct *thread); int wake_up_process(struct task_struct *p);