精华内容
下载资源
问答
  • D,一般删除都是文件索引,如果两个文件同时打开同一个文件,一个线程执行删除操作,只要另一个线程不退出,就可以继续该文件进行操作,一旦退出才找不到该文件的索引节点而报错。

    这里写图片描述

    D,一般删除都是文件索引,如果两个文件同时打开同一个文件,一个线程执行删除操作,只要另一个线程不退出,就可以继续对该文件进行操作,一旦退出才找不到该文件的索引节点而报错。

    这里写图片描述

    展开全文
  • 进程与进程描述符(task_struct)

    千次阅读 2017-04-18 18:33:21
    一、 进程进程(Process) 计算机中的程序关于某数据...程序是指令、数据及其组织形式的描述进程是程序的实体。————————————————————————————————————————————————

    一、 进程

    进程(Process)
    计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

    ———————————————————————————————————————————————————————————————

    • 释义:一段程序的执行过程
    • 特征:动态、独立、异步、并发
    • 结构特征:程序、数据和进程控制块
    • 相关概念:线程,管程

    定义

    狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。

    广义定义:
    进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。
    它是是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。

    进程的概念主要有两点:

    第一,进程是一个实体

    '每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。'

    第二,进程是一个“执行中的程序”

    '程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。'

    特征

    动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。

    并发性:任何进程都可以同其他进程一起并发执行

    独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;

    异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进

    结构特征:进程由程序、数据和进程控制块三部分组成。
    多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变。

    内容

    一个计算机系统进程包括(或者说“拥有”)下列数据:
    那个程序的可运行机器码的一个在存储器的映像。 分配到的存储器(通常包括虚拟内存的一个区域)。存储器的内容包括可运行代码、特定于进程的数据(输入、输出)、调用堆栈、堆栈(用于保存运行时运数中途产生的数据)。 分配给该进程的资源的操作系统描述符,诸如文件描述符(Unix术语)或文件句柄(Windows)、数据源和数据终端。 安全特性,诸如进程拥有者和进程的权限集(可以容许的操作)。 处理器状态(内文),诸如寄存器内容、物理存储器寻址等。当进程正在运行时,状态通常储存在寄存器,其他情况在存储器。

    切换

    进行进程切换就是从正在运行的进程中收回处理器,然后再使待运行进程来占用处理器。
    这里所说的从某个进程收回处理器,实质上就是把进程存放在处理器的寄存器中的中间数据找个地方存起来,从而把处理器的寄存器腾出来让其他进程使用。那么被中止运行进程的中间数据存在何处好呢?当然这个地方应该是进程的私有堆栈。
    让进程来占用处理器,实质上是把某个进程存放在私有堆栈中寄存器的数据(前一次本进程被中止时的中间数据)再恢复到处理器的寄存器中去,并把待运行进程的断点送入处理器的程序指针PC,于是待运行进程就开始被处理器运行了,也就是这个进程已经占有处理器的使用权了。
    这就像多个同学要分时使用同一张课桌一样,所谓要收回正在使用课桌同学的课桌使用权,实质上就是让他把属于他的东西拿走;而赋予某个同学课桌使用权,只不过就是让他把他的东西放到课桌上罢了。
    在切换时,一个进程存储在处理器各寄存器中的中间数据叫做进程的上下文,所以进程的 切换实质上就是被中止运行进程与待运行进程上下文的切换。在进程未占用处理器时,进程 的上下文是存储在进程的私有堆栈中的。

    状态

    进程执行时的间断性,决定了进程可能具有多种状态。事实上,运行中的进程可能具有以下三种基本状态。

    1)就绪状态(Ready):
    进程已获得除处理器外的所需资源,等待分配处理器资源;只要分配了处理器进程就可执行。就绪进程可以按多个优先级来划分队列。例如,当一个进程由于时间片用完而进入就绪状态时,排入低优先级队列;当进程由I/O操作完成而进入就绪状态时,排入高优先级队列。

    2)运行状态(Running):
    进程占用处理器资源;处于此状态的进程的数目小于等于处理器的数目。在没有其他进程可以执行时(如所有进程都在阻塞状态),通常会自动执行系统的空闲进程。

    3)阻塞状态(Blocked):
    由于进程等待某种条件(如I/O操作或进程同步),在条件满足之前无法继续执行。该事件发生前即使把处理器资源分配给该进程,也无法运行。
    区别

    程序

    程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。而进程是程序在处理机上的一次执行过程,它是一个动态的概念。
    程序可以作为一种软件资料长期存在,而进程是有一定生命期的。程序是永久的,进程是暂时的。
    进程更能真实地描述并发,而程序不能;
    进程是由进程控制块、程序段、数据段三部分组成;
    进程具有创建其他进程的功能,而程序没有。
    同一程序同时运行于若干个数据集合上,它将属于若干个不同的进程,也就是说同一程序可以对应多个进程。
    在传统的操作系统中,程序并不能独立运行,作为资源分配和独立运行的基本单元都是进程。

    线程

    进程和线程关系

    通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度。
    当下推出的通用操作系统都引入了线程,以便进一步提高系统的并发性,并把它视为现代操作系统的一个重要指标。

    控制

    进程控制是进程管理中最基本的功能。它用于创建一个新进程,终止一个已完成的进程,或者去终止一个因出现某事件而使其无法运行下去的进程,还可负责进程运行中的状态转换。

    创建进程

    1.引起创建进程的事件
    在多道程序环境中,只有(作为)进程(时)才能在系统中运行。因此,为使程序能运行,就必须为它创建进程。导致一个进程去创建另一个进程的典型事件,可以有以下四类:

    1) 用户登录
    在分时系统中,用户在终端键入登录命令后,如果是合法用户,系统将为该终端建立一个进程,并把它插入到就绪队列中。

    2)作业调度
    在批处理系统中,当作业调度程序按照一定的算法调度到某作业时,便将该作业装入到内存,为它分配必要的资源,并立即为它创建进程,再插入到就绪队列中。

    3) 提供服务
    当运行中的用户程序提出某种请求后,系统将专门创建一个进程来提供用户所需要的服务,例如,用户程序要求进行文件打印,操作系统将为它创建一个打印进程,这样,不仅可以使打印进程与该用户进程并发执行,而且还便于计算出为完成打印任务所花费的时间。

    4) 应用请求
    在上述三种情况中,都是由系统内核为它创建一个新进程,而这一类事件则是基于应用进程的需求,由它创建一个新的进程,以便使新进程以并发的运行方式完成特定任务。
    2.进程的创建过程
    这里写图片描述
    一旦操作系统发现了要求创建新进程的事件后,便调用进程创建原语create()按下述步骤创建一个新进程。

    1) 申请空白PCB。为新进程申请获得唯一的数字标识符,并从PCB集合中索取一个空白PCB。

    2) 为新进程分配资源。为新进程的程序和数据以及用户栈分配必要的内存空间。显然,此时操作系统必须知道新进程所需要的内存大小。

    3) 初始化进程控制块。PCB的初始化包括:
    ①初始化标识信息,将系统分配的标识符和父进程标识符,填入新的PCB中。
    ②初始化处理机状态信息,使程序计数器指向程序的入口地址,使栈指针指向栈顶。
    ③初始化处理机控制信息,将进程的状态设置为就绪状态或静止就绪状态,对于优先级,通常是将它设置为最低优先级,除非用户以显式的方式提出高优先级要求。

    4) 将新进程插入就绪队列,如果进程就绪队列能够接纳新进程,便将新进程插入到就绪队列中。

    进程终止
    1.引起进程终止的事件

    1)正常结束
    在任何计算机系统中,都应该有一个表示进程已经运行完成的指示。例如,在批处理系统中,通常在程序的最后安排一条Hold指令或终止的系统调用。当程序运行到Hold指令时,将产生一个中断,去通知OS本进程已经完成。

    2)异常结束
    在进程运行期间,由于出现某些错误和故障而迫使进程终止。这类异常事件很多,常见的有:越界错误,保护错,非法指令,特权指令错,运行超时,等待超时,算术运算错,I/O故障。

    3)外界干预
    外界干预并非指在本进程运行中出现了异常事件,而是指进程应外界的请求而终止运行。这些干预有:操作员或操作系统干预,父进程请求,父进程终止。

    1. 进程的终止过程
      如果系统发生了上述要求终止进程的某事件后,OS便调用进程终止原语,按下述过程去终止指定的进程。

    1)根据被终止进程的标识符,从PCB集合中检索出该进程的PCB,从中读出该进程状态。
    2)若被终止进程正处于执行状态,应立即终止该进程的执行,并置调度标志为真。用于指示该进程被终止后应重新进行调度。
    3)若该进程还有子孙进程,还应将其所有子孙进程予以终止,以防他们成为不可控的进程。
    4)将被终止的进程所拥有的全部资源,或者归还给其父进程,或者归还给系统。
    5)将被终止进程(它的PCB)从所在队列(或链表)中移出,等待其它程序来搜集信息。

    阻塞唤醒

    1.引起进程阻塞和唤醒的事件

    1)请求系统服务
    当正在执行的进程请求操作系统提供服务时,由于某种原因,操作系统并不立即满足该进程的要求时,该进程只能转变为阻塞状态来等待,一旦要求得到满足后,进程被唤醒。

    2)启动某种操作
    当进程启动某种操作后,如果该进程必须在该操作完成之后才能继续执行,则必须先使该进程阻塞,以等待该操作完成,该操作完成后,将该进程唤醒。

    3)新数据尚未到达
    对于相互合作的进程,如果其中一个进程需要先获得另一(合作)进程提供的数据才能运行以对数据进行处理,则是要其所需数据尚未到达,该进程只有(等待)阻塞,等到数据到达后,该进程被唤醒。

    4)无新工作可做
    系统往往设置一些具有某特定功能的系统进程,每当这种进程完成任务后,便把自己阻塞起来以等待新任务到来,新任务到达后,该进程被唤醒。

    2.进程阻塞过程

    正在执行的进程,当发现上述某事件后,由于无法继续执行,于是进程便通过调用阻塞原语block()把自己阻塞。可见,进程的阻塞是进程自身的一种主动行为。进入block过程后,由于此时该进程还处于执行状态,所以应先立即停止执行,把进程控制块中的现行状态由执行改为阻塞,并将PCB插入阻塞队列。如果系统中设置了因不同事件而阻塞的多个阻塞队列,则应将本进程插入到具有相同事件的阻塞(等待)队列。最后,转调度程序进行重新调度,将处理机分配给另一就绪进程,并进行切换,亦即,保留被阻塞进程的处理机状态(在PCB中),再按新进程的PCB中的处理机状态设置CPU环境。

    1. 进程唤醒过程
      当被阻塞的进程所期待的事件出现时,如I/O完成或者其所期待的数据已经到达,则由有关进程(比如,用完并释放了该I/O设备的进程)调用唤醒原语wakeup(),将等待该事件的进程唤醒。唤醒原语执行的过程是:首先把被阻塞的进程从等待该事件的阻塞队列中移出,将其PCB中的现行状态由阻塞改为就绪,然后再将该PCB插入到就绪队列中。

    调度算法

    进程的调度算法包括:
    实时系统中:FIFO(First Input First Output,先进先出算法),SJF(Shortest Job First,最短作业优先算法),SRTF(Shortest Remaining Time First,最短剩余时间优先算法)。

    交互式系统中:RR(Round Robin,时间片轮转算法),HPF(Highest Priority First,最高优先级算法),多级队列,最短进程优先,保证调度,彩票调度,公平分享调度。

    阶段
    进程是由进程控制块、程序段、数据段三部分组成。一个进程可以包含若干线程(Thread),线程可以帮助应用程序同时做几件事(比如一个线程向磁盘写入文件,另一个则接收用户的按键操作并及时做出反应,互相不干扰),在程序被运行后,系统首先要做的就是为该程序进程建立一个默认线程,然后程序可以根据需要自行添加或删除相关的线程。是可并发执行的程序。在一个数据集合上的运行过程,是系统进行资源分配和调度的一个独立单位,也是称活动、路径或任务,它有两方面性质:活动性、并发性。进程可以划分为运行、阻塞、就绪三种状态,并随一定条件而相互转化:就绪–运行,运行–阻塞,阻塞–就绪。

    进程为应用程序的运行实例,是应用程序的一次动态执行。看似高深,我们可以简单地理解为:它是操作系统当前运行的执行程序。在系统当前运行的执行程序里包括:系统管理计算机个体和完成各种操作所必需的程序;用户开启、执行的额外程序,当然也包括用户不知道,而自动运行的非法程序(它们就有可能是病毒程序)。


    二、进程描述符

    1.概念

    在linux中,每一个进程都有一个进程描述符,这个”进程描述符”是一个结构体名字叫做task_struct,在task_struct里面保存了许多关于进程控制的信息。
    task_struct是Linux内核的一种数据结构,它会被装载到RAM里并包含进程的信息。每个进程都把它的信息放在task_struct这个数据结构里面,而

    2.task_struct内容

    标示符:描述本进程的唯一标示符,用来区别其他进程。

    状态:任务状态,退出代码,退出信号等。

    优先级:相对于其他进程的优先级。

    程序计数器:程序中即将被执行的下一条指令的地址。

    内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。

    上下文数据:进程执行时处理器的寄存器中的数据。

    I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和正在被进程使用的文件列表。

    记账信息:可能包括处理器时间总和,使用的时钟总数,时间限制,记账号等。

    3.task_struct的分类

    调度数据成员

    (1) volatile long states; //进程状态
    (2) unsigned long flags; //进程标记符
    (3) long priority; //用来保存动态优先级
    (4) unsigned long rt_priority; //用来保存实时优先级,取值范围为0~99
    (5) long counter;
    (6) unsigned long policy;

    信号处理

    (1) unsigned long signal;
    (2) unsigned long blocked;
    (3) struct signal_struct *sig;

    进程队列指针

    (1) struct task_struct *next_task,*prev_task;
    (2) struct task_struct *next_run,*prev_run;
    (3) struct task_struct *p_opptr,*p_pptr;和struct task_struct *p_cptr,*p_ysptr,*p_osptr;

    进程标识

    (1) unsigned short uid,gid;
    (2) int groups[NGROUPS];
    (3) unsigned short euid,egid;
    (4) unsigned short fsuid,fsgid;
    (5) unsigned short suid,sgid;
    (6) int pid,pgrp,session;
    (7) int leader;

    时间数据成员

    (1) unsigned long timeout;
    (2) unsigned long it_real_value,it_real_iner;
    (3) struct timer_list real_timer;
    (4) unsigned long it_virt_value,it_virt_incr;
    (5) unsigned long it_prof_value,it_prof_incr;
    (6) long utime,stime,cutime,cstime,start_time;

    信号量数据成员

    (1) struct sem_undo *semundo;
    (2) struct sem_queue *semsleeping;

    进程上下文环境

    (1) struct desc_struct *ldt;
    (2) struct thread_struct tss;
    (3) unsigned long saved_kernel_stack;
    (4) unsigned long kernel_stack_page;

    文件系统数据成员

    (1) struct fs_struct *fs;
    (2) struct files_struct *files;
    (3) int link_count;

    内存数据成员

    (1) struct mm_struct *mm;

    页面管理

    (1) int swappable:1;
    (2) unsigned long swap_address;
    (3) unsigned long min_flt,maj_flt;
    (4) unsigned long nswap;
    (5) unsigned long cmin_flt,cmaj_flt,cnswap;
    (6) unsigned long old_maj_flt,dec_flt;
    (7) unsigned long swap_cnt;

    支持对称多处理器方式(SMP)时的数据成员

    (1) int processor;
    (2) int last_processor;
    (3) int lock_depth;

    其它数据成员

    (1) unsigned short used_math;
    (2) char comm[16];
    (3) struct rlimit rlim[RLIM_NLIMITS];
    (4) int errno;
    (5) long debugreg[8];
    (6) struct exec_domain *exec_domain;
    (7) unsigned long personality;
    (8) struct linux_binfmt *binfmt;
    (9) int exit_code,exit_signal;
    (10) int dumpable:1;
    (11) int did_exec:1;
    (12) int tty_old_pgrp;
    (13) struct tty_struct *tty;
    (14) struct wait_queue *wait_chldexit;

    进程队列的全局变量

    (1) current;
    (2) struct task_struct init_task;
    (3) struct task_struct *task[NR_TASKS];
    (4) unsigned long volatile jiffies;
    (5) int need_resched;
    (6) unsigned long intr_count;

    task_struct的定义

    truct task_struct {
    volatile long state;  //说明了该进程是否可以执行,还是可中断等信息
    unsigned long flags;  //Flage 是进程号,在调用fork()时给出
    int sigpending;    //进程上是否有待处理的信号
    mm_segment_t addr_limit; //进程地址空间,区分内核进程与普通进程在内存存放的位置不同
                            //0-0xBFFFFFFF for user-thead
                            //0-0xFFFFFFFF for kernel-thread
    //调度标志,表示该进程是否需要重新调度,若非0,则当从内核态返回到用户态,会发生调度
    volatile long need_resched;
    int lock_depth;  //锁深度
    long nice;       //进程的基本时间片
    //进程的调度策略,有三种,实时进程:SCHED_FIFO,SCHED_RR, 分时进程:SCHED_OTHER
    unsigned long policy;
    struct mm_struct *mm; //进程内存管理信息
    int processor;
    //若进程不在任何CPU上运行, cpus_runnable 的值是0,否则是1 这个值在运行队列被锁时更新
    unsigned long cpus_runnable, cpus_allowed;
    struct list_head run_list; //指向运行队列的指针
    unsigned long sleep_time;  //进程的睡眠时间
    //用于将系统中所有的进程连成一个双向循环链表, 其根是init_task
    struct task_struct *next_task, *prev_task;
    struct mm_struct *active_mm;
    struct list_head local_pages;       //指向本地页面      
    unsigned int allocation_order, nr_local_pages;
    struct linux_binfmt *binfmt;  //进程所运行的可执行文件的格式
    int exit_code, exit_signal;
    int pdeath_signal;     //父进程终止时向子进程发送的信号
    unsigned long personality;
    //Linux可以运行由其他UNIX操作系统生成的符合iBCS2标准的程序
    int did_exec:1; 
    pid_t pid;    //进程标识符,用来代表一个进程
    pid_t pgrp;   //进程组标识,表示进程所属的进程组
    pid_t tty_old_pgrp;  //进程控制终端所在的组标识
    pid_t session;  //进程的会话标识
    pid_t tgid;
    int leader;     //表示进程是否为会话主管
    struct task_struct *p_opptr,*p_pptr,*p_cptr,*p_ysptr,*p_osptr;
    struct list_head thread_group;   //线程链表
    struct task_struct *pidhash_next; //用于将进程链入HASH表
    struct task_struct **pidhash_pprev;
    wait_queue_head_t wait_chldexit;  //供wait4()使用
    struct completion *vfork_done;  //供vfork() 使用
    unsigned long rt_priority; //实时优先级,用它计算实时进程调度时的weight值
    
    //it_real_value,it_real_incr用于REAL定时器,单位为jiffies, 系统根据it_real_value
    //设置定时器的第一个终止时间. 在定时器到期时,向进程发送SIGALRM信号,同时根据
    //it_real_incr重置终止时间,it_prof_value,it_prof_incr用于Profile定时器,单位为jiffies。
    //当进程运行时,不管在何种状态下,每个tick都使it_prof_value值减一,当减到0时,向进程发送
    //信号SIGPROF,并根据it_prof_incr重置时间.
    //it_virt_value,it_virt_value用于Virtual定时器,单位为jiffies。当进程运行时,不管在何种
    //状态下,每个tick都使it_virt_value值减一当减到0时,向进程发送信号SIGVTALRM,根据
    //it_virt_incr重置初值。
    unsigned long it_real_value, it_prof_value, it_virt_value;
    unsigned long it_real_incr, it_prof_incr, it_virt_value;
    struct timer_list real_timer;   //指向实时定时器的指针
    struct tms times;      //记录进程消耗的时间
    unsigned long start_time;  //进程创建的时间
    //记录进程在每个CPU上所消耗的用户态时间和核心态时间
    long per_cpu_utime[NR_CPUS], per_cpu_stime[NR_CPUS]; 
    //内存缺页和交换信息:
    //min_flt, maj_flt累计进程的次缺页数(Copy on Write页和匿名页)和主缺页数(从映射文件或交换
    //设备读入的页面数); nswap记录进程累计换出的页面数,即写到交换设备上的页面数。
    //cmin_flt, cmaj_flt, cnswap记录本进程为祖先的所有子孙进程的累计次缺页数,主缺页数和换出页面数。
    //在父进程回收终止的子进程时,父进程会将子进程的这些信息累计到自己结构的这些域中
    unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap;
    int swappable:1; //表示进程的虚拟地址空间是否允许换出
    //进程认证信息
    //uid,gid为运行该进程的用户的用户标识符和组标识符,通常是进程创建者的uid,gid
    //euid,egid为有效uid,gid
    //fsuid,fsgid为文件系统uid,gid,这两个ID号通常与有效uid,gid相等,在检查对于文件
    //系统的访问权限时使用他们。
    //suid,sgid为备份uid,gid
    uid_t uid,euid,suid,fsuid;
    gid_t gid,egid,sgid,fsgid;
    int ngroups; //记录进程在多少个用户组中
    gid_t groups[NGROUPS]; //记录进程所在的组
    //进程的权能,分别是有效位集合,继承位集合,允许位集合
    kernel_cap_t cap_effective, cap_inheritable, cap_permitted;
    int keep_capabilities:1;
    struct user_struct *user;
    struct rlimit rlim[RLIM_NLIMITS];  //与进程相关的资源限制信息
    unsigned short used_math;   //是否使用FPU
    char comm[16];   //进程正在运行的可执行文件名
     //文件系统信息
    int link_count, total_link_count;
    //NULL if no tty 进程所在的控制终端,如果不需要控制终端,则该指针为空
    struct tty_struct *tty;
    unsigned int locks;
    //进程间通信信息
    struct sem_undo *semundo;  //进程在信号灯上的所有undo操作
    struct sem_queue *semsleeping; //当进程因为信号灯操作而挂起时,他在该队列中记录等待的操作
    //进程的CPU状态,切换时,要保存到停止进程的task_struct中
    struct thread_struct thread;
      //文件系统信息
    struct fs_struct *fs;
      //打开文件信息
    struct files_struct *files;
      //信号处理函数
    spinlock_t sigmask_lock;
    struct signal_struct *sig; //信号处理函数
    sigset_t blocked;  //进程当前要阻塞的信号,每个信号对应一位
    struct sigpending pending;  //进程上是否有待处理的信号
    unsigned long sas_ss_sp;
    size_t sas_ss_size;
    int (*notifier)(void *priv);
    void *notifier_data;
    sigset_t *notifier_mask;
    u32 parent_exec_id;
    u32 self_exec_id;
    
    spinlock_t alloc_lock;
    void *journal_info;
    };

    博客参照于:http://blog.csdn.net/lf_2016/article/details/54347820
    进程百度百科:http://baike.baidu.com/item/%E8%BF%9B%E7%A8%8B#2_1

    展开全文
  • 进程控制块PCB(进程描述符)

    千次阅读 2019-03-24 19:35:48
    每个进程在内核中都有一个进程控制块(PCB)来维护进程相关的信息,Linux内核的进程控制块是task_struct结构体。grep -r “task_struct” / 可以查找根目录下,包含task_struct的文件文件。或者 find /usr -name ...

    1PCB

    每个进程在内核中都有一个进程控制块(PCB)来维护进程相关的信息,Linux内核的进程控制块是task_struct结构体。grep -r “task_struct” /  可以查找根目录下,包含task_struct的文件文件。或者 find /usr -name sched.h。从而,在/usr/include/linux/sched.h文件中可以查看struct task_struct 结构体定义。其内部成员有很多,我们重点掌握以下部分即可:

    (1)进程id。系统中每个进程有唯一的id,在C语言中用pid_t类型表示,其实就是一个非负整数

    (2)进程的状态,有就绪、运行、挂起、停止等状态。

    (3)进程切换时需要保存和恢复的一些CPU寄存器的值。

    (4)描述虚拟地址空间的信息(如虚拟地址与物理地址之间的映射关系)。

    (5)描述控制终端的信息(桌面终端、文字终端和设备终端,pts/0或者tty0等)。

    (6)当前工作目录(当前进程的工作目录)。

    (7)umask掩码(对文件的一种保护机制,文件权限)。

    (8)文件描述符表,包含很多指向file结构体的指针。

    (9)和信号相关的信息。

    (10)用户id和组id。

    (11)会话(Session)和进程组(功能相似的一些进程组成一个进程组)。

    (12)进程可以使用的资源上限(Resource Limit)。(ulimit –a命令可以查看)

    2ulimit命令详解

    ulimit -a命令用来显示各种进程能够使用的资源上限(显示并控制shell启动的程序或进程所能够使用的资源上限,ulimitshell内建命令),Linux对于每个用户,系统限制其最大进程数,为提高性能,可以根据设备资源情况,设置个Linux用户的最大进程数,一些参数需要设置为无限制,如:数据段长度:ulimit -d unlimited     最大内存大小:ulimit -m unlimited      堆栈大小:ulimit -s unlimited

    在用这个命令的时候主要是为了产生core文件,就是程序运行发行段错误时的文件ulimit -c unlimited    从而产生core文件。

    [root@localhost ~]# ulimit -a

    core file size          (blocks, -c) 0

    data seg size           (kbytes, -d) unlimited

    scheduling priority             (-e) 0

    file size               (blocks, -f) unlimited

    pending signals                 (-i) 14473

    max locked memory       (kbytes, -l) 64

    max memory size         (kbytes, -m) unlimited

    open files                      (-n) 1024

    pipe size            (512 bytes, -p) 8

    POSIX message queues     (bytes, -q) 819200

    real-time priority              (-r) 0

    stack size              (kbytes, -s) 8192

    cpu time               (seconds, -t) unlimited

    max user processes              (-u) 14473

    virtual memory          (kbytes, -v) unlimited

    file locks                      (-x) unlimited

    如果需要修改open files (-n) 1024的值,命令就是limit -n 2048(随各自需要设置),其余类似。

    参数解释:

       -a  显示目前资源限制的设定。 
           -c <core文件上限>  设定core文件的最大值,单位为区块。 
           -d <数据节区大小>  程序数据节区的最大值,单位为KB。 
           -f <文件大小>  shell所能建立的最大文件,单位为区块。 
           -H  设定资源的硬性限制,也就是管理员所设下的限制。 
           -m <内存大小>  指定可使用内存的上限,单位为KB。 
           -n <文件数目>  指定同一时间最多可开启的文件数。 
           -p <缓冲区大小>  指定管道缓冲区的大小,单位512字节。 
           -s <堆叠大小>  指定堆叠的上限,单位为KB。 
           -S  设定资源的弹性限制。 
           -t <CPU时间>  指定CPU使用时间的上限,单位为秒。 
           -u <程序数目>  用户最多可开启的程序数目。 
           -v <虚拟内存大小>  指定可使用的虚拟内存上限,单位为KB。

    3)进程状态

    进程基本的状态有5种。分别为初始态(创建),就绪态,运行态,挂起态与终止态。其中初始态为进程准备阶段,常与就绪态结合来看。

     

    展开全文
  • 因此,对进程及其调度进行一般管理就显得极为重要。  在用户空间,进程是由进程标识符(PID)表示的。从用户的角度来看,一个 PID 是一个数字值,可惟一标识一个进程。一个 PID 在进程的整个生命期间不会更
        Linux 是一种动态系统,能够适应不断变化的计算需求。Linux 计算需求的表现是以进程的通用抽象为中心的。进程可以是短期的(从命令行执行的一个命令),也可以是长期的(一种网络服务)。因此,对进程及其调度进行一般管理就显得极为重要。
    
        在用户空间,进程是由进程标识符(PID)表示的。从用户的角度来看,一个 PID 是一个数字值,可惟一标识一个进程。一个 PID 在进程的整个生命期间不会更改,但 PID 可以在进程销毁后被重新使用,所以对它们进行缓存并不见得总是理想的。在用户空间,创建进程可以采用几种方式。可以执行一个程序(这会导致新进程的创建),也可以在程序内,调用一个fork或exec系统调用。fork调用会导致创建一个子进程,而exec调用则会用新程序代替当前进程上下文。这里将对这几种方法进行讨论以便您能很好地理解它们的工作原理。
        这里将按照下面的顺序展开对进程的介绍,首先展示进程的内核表示以及它们是如何在内核内被管理的,然后来看看进程创建和调度的各种方式(在一个或多个处理器上),最后介绍进程的销毁。内核的版本为2.6.32.45。
        1. 进程描述符

        在 Linux 内核内,进程是由相当大的一个称为 task_struct 的结构表示的。此结构包含所有表示此进程所必需的数据,此外,还包含了大量的其他数据用来统计(accounting)和维护与其他进程的关系(如父和子)。task_struct 位于 ./linux/include/linux/sched.h(注意./linux/指向内核源代码树)。下面是task_struct结构:

    struct task_struct {
    	volatile long state;	/* -1 不可运行, 0 可运行, >0 已停止 */
    	void *stack;			/* 堆栈 */
    	atomic_t usage;
    	unsigned int flags;	/* 一组标志 */
    	unsigned int ptrace;
    	/* ...  */
    
    	int prio, static_prio, normal_prio;  /* 优先级  */
    	/* ...  */
    
    	struct list_head tasks;  /* 执行的线程(可以有很多)  */
    	struct plist_node pushable_tasks;
    
    	struct mm_struct *mm, *active_mm;   /* 内存页(进程地址空间)  */
    
    	/* 进行状态 */
    	int exit_state;
    	int exit_code, exit_signal;
    	int pdeath_signal;  /*  当父进程死亡时要发送的信号  */
    	/* ... */	
    
    	pid_t pid;  /* 进程号 */
    	pid_t tgid;
    
    	/* ... */
    	struct task_struct *real_parent; /* 实际父进程real parent process */
    	struct task_struct *parent; /* SIGCHLD的接受者,由wait4()报告 */
    	struct list_head children;	/* 子进程列表 */
    	struct list_head sibling;	/* 兄弟进程列表 */
    	struct task_struct *group_leader;	/* 线程组的leader */
    	/* ... */
    
    	char comm[TASK_COMM_LEN]; /* 可执行程序的名称(不包含路径) */
    	/* 文件系统信息 */
    	int link_count, total_link_count;
    	/* ... */
    
    	/* 特定CPU架构的状态 */
    	struct thread_struct thread;
    	/* 进程当前所在的目录描述 */
    	struct fs_struct *fs;
    	/* 打开的文件描述信息 */
    	struct files_struct *files;
    	/* ... */
    };
        在task_struct结构中,可以看到几个预料之中的项,比如执行的状态、堆栈、一组标志、父进程、执行的线程(可以有很多)以及开放文件。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 thread结构则用来标识进程的存储状态,此元素依赖于Linux在其上运行的特定架构。例如对于x86架构,在 ./linux/arch/x86/include/asm/processor.h的thread_struct结构中可以找到该进程自执行上下文切换后的存储(硬件注册表、程序计数器等)。代码如下:

    struct thread_struct {
    	/* Cached TLS descriptors: */
    	struct desc_struct	tls_array[GDT_ENTRY_TLS_ENTRIES];
    	unsigned long		sp0;
    	unsigned long		sp;
    #ifdef CONFIG_X86_32
    	unsigned long		sysenter_cs;
    #else
    	unsigned long		usersp;	/* Copy from PDA */
    	unsigned short		es;
    	unsigned short		ds;
    	unsigned short		fsindex;
    	unsigned short		gsindex;
    #endif
    #ifdef CONFIG_X86_32
    	unsigned long		ip;
    #endif
    	/* ... */
    #ifdef CONFIG_X86_32
    	/* Virtual 86 mode info */
    	struct vm86_struct __user *vm86_info;
    	unsigned long		screen_bitmap;
    	unsigned long		v86flags;
    	unsigned long		v86mask;
    	unsigned long		saved_sp0;
    	unsigned int		saved_fs;
    	unsigned int		saved_gs;
    #endif
    	/* IO permissions: */
    	unsigned long		*io_bitmap_ptr;
    	unsigned long		iopl;
    	/* Max allowed port in the bitmap, in bytes: */
    	unsigned		io_bitmap_max;
    /* MSR_IA32_DEBUGCTLMSR value to switch in if TIF_DEBUGCTLMSR is set.  */
    	unsigned long	debugctlmsr;
    	/* Debug Store context; see asm/ds.h */
    	struct ds_context	*ds_ctx;
    };
        2. 进程管理
        在很多情况下,进程都是动态创建并由一个动态分配的 task_struct 表示。一个例外是 init 进程本身,它总是存在并由一个静态分配的task_struct表示,参看./linux/arch/x86/kernel/init_task.c,代码如下:
    static struct signal_struct init_signals = INIT_SIGNALS(init_signals);
    static struct sighand_struct init_sighand = INIT_SIGHAND(init_sighand);
    
    /*
     * 初始化线程结构
     */
    union thread_union init_thread_union __init_task_data =
    	{ INIT_THREAD_INFO(init_task) };
    
    /*
     * 初始化init进程的结构。所有其他进程的结构将由fork.c中的slabs来分配
     */
    struct task_struct init_task = INIT_TASK(init_task);
    EXPORT_SYMBOL(init_task);
    
    /*
     * per-CPU TSS segments. 
     */
    DEFINE_PER_CPU_SHARED_ALIGNED(struct tss_struct, init_tss) = INIT_TSS;
        注意进程虽然都是动态分配的,但还是需要考虑最大进程数。在内核内最大进程数是由一个称为max_threads的符号表示的,它可以在 ./linux/kernel/fork.c 内找到。可以通过 /proc/sys/kernel/threads-max 的 proc 文件系统从用户空间更改此值。
        Linux 内所有进程的分配有两种方式。第一种方式是通过一个哈希表,由 PID 值进行哈希计算得到;第二种方式是通过双链循环表。循环表非常适合于对任务列表进行迭代。由于列表是循环的,没有头或尾;但是由于 init_task 总是存在,所以可以将其用作继续向前迭代的一个锚点。让我们来看一个遍历当前任务集的例子。任务列表无法从用户空间访问,但该问题很容易解决,方法是以模块形式向内核内插入代码。下面给出一个很简单的程序,它会迭代任务列表并会提供有关每个任务的少量信息(name、pid 和 parent 名)。注意,在这里,此模块使用 printk 来发出结果。要查看具体的结果,可以通过 cat 实用工具(或实时的 tail -f /var/log/messages)查看 /var/log/messages 文件。next_task 函数是 sched.h 内的一个宏,它简化了任务列表的迭代(返回下一个任务的 task_struct 引用)。如下:
    #define next_task(p) \
    	list_entry_rcu((p)->tasks.next, struct task_struct, tasks)
        查询任务列表信息的简单内核模块:
    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/sched.h>
    
    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);
    	
    	printk(KERN_INFO "Current task is %s [%d]\n", current->comm,current->pid);
    	return 0;
    }
    
    void cleanup_module(void)
    {
    	return;
    }
        编译此模块的Makefile文件如下:
    obj-m += procsview.o
    
    KDIR := /lib/modules/$(shell uname -r)/build
    PWD := $(shell pwd)
    
    default:
    	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
        在编译后,可以用 insmod procsview.ko 插入模块对象,也可以用 rmmod procsview 删除它。插入后,/var/log/messages 可显示输出,如下所示。从中可以看到,这里有一个空闲任务(称为 swapper)和 init 任务(pid 1)。
    Dec 28 23:18:16 ubuntu kernel: [12128.910863] === swapper [0] parent swapper
    Dec 28 23:18:16 ubuntu kernel: [12128.910934] === init [1] parent swapper
    Dec 28 23:18:16 ubuntu kernel: [12128.910945] === kthreadd [2] parent swapper
    Dec 28 23:18:16 ubuntu kernel: [12128.910953] === migration/0 [3] parent kthreadd
    ......
    Dec 28 23:24:12 ubuntu kernel: [12485.295015] Current task is insmod [6051]
        Linux 维护一个称为current的宏,标识当前正在运行的进程(类型是 task_struct)。模块尾部的那行prink用于输出当前进程的运行命令及进程号。注意到当前的任务是 insmod,这是因为 init_module 函数是在 insmod 命令执行的上下文运行的。current 符号实际指的是一个函数(get_current),可在一个与 arch 有关的头部中找到它。比如 ./linux/arch/x86/include/asm/current.h,如下:
    #include <linux/compiler.h>
    #include <asm/percpu.h>
    
    #ifndef __ASSEMBLY__
    struct task_struct;
    
    DECLARE_PER_CPU(struct task_struct *, current_task);
    
    static __always_inline struct task_struct *get_current(void)
    {
    	return percpu_read_stable(current_task);
    }
    
    #define current get_current()
    
    #endif /* __ASSEMBLY__ */
    
    #endif /* _ASM_X86_CURRENT_H */
        3. 进程创建
        用户空间内可以通过执行一个程序、或者在程序内调用fork(或exec)系统调用来创建进程,fork调用会导致创建一个子进程,而exec调用则会用新程序代替当前进程上下文。一个新进程的诞生还可以分别通过vfork()和clone()。fork、vfork和clone三个用户态函数均由libc库提供,它们分别会调用Linux内核提供的同名系统调用fork,vfork和clone。下面以fork系统调用为例来介绍。
        传统的创建一个新进程的方式是子进程拷贝父进程所有资源,这无疑使得进程的创建效率低,因为子进程需要拷贝父进程的整个地址空间。更糟糕的是,如果子进程创建后又立马去执行exec族函数,那么刚刚才从父进程那里拷贝的地址空间又要被清除以便装入新的进程映像。为了解决这个问题,内核中提供了上述三种不同的系统调用。
        (1)内核采用写时复制技术对传统的fork函数进行了下面的优化。即子进程创建后,父子以只读的方式共享父进程的资源(并不包括父进程的页表项)。当子进程需要修改进程地址空间的某一页时,才为子进程复制该页。采用这样的技术可以避免对父进程中某些数据不必要的复制。
        (2)使用vfork函数创建的子进程会完全共享父进程的地址空间,甚至是父进程的页表项。父子进程任意一方对任何数据的修改使得另一方都可以感知到。为了使得双方不受这种影响,vfork函数创建了子进程后,父进程便被阻塞直至子进程调用了exec()或exit()。由于现在fork函数引入了写时复制技术,在不考虑复制父进程页表项的情况下,vfork函数几乎不会被使用。
        (3)clone函数创建子进程时灵活度比较大,因为它可以通过传递不同的clone标志参数来选择性的复制父进程的资源。
        大部分系统调用对应的例程都被命名为 sys_* 并提供某些初始功能以实现调用(例如错误检查或用户空间的行为),实际的工作常常会委派给另外一个名为 do_* 的函数。在./linux/include/asm-generic/unistd.h中记录了所有的系统调用号及名称。注意fork实现与体系结构相关,对32位的x86系统会使用./linux/arch/x86/include/asm/unistd_32.h中的定义,fork系统调用编号为2。fork系统调用在unistd.h中的宏关联如下:
    #define __NR_fork 1079
    #ifdef CONFIG_MMU
    __SYSCALL(__NR_fork, sys_fork)
    #else
    __SYSCALL(__NR_fork, sys_ni_syscall)
    #endif
        在unistd_32.h中的调用号关联为:
    #define __NR_fork          2
        在很多情况下,用户空间任务和内核任务的底层机制是一致的。系统调用fork、vfork和clone在内核中对应的服务例程分别为sys_fork(),sys_vfork()和sys_clone()。它们最终都会依赖于一个名为 do_fork 的函数来创建新进程。例如在创建内核线程时,内核会调用一个名为 kernel_thread 的函数(对32位系统参见 ./linux/arch/x86/kernel/process_32.c,注意process.c是包含32/64bit都适用的代码,process_32.c是特定于32位架构,process_64.c是特定于64位架构),此函数执行某些初始化后会调用 do_fork。创建用户空间进程的情况与此类似。在用户空间,一个程序会调用fork,通过int $0x80之类的软中断会导致对名为sys_fork的内核函数的系统调用(参见 ./linux/arch/x86/kernel/process_32.c),如下:
    int sys_fork(struct pt_regs *regs)
    {
    	return do_fork(SIGCHLD, regs->sp, regs, 0, NULL, NULL);
    }
        最终都是直接调用do_fork。进程创建的函数层次结构如下图:



    图1 进程创建的函数层次结构

        从图中可以看到 do_fork 是进程创建的基础。可以在 ./linux/kernel/fork.c 内找到 do_fork 函数(以及合作函数 copy_process)。
        当用户态的进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数。在X86体系中,可以通过两种不同的方式进入系统调用:执行int $0×80汇编命令和执行sysenter汇编命令。后者是Intel在Pentium II中引入的指令,内核从2.6版本开始支持这条命令。这里将集中讨论以int $0×80方式进入系统调用的过程。
        通过int $0×80方式调用系统调用实际上是用户进程产生一个中断向量号为0×80的软中断。当用户态fork()调用发生时,用户态进程会保存调用号以及参数,然后发出int $0×80指令,陷入0x80中断。CPU将从用户态切换到内核态并开始执行system_call()。这个函数是通过汇编命令来实现的,它是0×80号软中断对应的中断处理程序。对于所有系统调用来说,它们都必须先进入system_call(),也就是所谓的系统调用处理程序。再通过系统调用号跳转到具体的系统调用服务例程处。32位x86系统的系统调用处理程序在./linux/arch/x86/kernel/entry_32.S中,代码如下:

    .macro SAVE_ALL
    	cld
    	PUSH_GS
    	pushl %fs
    	CFI_ADJUST_CFA_OFFSET 4
    	/*CFI_REL_OFFSET fs, 0;*/
    	pushl %es
    	CFI_ADJUST_CFA_OFFSET 4
    	/*CFI_REL_OFFSET es, 0;*/
    	pushl %ds
    	CFI_ADJUST_CFA_OFFSET 4
    	/*CFI_REL_OFFSET ds, 0;*/
    	pushl %eax
    	CFI_ADJUST_CFA_OFFSET 4
    	CFI_REL_OFFSET eax, 0
    	pushl %ebp
    	CFI_ADJUST_CFA_OFFSET 4
    	CFI_REL_OFFSET ebp, 0
    	pushl %edi
    	CFI_ADJUST_CFA_OFFSET 4
    	CFI_REL_OFFSET edi, 0
    	pushl %esi
    	CFI_ADJUST_CFA_OFFSET 4
    	CFI_REL_OFFSET esi, 0
    	pushl %edx
    	CFI_ADJUST_CFA_OFFSET 4
    	CFI_REL_OFFSET edx, 0
    	pushl %ecx
    	CFI_ADJUST_CFA_OFFSET 4
    	CFI_REL_OFFSET ecx, 0
    	pushl %ebx
    	CFI_ADJUST_CFA_OFFSET 4
    	CFI_REL_OFFSET ebx, 0
    	movl $(__USER_DS), %edx
    	movl %edx, %ds
    	movl %edx, %es
    	movl $(__KERNEL_PERCPU), %edx
    	movl %edx, %fs
    	SET_KERNEL_GS %edx
    .endm
    /* ... */
    ENTRY(system_call)
    	RING0_INT_FRAME			# 无论如何不能进入用户空间
    	pushl %eax			# 将保存的系统调用编号压入栈中
    	CFI_ADJUST_CFA_OFFSET 4
    	SAVE_ALL
    	GET_THREAD_INFO(%ebp)
    					# 检测进程是否被跟踪
    	testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)
    	jnz syscall_trace_entry
    	cmpl $(nr_syscalls), %eax
    	jae syscall_badsys
    syscall_call:
    	call *sys_call_table(,%eax,4)	# 跳入对应服务例程
    	movl %eax,PT_EAX(%esp)		# 保存进程的返回值
    syscall_exit:
    	LOCKDEP_SYS_EXIT
    	DISABLE_INTERRUPTS(CLBR_ANY)	# 不要忘了在中断返回前关闭中断
    	TRACE_IRQS_OFF
    	movl TI_flags(%ebp), %ecx
    	testl $_TIF_ALLWORK_MASK, %ecx	# current->work
    	jne syscall_exit_work
    
    restore_all:
    	TRACE_IRQS_IRET
    restore_all_notrace:
    	movl PT_EFLAGS(%esp), %eax	# mix EFLAGS, SS and CS
    	# Warning: PT_OLDSS(%esp) contains the wrong/random values if we
    	# are returning to the kernel.
    	# See comments in process.c:copy_thread() for details.
    	movb PT_OLDSS(%esp), %ah
    	movb PT_CS(%esp), %al
    	andl $(X86_EFLAGS_VM | (SEGMENT_TI_MASK << 8) | SEGMENT_RPL_MASK), %eax
    	cmpl $((SEGMENT_LDT << 8) | USER_RPL), %eax
    	CFI_REMEMBER_STATE
    	je ldt_ss			# returning to user-space with LDT SS
    restore_nocheck:
    	RESTORE_REGS 4			# skip orig_eax/error_code
    	CFI_ADJUST_CFA_OFFSET -4
    irq_return:
    	INTERRUPT_RETURN
    .section .fixup,"ax"
        分析:
        (1)在system_call函数执行之前,CPU控制单元已经将eflags、cs、eip、ss和esp寄存器的值自动保存到该进程对应的内核栈中。随之,在 system_call内部首先将存储在eax寄存器中的系统调用号压入栈中。接着执行SAVE_ALL宏。该宏在栈中保存接下来的系统调用可能要用到的所有CPU寄存器。
        (2)通过GET_THREAD_INFO宏获得当前进程的thread_inof结构的地址;再检测当前进程是否被其他进程所跟踪(例如调试一个程序时,被调试的程序就处于被跟踪状态),也就是 thread_info结构中flag字段的_TIF_ALLWORK_MASK被置1。如果发生被跟踪的情况则转向syscall_trace_entry标记的处理命令处。
        (3)对用户态进程传递过来的系统调用号的合法性进行检查。如果不合法则跳入到syscall_badsys标记的命令处。
        (4)如果系统调用好合法,则根据系统调用号查找./linux/arch/x86/kernel/syscall_table_32.S中的系统调用表sys_call_table,找到相应的函数入口点,跳入sys_fork这个服务例程当中。由于 sys_call_table表的表项占4字节,因此获得服务例程指针的具体方法是将由eax保存的系统调用号乘以4再与sys_call_table表的基址相加。syscall_table_32.S中的代码如下:

    ENTRY(sys_call_table)
    	.long sys_restart_syscall	/* 0 - old "setup()" system call, used for restarting */
    	.long sys_exit
    	.long ptregs_fork
    	.long sys_read
    	.long sys_write
    	.long sys_open		/* 5 */
    	.long sys_close
    	/* ... */
        sys_call_table是系统调用多路分解表,使用 eax 中提供的索引来确定要调用该表中的哪个系统调用。
        (5)当系统调用服务例程结束时,从eax寄存器中获得当前进程的的返回值,并把这个返回值存放在曾保存用户态eax寄存器值的那个栈单元的位置上。这样,用户态进程就可以在eax寄存器中找到系统调用的返回码。
        经过的调用链为fork()--->int $0×80软中断--->ENTRY(system_call)--->ENTRY(sys_call_table)--->sys_fork()--->do_fork()。实际上fork、vfork和clone三个系统调最终都是调用do_fork()。只不过在调用时所传递的参数有所不同,而参数的不同正好导致了子进程与父进程之间对资源的共享程度不同。因此,分析do_fork()成为我们的首要任务。在进入do_fork函数进行分析之前,很有必要了解一下它的参数。
        clone_flags:该标志位的4个字节分为两部分。最低的一个字节为子进程结束时发送给父进程的信号代码,通常为SIGCHLD;剩余的三个字节则是各种clone标志的组合(本文所涉及的标志含义详见下表),也就是若干个标志之间的或运算。通过 clone标志可以有选择的对父进程的资源进行复制。本文所涉及到的clone标志详见下表。


    表1 clone标志含义

        stack_start:子进程用户态堆栈的地址。
        regs:指向pt_regs结构体的指针。当系统发生系统调用,即用户进程从用户态切换到内核态时,该结构体保存通用寄存器中的值,并被存放于内核态的堆栈中。
        stack_size:未被使用,通常被赋值为0。
        parent_tidptr:父进程在用户态下pid的地址,该参数在CLONE_PARENT_SETTID标志被设定时有意义。
        child_tidptr:子进程在用户态下pid的地址,该参数在CLONE_CHILD_SETTID标志被设定时有意义。
        do_fork函数在./linux/kernel/fork.c中,主要工作就是复制原来的进程成为另一个新的进程,它完成了整个进程创建中的大部分工作。代码如下:

    long do_fork(unsigned long clone_flags,
    	      unsigned long stack_start,
    	      struct pt_regs *regs,
    	      unsigned long stack_size,
    	      int __user *parent_tidptr,
    	      int __user *child_tidptr)
    {
    	struct task_struct *p;
    	int trace = 0;
    	long nr;
    
    	/*
    	 * 做一些预先的参数和权限检查
    	 */
    	if (clone_flags & CLONE_NEWUSER) {
    		if (clone_flags & CLONE_THREAD)
    			return -EINVAL;
    		/* 希望当用户名称被支持时,这里的检查可去掉
    		 */
    		if (!capable(CAP_SYS_ADMIN) || !capable(CAP_SETUID) ||
    				!capable(CAP_SETGID))
    			return -EPERM;
    	}
    
    	/*
    	 * 希望在2.6.26之后这些标志能实现循环
    	 */
    	if (unlikely(clone_flags & CLONE_STOPPED)) {
    		static int __read_mostly count = 100;
    
    		if (count > 0 && printk_ratelimit()) {
    			char comm[TASK_COMM_LEN];
    
    			count--;
    			printk(KERN_INFO "fork(): process `%s' used deprecated "
    					"clone flags 0x%lx\n",
    				get_task_comm(comm, current),
    				clone_flags & CLONE_STOPPED);
    		}
    	}
    
    	/*
    	 * 当从kernel_thread调用本do_fork时,不使用跟踪
    	 */
    	if (likely(user_mode(regs)))	/* 如果从用户态进入本调用,则使用跟踪 */
    		trace = tracehook_prepare_clone(clone_flags);
    
    	p = copy_process(clone_flags, stack_start, regs, stack_size,
    			 child_tidptr, NULL, trace);
    	/*
    	 * 在唤醒新线程之前做下面的工作,因为新线程唤醒后本线程指针会变成无效(如果退出很快的话)
    	 */
    	if (!IS_ERR(p)) {
    		struct completion vfork;
    
    		trace_sched_process_fork(current, p);
    
    		nr = task_pid_vnr(p);
    
    		if (clone_flags & CLONE_PARENT_SETTID)
    			put_user(nr, parent_tidptr);
    
    		if (clone_flags & CLONE_VFORK) {
    			p->vfork_done = &vfork;
    			init_completion(&vfork);
    		}
    
    		audit_finish_fork(p);
    		tracehook_report_clone(regs, clone_flags, nr, p);
    
    		/*
    		 * 我们在创建时设置PF_STARTING,以防止跟踪进程想使用这个标志来区分一个完全活着的进程
    		 * 和一个还没有获得trackhook_report_clone()的进程。现在我们清除它并且设置子进程运行
    		 */
    		p->flags &= ~PF_STARTING;
    
    		if (unlikely(clone_flags & CLONE_STOPPED)) {
    			/*
    			 * 我们将立刻启动一个即时的SIGSTOP
    			 */
    			sigaddset(&p->pending.signal, SIGSTOP);
    			set_tsk_thread_flag(p, TIF_SIGPENDING);
    			__set_task_state(p, TASK_STOPPED);
    		} else {
    			wake_up_new_task(p, clone_flags);
    		}
    
    		tracehook_report_clone_complete(trace, regs,
    						clone_flags, nr, p);
    
    		if (clone_flags & CLONE_VFORK) {
    			freezer_do_not_count();
    			wait_for_completion(&vfork);
    			freezer_count();
    			tracehook_report_vfork_done(p, nr);
    		}
    	} else {
    		nr = PTR_ERR(p);
    	}
    	return nr;
    }
        (1)在一开始,该函数定义了一个task_struct类型的指针p,用来接收即将为新进程(子进程)所分配的进程描述符。trace表示跟踪状态,nr表示新进程的pid。接着做一些预先的参数和权限检查。
        (2)接下来检查clone_flags是否设置了CLONE_STOPPED标志。如果设置了,则做相应处理,打印消息说明进程已过时。通常这样的情况很少发生,因此在判断时使用了unlikely修饰符。使用该修饰符的判断语句执行结果与普通判断语句相同,只不过在执行效率上有所不同。正如该单词的含义所表示的那样,当前进程很少为停止状态。因此,编译器尽量不会把if内的语句与当前语句之前的代码编译在一起,以增加cache的命中率。与此相反,likely修饰符则表示所修饰的代码很可能发生。tracehook_prepare_clone用于设置子进程是否被跟踪。所谓跟踪,最常见的例子就是处于调试状态下的进程被debugger进程所跟踪。进程的ptrace字段非0说明debugger程序正在跟踪它。如果调用是从用户态进来的(而不从kernel_thread进来的),且当前进程(父进程)被另外一个进程所跟踪,那么子进程也要设置为被跟踪,并且将跟踪标志CLONE_PTRACE加入标志变量clone_flags中。如果父进程不被跟踪,则子进程也不会被跟踪,设置好后返回trace。
        (3)接下来的这条语句要做的是整个创建过程中最核心的工作:通过copy_process()创建子进程的描述符,分配pid,并创建子进程执行时所需的其他数据结构,最终则会返回这个创建好的进程描述符p。该函数中的参数意义与do_fork函数相同。注意原来内核中为子进程分配pid的工作是在do_fork中完成,现在新的内核已经移到copy_process中了。
        (4)如果copy_process函数执行成功,那么将继续执行if(!IS_ERR(p))部分。首先定义了一个完成量vfork,用task_pid_vnr(p)从p中获取新进程的pid。如果clone_flags包含CLONE_VFORK标志,那么将进程描述符中的vfork_done字段指向这个完成量,之后再对vfork完成量进行初始化。完成量的作用是,直到任务A发出信号通知任务B发生了某个特定事件时,任务B才会开始执行,否则任务B一直等待。我们知道,如果使用vfork系统调用来创建子进程,那么必然是子进程先执行。究其原因就是此处vfork完成量所起到的作用。当子进程调用exec函数或退出时就向父进程发出信号,此时父进程才会被唤醒,否则一直等待。此处的代码只是对完成量进行初始化,具体的阻塞语句则在后面的代码中有所体现。
        (5)如果子进程被跟踪或者设置了CLONE_STOPPED标志,那么通过sigaddset函数为子进程增加挂起信号,并将子进程的状态设置为TASK_STOPPED。signal对应一个unsigned long类型的变量,该变量的每个位分别对应一种信号。具体的操作是将SIGSTOP信号所对应的那一位置1。如果子进程并未设置CLONE_STOPPED标志,那么通过wake_up_new_task将进程放到运行队列上,从而让调度器进行调度运行。wake_up_new_task()在./linux/kernel/sched.c中,用于唤醒第一次新创建的进程,它将为新进程做一些初始的必须的调度器统计操作,然后把进程放到运行队列中。一旦当然正在运行的进程时间片用完(通过时钟tick中断来控制),就会调用schedule(),从而进行进程调度。代码如下:
    void wake_up_new_task(struct task_struct *p, unsigned long clone_flags)
    {
    	unsigned long flags;
    	struct rq *rq;
    	int cpu = get_cpu();
    
    #ifdef CONFIG_SMP
    	rq = task_rq_lock(p, &flags);
    	p->state = TASK_WAKING;
    
    	/*
    	 * Fork balancing, do it here and not earlier because:
    	 *  - cpus_allowed can change in the fork path
    	 *  - any previously selected cpu might disappear through hotplug
    	 *
    	 * We set TASK_WAKING so that select_task_rq() can drop rq->lock
    	 * without people poking at ->cpus_allowed.
    	 */
    	cpu = select_task_rq(rq, p, SD_BALANCE_FORK, 0);
    	set_task_cpu(p, cpu);
    
    	p->state = TASK_RUNNING;
    	task_rq_unlock(rq, &flags);
    #endif
    
    	rq = task_rq_lock(p, &flags);
    	update_rq_clock(rq);
    	activate_task(rq, p, 0);
    	trace_sched_wakeup_new(rq, p, 1);
    	check_preempt_curr(rq, p, WF_FORK);
    #ifdef CONFIG_SMP
    	if (p->sched_class->task_woken)
    		p->sched_class->task_woken(rq, p);
    #endif
    	task_rq_unlock(rq, &flags);
    	put_cpu();
    }
        这里先用get_cpu()获取CPU,如果是对称多处理系统(SMP),先设置我为TASK_WAKING状态,由于有多个CPU(每个CPU上都有一个运行队列),需要进行负载均衡,选择一个最佳CPU并设置我使用这个CPU,然后设置我为TASK_RUNNING状态。这段操作是互斥的,因此需要加锁。注意TASK_RUNNING并不表示进程一定正在运行,无论进程是否正在占用CPU,只要具备运行条件,都处于该状态。 Linux把处于该状态的所有PCB组织成一个可运行队列run_queue ,调度程序从这个队列中选择进程运行。事实上,Linux是将就绪态和运行态合并为了一种状态。
        然后用./linux/kernel/sched.c:activate_task()把当前进程插入到对应CPU的runqueue上,最终完成入队的函数是active_task()--->enqueue_task(),其中核心代码行为:
    p->sched_class->enqueue_task(rq, p, wakeup, head);
        sched_class在./linux/include/linux/sched.h中,是调度器一系列操作的面向对象抽象,这个类包括进程入队、出队、进程运行、进程切换等接口,用于完成对进程的调度运行。
        (6)tracehook_report_clone_complete函数用于在进程复制快要完成时报告跟踪情况。如果父进程被跟踪,则将子进程的pid赋值给父进程的进程描述符的pstrace_message字段,并向父进程的父进程发送SIGCHLD信号。
        (7)如果CLONE_VFORK标志被设置,则通过wait操作将父进程阻塞,直至子进程调用exec函数或者退出。
        (8)如果copy_process()在执行的时候发生错误,则先释放已分配的pid,再根据PTR_ERR()的返回值得到错误代码,保存于nr中。

    展开全文
  • 系统进程描述

    千次阅读 2004-11-24 12:18:00
    系统进程 [system process] alg.exe csrss.exe ddhelp.exe dllhost.exe explorer.exe inetinfo.exe internat.exe kernel32.dll lsass.exe mdm.exe mmtask.tsk mprexe.exe msgsrv32.exe mstask.exe regsvc.exe rpcss....
  • 日期 内核版本 架构 作者 GitHub CSDN 2016-05-12 Linux-4.5 X86 & arm gatieme LinuxDeviceDrivers Linux-进程管理与调度 ...注意,程序并不是进程,实际上两个或多个进程不仅有可能执行同一程序
  • 详解Linux中的进程描述符task_struct

    千次阅读 2016-06-05 17:15:45
    进程是处于执行期的程序以及它所管理的资源(如打开的文件...Linux内核通过一个被称为进程描述符的task_struct结构体来管理进程,这个结构体包含了一个进程所需的所有信息。它定义在linux-2.6.38.8/include/linux/sche
  • 进程描述符:filedescrption,包含很多执行file结构体的指针 当我们打开一个文件的时候: int fd = open(); 返回的fd对应着一个结构体,file_struct{ ... } 内核中文件结构体中的内容: struct file { ...
  • TCP服务器中,多进程并发模式算是比较常见的... 误区二:TCP服务器fork一个新进程后,两个进程共享已有套接字描述符,那么两个进程可以同时读取到这个套接字中接收到的所有数据,即每个进程都是以复制的形式读取,
  • 进程间传递文件描述

    千次阅读 2016-08-09 20:00:56
    首先,必须声明,“进程间传递文件描述符”这个说法是错误的。 在处理文件时,内核空间和用户空间使用的主要对象是不同的。用户程序来说,一个文件由一个文件描述符标识。该描述符是一个整数,在所有有关文件的...
  • 进程描述与控制 进程描述 程序的顺序执行和并发执行 顺序执行的特点 顺序性:每一操作都在下一操作开始前结束,严格按照顺序; 封闭性:程序在封闭环境下执行,程序运行时占全机资源,资源的状态只有...
  • 操作系统 进程描述与控制

    千次阅读 2017-03-19 13:43:00
    本系列是计算机操作系统的笔记 采用的书是《计算机操作系统》汤子瀛第二章 进程描述与控制2.1前驱图和程序执行2.1.1前驱图1. 所谓前驱图,是指一个有向无循环图,可记为DAG(Directed Acyclic Graph),它用于描述...
  • 下面对进程描述中,错误的是 。A.进程是动态的概念 B. 进程执行需要处理机C.进程是有生命周期的 D. 进程是指令的集合【答案】D【解析】程序是指令的集合。而进程是程序的一次执行,是动态的,有生命周期的。1-...
  • 为了能使程序并发执行,并且可以并发执行的程序加以描述和控制,于是引入了“进程”什么是进程进程是程序的一次执行; 是一个程序及其数据在处理机上顺序执行时所发生的活动; 是具有独立功能的程序在一个数据...
  • 进程是处于执行期的程序以及它所管理的资源(如打开的...Linux内核通过一个被称为进程描述符的task_struct结构体来管理进程,这个结构体包含了一个进程所需的所有信息。它定义在linux-2.6.38.8/include/linux/sched.
  • 已安装 pre-removal 脚本 返回了错误号 1或2 与 子进程 已安装 post-installation 脚本 返回了错误号 1或2 | 学步园   http://www.xuebuyuan.com/2153374.html 子进程 已安装 pre-removal 脚本 返回了...
  • 1、错误描述 2、错误原因 今天,我在摸索如何利用命令查看MySQL日志,查了很多资料,大多数是通过修改my.ini文件配置。我修改了配置后,准备重启MySQL服务器,先执行了net stop mysql,发现服务还是没停;...
  • ISO C的标准I/0库函数(fopen, fclose, fread, fwrite, fscanf, fprintf等)使用文件指针,UNIX的I/O函数(open, close, read, write, ioctl)使用文件描述符。下面重点来说下,文件描述符是如何工作的。 文件...
  • linux进程与它的文件描述

    千次阅读 2011-10-01 20:22:39
    一)概述 .open系统调用返回的文件描述符是非负整型....当创建进程时,通常有3个打开文件描述符(0,1,2),0代表标准输入,1代表标准输出,2代表标准错误,它们统称为标准IO. .当多个描述符指向同一个文件,每个文件描述符仍保
  • 查看进程打开的文件描述

    千次阅读 2019-10-24 08:58:59
    所以如传输控制协议 (TCP) 和用户数据报协议 (UDP) 套接字等,系统在后台都为该应用程序分配了一个文件描述符,无论这个文件的本质如何,该文件描述符为应用程序与基础操作系统之间的交互提供了通用接口。...
  • 系统进程描述 [个人收集]

    千次阅读 2004-08-10 23:31:00
    系统进程 ? ? ?[system process] ? alg.exe ? csrss.exe ?ddhelp.exe ? dllhost.exe ? explorer.exe ?inetinfo.exe ? internat.exe ? kernel32.dll ?lsass.exe ? mdm.exe ? mmtask.tsk ?mprexe.exe ? msgsrv32.exe ?...
  • 当创建进程时,通常有3个打开文件描述符(0,1,2),0代表标准输入,1代表标准输出,2代表标准错误,它们统称为标准IO.  .当多个描述符指向同一个文件,每个文件描述符仍保持他独特的性能.  .由于文件描述符在一个进程中...
  • linux 进程文件 文件描述

    千次阅读 2014-10-07 14:22:58
    打开文件后,进程得到的文件描述符实质上就是文件描述符表的下标,内核根据这个下标值去访问相应的文件对象,从而实现文件的操作。 注意,同一个进程多次打开同一个文件时,内核会创建多个file对象。 当...
  • TNS: 监听程序当前无法识别连接描述符中请求的服务! 解决方案:  到oracle 的安装目录下面如D:\oracle\product\10.2.0\db_1\NETWORK\ADMIN中找到listner.ora文件。 【修改前的文件】: -
  • 在C程序中,文件由文件指针或者文件描述符表示。ISO C的标准I/0库函数(fopen, fclose, fread, fwrite, fscanf, fprintf等)使用文件指针,UNIX的I/O函数(open, close, read, write, ioctl)使用文件描述符。下面...
  • 依稀记得去年年中时,有个同事也问过我如何获取被调用进程的输出结果,当时还研究了一番,只是没有做整理。今天花点时间,将该方法整理成文。(转载请指明出于breaksoftware的csdn博客)  在信息化非常发达的今天,...
  • tornado多进程log日志切分错误的解决方式
  • 在C程序中,文件由文件指针或者文件描述符表示。ISO C的标准I/0库函数(fopen, fclose, fread, fwrite, fscanf, fprintf等)使用文件指针,UNIX的I/O函数(open, close, read, write, ioctl)使用文件描述符。下面...
  • 进程间传递描述符一

    万次阅读 多人点赞 2010-04-14 17:54:00
    向通道发送一个特殊的消息,内核将这个消息做特殊处理,从而将打开的描述符传递到接收进程。 然后接收方调用 recvmsg 从通道接收消息,从而得到打开的描述符。然而实际操作起来并不像看起来那样单纯。...
  • 用于描述进程之间执行的先后顺序。 p1和p2存在着前驱关系(也可以写成p1→p2),表示在p2开始执行之前p1必须完成,此时称p1为p2的直接前驱,而称p2为p1的直接后继;并且把没有前驱的结点称为初始结点,把没有后继的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 323,290
精华内容 129,316
关键字:

对进程描述错误的是