精华内容
下载资源
问答
  • 进程具有以下主要特性: (1)并发性:可以与其它进程一道在宏观上同时向前推进。 (2)动态性:进程是执行中的程序。此外进程的动态性还体现在如下两个方面:首先,进程是动态产生、动态消亡的;其次,在进程的...

    进程是具有一定独立功能的程序关于一个数据集合的一次运行活动。

    进程具有以下主要特性:
    (1)并发性: 可以与其它进程一道在宏观上同时向前推进。
    (2)动态性: 进程是执行中的程序。此外进程的动态性还体现在如下两个方面:首先,进程是动态产生、动态消亡的;其次,在进程的生存期内,其状态处于经常性的动态变化之中。
    (3)独立性: 进程是调度的基本单位,它可以获得处理机并参与并发执行。
    (4)交往性: 进程在运行过程中可能会与其它进程发生直接或间接的相互作用。
    (5)异步性: 每个进程都以其相对独立、不可预知的速度向前推进。
    (6)结构性: 每个进程有一个控制块PCB。

    进程和程序的相同点: 程序是构成进程的组成部分之一,一个进程存在的目的就是执行其所对应的程序,如果没有程序,进程就失去了其存在的意义。

    进程与程序的差别:

    (1)程序是静态的,而进程是动态的;

    (2)程序可以写在纸上或在某一存储介质上长期保存,而进程具有生存期,创建后存在,撤销后消亡;

    (3)一个程序可以对应多个进程,但一个进程只能对应一个程序;例如,一组学生在一个分时系统中做C语言实习,他们都需要使用C语言的编译程序对其源程序进行编译,为此每个学生都需要有一个进程,这些进程都运行C语言的编译程序。 另外,一个程序的多次执行也分别对应不同的进程。

    展开全文
  • 什么是守护进程?守护进程特性

    千次阅读 2007-07-22 21:48:00
    守护进程守护进程是生存期长的一种进程。它们独立于控制终端并且周期性的执行某种任务或等待处理某些发生 的事件。他们常常在系统引导装入时启动,在系统关闭时终止。unix系统有很多守护进程,大多数服务器都是用...
     
    守护进程

    守护进程是生存期长的一种进程。它们独立于控制终端并且周期性的执行某种任务或等待处理某些发生 的事件。他们常常在系统引导装入时启动,在系统关闭时终止。unix系统有很多守护进程,大多数服务器都是用守护进程实现的。比如,网络服务inetd、 Web服务http等。同时,守护进程完成许多系统任务。比如,作业规划进程crond、打印进程lqd等。

    这里主要说明守护进程的进程结构,以及如何编写守护进程程序。因为守护进程没有控制终端,所以我们还要介绍在守护进程运行时错误输出的方法。

    守护进程及其特性

    守护进程最重要的特性是后台运行。在这一点上,DOS下的常驻内存程序TSR与之相似。

    其次,守护进程必须与其运行前的环境隔离开来。这些环境包括未关闭的文件描述符、控制终端、会话和进程组、工作目录以及文件创建掩码等。这些环境通常是守护进程从执行它的父进程(特别是shell)中继承下来的。

    最后,守护进程的启动方式有其特殊之处。它可以在系统启动时从启动脚本/etc/rc.d中启动,可以由inetd守护进程启动,可以有作业规划进程crond启动,还可以由用户终端(通常是shell)执行。

    总之,除开这些特殊性以外,守护进程与普通进程基本上没有什么区别。因此,编写守护进程实际上是把一个普通进程按照上述的守护进程的特性改造成为守护进程。如果大家对进程的认识比较深入,就对守护进程容易理解和编程了。

    首先我们来察看一些常用的系统守护进程,看一下他们和几个概念:进程组、控制终端和对话期有什么联系。p s命令打印系统中各个进程的状态。该命令有多个选择项,有关细节请参考系统手册。为了察看所需的信息,执行:
    ps –axj

    PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
    0 1 0 0 ? -1 S 0 0:04 init
    1 2 1 1 ? -1 SW 0 0:00 [keventd]
    1 3 1 1 ? -1 SW 0 0:00 [kapm-idled]
    0 4 1 1 ? -1 SWN 0 0:00 [ksoftirqd_CPU0]
    0 5 1 1 ? -1 SW 0 0:00 [kswapd]
    0 6 1 1 ? -1 SW 0 0:00 [kreclaimd]
    0 7 1 1 ? -1 SW 0 0:00 [bdflush]
    0 8 1 1 ? -1 SW 0 0:00 [kupdated]
    1 9 1 1 ? -1 SW< 0 0:00 [mdrecoveryd]
    1 17 1 1 ? -1 SW 0 0:02 [kjournald]
    1 92 1 1 ? -1 SW 0 0:00 [khubd]
    1 573 573 573 ? -1 S 0 0:03 syslogd -r -x
    1 578 578 578 ? -1 S 0 0:00 klogd -2
    1 598 598 598 ? -1 S 32 0:00 portmap

    进 程号为1、2的这些进程非常特殊,存在于系统的整个生命期中。它们没有父进程ID ,没有组进程ID ,也没有对话期ID 。syslogd 守护进程可用于任何为操作人员记录系统消息的程序中。可以在一台实际的控制台上打印这些消息,也可将它们写到一个文件中。sendmail 是标准邮递守护进程。update 程序定期将内核缓存中的内容写到硬盘上(通常是每隔30 秒)。为了做到这一点,该程序每隔30 秒调用sync(2 )函数一次。cron 守护进程在指定的日期和时间执行指定的命令。许多系统管理任务是由cron 定期地使相关程序执行而得以实现的。inetd进程监听系统的网络界面,以输入对各种网络服务器的请求。最后一个守护进程,lpd 处理对系统提出的各个打印请求。

    注意,所有守护进程都以超级用户(用户ID为0)的优先权运行。没有一个守护进程具有控制终端,终端名称 设置为问号(?)、终端前台进程组ID设置为-1。缺少控制终端是守护进程调用了setsid的结果。除update以外的所有守护进程都是进程组的首进 程,对话期的首进程,而且是这些进程组和对话期中的唯一进程。最后,应当引起注意的是所有这些守护进程的父进程都是init进程。

    在接触实际编程前,我们来看看编写守护进程要碰到的概念:进程组合会话期。

    进程组

    每个进程除了有一进程ID之外,还属于一个进程组(在讨论信号时就会涉及进程组)进程组是一个或多个进程的集合。每个进程有一个唯一的进程组ID。进程组ID类似于进程ID——它是一个正整数,并可存放在pid_t数据类型中。

    每 个进程组有一个组长进程。组长进程的标识是,其进程组ID等于其进程ID,进程组组长可以创建一个进程组,创建该组中的进程,然后终止,只要在某个进程组 中有一个进程存在,则该进程就存在,这与其组长进程是否终止无关。从进程组创建开始到其中最后一个进程离开为止的时间区间称为进程组的生命期。某个进程组 中的最后一个进程可以终止,也可以参加另一进程组。

    前面已经提到进程调用setgid可以参加一个现存的组或者创建一个新进程组(setsid也可以创建一个新的进程组,后面将用到)

    会话期

    会话期(session)是一个或多个进程组的集合。其中,在一个会话期中有3个进程组,通常是有shell的管道线将几个进程编成一组的。

    下面说明有关会话期和进程组的一些特性:

    一个会话期可以有一个单独的控制终端(controlling terminal),这一般是我们在其上登录的终端设备(终端登录)或伪终端设备(网络登录),但这个控制终端并不是必需的。

    建 立与控制终端连接的会话期首进程,被称之为控制进程(contronlling process)。以及一个会话期中的几个进程组可被分为一个前台进程组(foreground process group)以及一个或几个后台进程组(background process group)

    如果一个会话期有一个控制终端,则它有一个前台进程组,其他进程组为后台进程组。无论何时键入中断键(常常是delete或ctrl-c)或退出键(通常是ctrl-/),就会造成将中断信号或退出信号送至前途进程组的所有进程。

    守护进程的编程规则

    在不同Unix环境下,守护进程的具体编程细节并不一致。但所幸的是,守护进程的编程原则其实都一样,区别仅在于具体的实现细节不同,这个原则就是要满足守护进程的特性。编程规则如下:

    1、在后台运行

    为避免挂起控制终端,要将daemon放入后台执行,其方法是,在进程中调用fork使父进程终止,让daemon在子进程中后台执行。具体就是调用f o r k ,然后使父进程e x i t 。这样做实现了下面几点:
    第一,如果该精灵进程是由一条简单s h e l l 命令起动的,那么使父进程终止使得s h e l l 认为这条命令已经执行完成。
    第二,子进程继承了父进程的进程组I D ,但具有一个新的进程I D ,这就保证了子进程不是一个进程组的首进程。这对于下面就要做的s e t s i d 调用是必要的前提条件。

    2、脱离控制终端,登录会话和进程组

    登录会话可以包含多个进程组,这些进程组共享一个控制终端,这个控制终端通常是创建进程的登录终端、控制终端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们,使之不受它们的影响。

    其方法是在第一点的基础上,调用setsid()使进程成为会话组长:

    需要说明的是,当进程是会话组长时,setsid()调用会失败,但第一点已经保证进程不是会话组长。setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离,由于会话过程对控制终端的独占性,进程同时与控制终端脱离。
    具体是操作就是:
    (a )成为新对话期的首进程
    (b )成为一个新进程组的首进程
    (c )没有控制终端。

    3、禁止进程重新打开控制终端

    现在,进程已经成为无终端的会话组长,但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端:

    4、关闭打开的文件描述符

    进 程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在地文件系统无法卸下以及无法预料的错误。一般来说,必要的是关 闭0、1、2三个文件描述符,即标准输入、标准输出、标准错误。因为我们一般希望守护进程自己有一套信息输出、输入的体系,而不是把所有的东西都发送到终 端屏幕上。调用fclose();

    5、改变当前工作目录

    将当前工作目录更改为根目录。从父进程继承过来的当前工作目录可能在一个装配的文件系统中。因为精灵进程通常在系统再引导之前是一直存在的,所以如果精灵进程的当前工作目录在一个装配文件系统中,那么该文件系统就不能被拆卸。

    另外,某些精灵进程可能会把当前工作目录更改到某个指定位置,在此位置做它们的工作。例如,行式打印机假脱机精灵进程常常将其工作目录更改到它们的s p o o l 目录上。
    可以调用chdir(“目录”);

    6、重设文件创建掩码

    将文件方式创建屏蔽字设置为0 。由继承得来的文件方式创建屏蔽字可能会拒绝设置某些许可权。例如,若精灵进程要创建一个组可读、写的文件,而继承的文件方式创建屏蔽字,屏蔽了这两种许可权,则所要求的组可读、写就不能起作用。

    7、处理SIGCHLD 信号

    处 理SIGCHLD信号并不是必需的。但对于某些进程,特别是服务器进程往往在请求到来时生产子进程出来请求。如果父进程不等待子进程结束,子进程将成为僵 尸进程,(zombie)而仍占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在系统V下可以简单的将 SIGCHLD信号的操作设为SIG-IGN:

    signal(SIGCHLD,SIG_IGN);

    这样,内核在子进程结束时不会产生僵尸进程,这一点与BSD4不同,在BSD4下必须显示等 待子进程结束才能释放僵尸进程。

    守护进程实例

    守护进程实例包括两部分:主程序test.c和初始化程序init.c。主程序每隔一分钟向/tmp
    目录中的日志test.log 报告运行状态。初始化程序中的init_daemon 函数负责生成守护进程

    void make_daemon(void)
    {
    pid_t pid;
    FILE * lockfd;
    sigset_t sighup;
    int i;

    extern pid_t getsid(pid_t);
    pid = fork();//第一个子进程生成
    if (pid < 0) {
    printinfo("fork error!",INFOERROR);
    exit(FAILEXIT);
    }else if (pid > 0) {
    printinfo("fork 1 ok! ", INFOSCREEN);
    exit(OKEXIT);//退出父进程,摆脱shell的控制
    }
    pid = getpid();//获得子进程自身的id
    lockfd = fopen(PIDFILE, "w");//以下是将pid写入文件
    if (lockfd != NULL) {
    fprintf(lockfd, "%d/n", pid);
    fclose(lockfd);
    }//写入pid
    if (getsid(0) != pid) {//创建新的会话期
    if (setsid() < 0) {
    printinfo("backupdaemon setsid error!",INFOERROR);
    perror("setsid");
    }
    }
    if(pid=fork()){//再次生成子进程,这时候是孙子进程
    exit(0);//退出上一代进程
    }else if(pid<0){
    exit(1);
    }
    close(1);//关闭文件
    close(2);
    chdir(rundir);//改变运行的目录
    umask(022);//改变文件权限
    }

    守护进程的错误输出

    守 护进程不属于任何终端,所以当需要输出某些信息时,它无法像一般程序那样将信息直接输出到标准输出和标准错误输出中。我们很大时候也不希望每个守护进程将 它自己的出错消息写到一个单独的文件中。因为对于系统管理人员而言,要记住哪一个守护进程写到哪一个记录文件中,并定期的检查这些文件,他一定会为此感到 头疼的。所以,我们需要有一个集中的守护进程出错记录机制。目前很多系统都引入了syslog记录进程来实现这一目的。

    自伯克利开发了BSD syslog并广泛应用以来,BSD syslog 机制被大多数守护进程所使用。我们下面介绍BSD syslog 的用法。有三种方法产生记录消息:

    1 内核例程可以调用log函数。任何一个用户进程通过打开和读/dev/klog设备就可以读取这些消息。因为我们无意编写内核中的例程,所以不再进一步说明此函数。

    2 大多数用户进程(守护进程)调用syslog函数以产生记录消息。我们将在下面说明其调用序列。这使消息发送至Unix域数据报套接口/dev/log。

    3 在此主机上,或通过TCP/IP网络连接到此主机的某一其他主机上的一个用户进程可将记录消息发向UDP端口514。

    注意:syslog 函数并不产生这些UDP数据报——它们要求产生此记录消息的进程具有显式的网络编程。

    通 常,syslog守护进程读取三种格式的记录消息。此守护进程在启动时读一个配置文件。一般来说,其文件名为/etc/syslog.conf,该文件决 定了不同种类的消息应送向何处。例如,紧急消息可被送向系统管理员(若已登录),并在控制台上显示,而警告消息则可记录到一个文件中。该机制提供了 syslog函数,其调用格式如下

    #include
    void openlog (char*ident,int option ,int facility);
    void syslog(int priority,char*format,……)
    void closelog();

    调 用openlog是可选择的。如果不调用openlog,则在第一次调用syslog时,自动调用openlog。调用closelog也是可选择的,它 只是关闭被用于与syslog守护进程通信的描述符。调用openlog 使我们可以指定一个ident,以后, 此ident 将被加至每则记录消息中。ident 一般是程序的名称(例如 ,cron ,inetd 等)。option 有4种可能:

    LOG_CONS 若日志消息不能通过Unix域数据报发送至syslog,则将该消息写至控制台。

    LOG_NDELAY1 立即打开Unix域数据报套接口至syslog守护进程,而不要等到记录第一消息。通常,在记录第一条消息之前,该套接口不打开。

    LOG_PERROR 除将日志消息发送给syslog 外,还将它至标准出错。此选项仅由4.3BSDReno及以后版本支持。

    LOG_PID 每条消息都包含进程ID。此选项可供对每个请求都fork一个子进程的守护进程使用。

    在openlog 中设置facility参数的目的是让配置文件可以说明,来自不同设施的消息以不同的方式进行处理。如果不调用openlog,或者以facility 为0来调用它,那么在调用syslog 时,可将facility作为priority参数的一个部分进行说明。调用syslog产生一个记录消息。其priority参数是facility和 level的组合,它们可选取的值分别列于下面。level值按优先级从高级到最低按序排列。
    展开全文
  • 进程和子进程到底是如何执行的?

     20170724_父进程和子进程到底是如何执行的?


    这篇博客写的还不错。保留以供继续学习。

    问题:子进程父进程哪个先执行:【转】关于 fork 和父子进程的理解 + 【转】


    关于 fork 和父子进程的理解   (http://blog.163.com/kingore@126/blog/static/8540871920097300284862/)

    代码:
    #include <unistd.h> 
    #include <sys/types.h> 

    main () 

            pid_t pid; 
            pid=fork(); 

            if (pid < 0) 
                    printf("error in fork!"); 
            else if (pid == 0) 
                    printf("i am the child process, my process id is %d\n",getpid()); 
            else 
                    printf("i am the parent process, my process id is %d\n",getpid()); 
    }


    结果是 
    [root@localhost c]# ./a.out 
    i am the child process, my process id is 4286 
    i am the parent process, my process id is 4285 
    -------------------------------------------------------------
    Q: 我就想不到为什么两行都打印出来了,在我想来,不管pid是多少,都应该只有一行才对

    A: 这里的if和else不是以前理解的选择分支。fork后产生的子进程和父进程并行运行的.
    这种理解是不正确的。if 和 else 还是选择分支。 主要的原因是,fork() 函数调用一次,返回两次。两次返回的区别是:子进程的返回值是0,父进程返回值为新子进程的进程ID。

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

    Q: 但是只有一个pid=fork(); 呀,fork()返回的第二次值在什么时候赋给pid呢

    A: pid这个变量是有两个的, 父进程一个, 子进程一个。

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

    要搞清楚fork的执行过程,就必须先讲清楚操作系统中的“进程(process)”概念。一个进程,主要包含三个元素: 

    o. 一个可以执行的程序; 
    o. 和该进程相关联的全部数据(包括变量,内存空间,缓冲区等等); 
    o. 程序的执行上下文(execution context)。 

    不 妨简单理解为,一个进程表示的,就是一个可执行程序的一次执行过程中的一个状态。操作系统对进程的管理,典型的情况,是通过进程表完成的。进程表中的每一 个表项,记录的是当前操作系统中一个进程的情况。对于单 CPU的情况而言,每一特定时刻只有一个进程占用 CPU,但是系统中可能同时存在多个活动的(等待执行或继续执行的)进程。

    一个称为“程序计数器(program counter, pc)”的寄存器,指出当前占用 CPU的进程要执行的下一条指令的位置。 

    当 分给某个进程的 CPU时间已经用完,操作系统将该进程相关的寄存器的值,保存到该进程在进程表中对应的表项里面;把将要接替这个进程占用 CPU的那个进程的上下文,从进程表中读出,并更新相应的寄存器(这个过程称为“上下文交换(process context switch)”,实际的上下文交换需要涉及到更多的数据,那和fork无关,不再多说,主要要记住程序寄存器pc指出程序当前已经执行到哪里,是进程上 下文的重要内容,换出 CPU的进程要保存这个寄存器的值,换入CPU的进程,也要根据进程表中保存的本进程执行上下文信息,更新这个寄存器)。 

    好了,有这些概念打底,可以说fork了。当你的程序执行到下面的语句: 
    pid=fork(); 
    操 作系统创建一个新的进程(子进程),并且在进程表中相应为它建立一个新的表项。新进程和原有进程的可执行程序是同一个程序;上下文和数据,绝大部分就是原 进程(父进程)的拷贝,但它们是两个相互独立的进程!此时程序寄存器pc,在父、子进程的上下文中都声称,这个进程目前执行到fork调用即将返回(此时 子进程不占有CPU,子进程的pc不是真正保存在寄存器中,而是作为进程上下文保存在进程表中的对应表项内)。问题是怎么返回,在父子进程中就分道扬镳。 

    父进程继续执行,操作系统对fork的实现,使这个调用在父进程中返回刚刚创建的子进程的pid(一个正整数),所以下面的if语句中pid<0, pid==0的两个分支都不会执行。所以输出i am the parent process... 

    子 进程在之后的某个时候得到调度,它的上下文被换入,占据 CPU,操作系统对fork的实现,使得子进程中fork调用返回0。所以在这个进程(注意这不是父进程了哦,虽然是同一个程序,但是这是同一个程序的另 外一次执行,在操作系统中这次执行是由另外一个进程表示的,从执行的角度说和父进程相互独立)中pid=0。这个进程继续执行的过程中,if语句中 pid<0不满足,但是pid==0是true。所以输出i am the child process... 

    我想你比较困惑的就是,为什么看上去程序中互斥的两个分支都被执行了。在一个程序的一次执行中,这当然是不可能的;但是你看到的两行输出是来自两个进程,这两个进程来自同一个程序的两次执行。 

    我的天,不知道说明白了没……

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

    到底哪个进程执行在先,这个和操作系统的调度算法等等很多因素相关。我觉得理解上的困难,关键在于为什么会有两个输出,而不是谁先谁后。

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

    fork 之后,操作系统会复制一个与父进程完全相同的子进程,虽说是父子关系,但是在操作系统看来,他们更像兄弟关系,这2个进程共享代码空间,但是数据空间是互 相独立的,子进程数据空间中的内容是父进程的完整拷贝,指令指针也完全相同,但只有一点不同,如果fork成功,子进程中fork的返回值是0,父进程中 fork的返回值是子进程的进程号,如果fork不成功,父进程会返回错误。 
    可以这样想象,2个进程一直同时运行,而且步调一致,在fork之后,他们分别作不同的工作,也就是分岔了。这也是fork为什么叫fork的原因。 
    至于那一个最先运行,可能与操作系统有关,而且这个问题在实际应用中并不重要,如果需要父子进程协同,可以通过原语的办法解决。




    ****************************************************************************************************************************

    linux中fork函数及子进程父进程进程先后(http://blog.csdn.net/wu_zf/article/details/7640970)

    一、fork入门知识

         一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
        一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。

         我们来看一个例子:

    [cpp]  view plain copy
    1. #include <unistd.h>  
    2. #include <stdio.h>   
    3. int main ()   
    4. {   
    5.     pid_t fpid; //fpid表示fork函数返回的值  
    6.     int count=0;  
    7.     fpid=fork();   
    8.     if (fpid < 0)   
    9.         printf("error in fork!");   
    10.     else if (fpid == 0) {  
    11.         printf("i am the child process, my process id is %d/n",getpid());   
    12.         printf("我是爹的儿子/n");//对某些人来说中文看着更直白。  
    13.         count++;  
    14.     }  
    15.     else {  
    16.         printf("i am the parent process, my process id is %d/n",getpid());   
    17.         printf("我是孩子他爹/n");  
    18.         count++;  
    19.     }  
    20.     printf("统计结果是: %d/n",count);  
    21.     return 0;  
    22. }  

         运行结果是:
        i am the child process, my process id is 5574
        我是爹的儿子
        统计结果是: 1
        i am the parent process, my process id is 5573
        我是孩子他爹
        统计结果是: 1

        在语句fpid=fork()之前,只有一个进程在执行这段代码,但在这条语句之后,就变成两个进程在执行了,这两个进程的几乎完全相同,将要执行的下一条语句都是if(fpid<0)……
        为什么两个进程的fpid不同呢,这与fork函数的特性有关。

        fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
        1)在父进程中,fork返回新创建子进程的进程ID;
        2)在子进程中,fork返回0;
        3)如果出现错误,fork返回一个负值;

        在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。

        引用一位网友的话来解释fpid的值为什么在父子进程中不同。“其实就相当于链表,进程形成了链表,父进程的fpid(p 意味point)指向子进程的进程id, 因为子进程没有子进程,所以其fpid为0.
        fork出错可能有两种原因:
        1)当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。
        2)系统内存不足,这时errno的值被设置为ENOMEM。
        创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。
        每个进程都有一个独特(互不相同)的进程标识符(process ID),可以通过getpid()函数获得,还有一个记录父进程pid的变量,可以通过getppid()函数获得变量的值。
       
     fork执行完毕后,出现两个进程,

        有人说两个进程的内容完全一样啊,怎么打印的结果不一样啊,那是因为判断条件的原因,上面列举的只是进程的代码和指令,还有变量啊。
        执行完fork后,进程1的变量为count=0,fpid!=0(父进程)。进程2的变量为count=0,fpid=0(子进程),这两个进程的变量都是独立的,存在不同的地址中,不是共用的,这点要注意。可以说,我们就是通过fpid来识别和操作父子进程的。
        还有人可能疑惑为什么不是从#include处开始复制代码的,这是因为fork是把进程当前的情况拷贝一份,执行fork时,进程已经执行完了int count=0;fork只拷贝下一个要执行的代码到新的进程。

    二、fork进阶知识

        先看一份代码:

    [cpp]  view plain copy
    1. #include <unistd.h>  
    2. #include <stdio.h>  
    3. int main(void)  
    4. {  
    5.    int i=0;  
    6.    printf("i son/pa ppid pid  fpid/n");  
    7.    //ppid指当前进程的父进程pid  
    8.    //pid指当前进程的pid,  
    9.    //fpid指fork返回给当前进程的值  
    10.    for(i=0;i<2;i++){  
    11.        pid_t fpid=fork();  
    12.        if(fpid==0)  
    13.            printf("%d child  %4d %4d %4d/n",i,getppid(),getpid(),fpid);  
    14.        else  
    15.            printf("%d parent %4d %4d %4d/n",i,getppid(),getpid(),fpid);  
    16.    }  
    17.    return 0;  
    18. }  

        运行结果是:
        i son/pa ppid pid  fpid
        0 parent 2043 3224 3225
        0 child  3224 3225    0
        1 parent 2043 3224 3226
        1 parent 3224 3225 3227
        1 child     1 3227    0
        1 child     1 3226    0 

        这份代码比较有意思,我们来认真分析一下:
        第一步:在父进程中,指令执行到for循环中,i=0,接着执行fork,fork执行完后,系统中出现两个进程,分别是p3224和p3225(后面我都用pxxxx表示进程id为xxxx的进程)。可以看到父进程p3224的父进程是p2043,子进程p3225的父进程正好是p3224。我们用一个链表来表示这个关系:
        p2043->p3224->p3225 
        第一次fork后,p3224(父进程)的变量为i=0,fpid=3225(fork函数在父进程中返向子进程id),代码内容为:

    [c-sharp]  view plain copy
    1. for(i=0;i<2;i++){  
    2.     pid_t fpid=fork();//执行完毕,i=0,fpid=3225  
    3.     if(fpid==0)  
    4.        printf("%d child  %4d %4d %4d/n",i,getppid(),getpid(),fpid);  
    5.     else  
    6.        printf("%d parent %4d %4d %4d/n",i,getppid(),getpid(),fpid);  
    7. }  
    8. return 0;  

        p3225(子进程)的变量为i=0,fpid=0(fork函数在子进程中返回0),代码内容为:

    [c-sharp]  view plain copy
    1. for(i=0;i<2;i++){  
    2.     pid_t fpid=fork();//执行完毕,i=0,fpid=0  
    3.     if(fpid==0)  
    4.        printf("%d child  %4d %4d %4d/n",i,getppid(),getpid(),fpid);  
    5.     else  
    6.        printf("%d parent %4d %4d %4d/n",i,getppid(),getpid(),fpid);  
    7. }  
    8. return 0;  

        所以打印出结果:
        0 parent 2043 3224 3225
        0 child  3224 3225    0

        第二步:假设父进程p3224先执行,当进入下一个循环时,i=1,接着执行fork,系统中又新增一个进程p3226,对于此时的父进程,p2043->p3224(当前进程)->p3226(被创建的子进程)。
        对于子进程p3225,执行完第一次循环后,i=1,接着执行fork,系统中新增一个进程p3227,对于此进程,p3224->p3225(当前进程)->p3227(被创建的子进程)。从输出可以看到p3225原来是p3224的子进程,现在变成p3227的父进程。父子是相对的,这个大家应该容易理解。只要当前进程执行了fork,该进程就变成了父进程了,就打印出了parent。
        所以打印出结果是:
        1 parent 2043 3224 3226
        1 parent 3224 3225 3227 

        第三步:第二步创建了两个进程p3226,p3227,这两个进程执行完printf函数后就结束了,因为这两个进程无法进入第三次循环,无法fork,该执行return 0;了,其他进程也是如此。
        以下是p3226,p3227打印出的结果:
        1 child     1 3227    0
        1 child     1 3226    0 

        细心的读者可能注意到p3226,p3227的父进程难道不该是p3224和p3225吗,怎么会是1呢?这里得讲到进程的创建和死亡的过程,在p3224和p3225执行完第二个循环后,main函数就该退出了,也即进程该死亡了,因为它已经做完所有事情了。p3224和p3225死亡后,p3226,p3227就没有父进程了,这在操作系统是不被允许的,所以p3226,p3227的父进程就被置为p1了,p1是永远不会死亡的,至于为什么,这里先不介绍,留到“三、fork高阶知识”讲。


        总结一下,这个程序执行的流程如下:

         这个程序最终产生了3个子进程,执行过6次printf()函数。
        我们再来看一份代码:

    [cpp]  view plain copy
    1. #include <unistd.h>  
    2. #include <stdio.h>  
    3. int main(void)  
    4. {  
    5.    int i=0;  
    6.    for(i=0;i<3;i++){  
    7.        pid_t fpid=fork();  
    8.        if(fpid==0)  
    9.            printf("son/n");  
    10.        else  
    11.            printf("father/n");  
    12.    }  
    13.    return 0;  
    14.   
    15. }  

         它的执行结果是:
        father
        son
        father
        father
        father
        father
        son
        son
        father
        son
        son
        son
        father
        son 

        这里就不做详细解释了,只做一个大概的分析。
        for        i=0         1           2
                  father     father     father
                                            son
                                son       father
                                            son
                   son       father     father
                                            son
                                son       father
                                            son
        其中每一行分别代表一个进程的运行打印结果。
        总结一下规律,对于这种N次循环的情况,执行printf函数的次数为2*(1+2+4+……+2N-1)次,创建的子进程数为1+2+4+……+2N-1个。

        (感谢gao_jiawei网友指出的错误,原本我的结论是“执行printf函数的次数为2*(1+2+4+……+2N)次,创建的子进程数为1+2+4+……+2”,这是错的)
        网上有人说N次循环产生2*(1+2+4+……+2N)个进程,这个说法是不对的,希望大家需要注意。

        数学推理见http://202.117.3.13/wordpress/?p=81(该博文的最后)。
       

        同时,大家如果想测一下一个程序中到底创建了几个子进程,最好的方法就是调用printf函数打印该进程的pid,也即调用printf("%d/n",getpid());或者通过printf("+/n");来判断产生了几个进程。

    有人想通过调用printf("+");来统计创建了几个进程,这是不妥当的。具体原因我来分析。
        老规矩,大家看一下下面的代码:

    [cpp]  view plain copy
    1. #include <unistd.h>  
    2. #include <stdio.h>  
    3. int main() {  
    4.     pid_t fpid;//fpid表示fork函数返回的值  
    5.     //printf("fork!");  
    6.     printf("fork!/n");  
    7.     fpid = fork();  
    8.     if (fpid < 0)  
    9.         printf("error in fork!");  
    10.     else if (fpid == 0)  
    11.         printf("I am the child process, my process id is %d/n", getpid());  
    12.     else  
    13.         printf("I am the parent process, my process id is %d/n", getpid());  
    14.     return 0;  
    15. }  

        执行结果如下:
        fork!
        I am the parent process, my process id is 3361
        I am the child process, my process id is 3362

               如果把语句printf("fork!/n"); 注释掉,执行printf("fork!");
        则新的程序的执行结果是:
        fork!I am the parent process, my process id is 3298
        fork!I am the child process, my process id is 3299 

        程序的唯一的区别就在于一个/n回车符号,为什么结果会相差这么大呢?
       
     这就跟printf的缓冲机制有关了,printf某些内容时,操作系统仅仅是把该内容放到了stdout的缓冲队列里了,并没有实际的写到屏幕上。但是,只要看到有/n 则会立即刷新stdout,因此就马上能够打印了。
        运行了printf("fork!")后,“fork!”仅仅被放到了缓冲里,程序运行到fork时缓冲里面的“fork!”  被子进程复制过去了。因此在子进程度stdout缓冲里面就也有了fork! 。所以,你最终看到的会是fork!  被printf了2次!!!!
        而运行printf("fork! /n")后,“fork!”被立即打印到了屏幕上,之后fork到的子进程里的stdout缓冲里不会有fork! 内容。因此你看到的结果会是fork! 被printf了1次!!!!
        所以说printf("+"); 不能正确 地反应进程的数量。
        大家看了这么多可能有点疲倦吧,不过我还得贴最后一份代码来进一步分析fork函数。

    [cpp]  view plain copy
    1. #include <stdio.h>  
    2. #include <unistd.h>  
    3. int main(int argc, char* argv[])  
    4. {  
    5.    fork();  
    6.    fork() && fork() || fork();  
    7.    fork();  
    8.    return 0;  
    9. }  

        问题是不算main这个进程自身,程序到底创建了多少个进程。
        为了解答这个问题,我们先做一下弊,先用程序验证一下,到此有多少个进程。

    [c-sharp]  view plain copy
    1. #include <stdio.h>  
    2. int main(int argc, char* argv[])  
    3. {  
    4.    fork();  
    5.    fork() && fork() || fork();  
    6.    fork();  
    7.    printf("+/n");  
    8. }  

        答案是总共20个进程,除去main进程,还有19个进程。
        我们再来仔细分析一下,为什么是还有19个进程。
        第一个fork和最后一个fork肯定是会执行的。
        主要在中间3个fork上,可以画一个图进行描述。
        这里就需要注意 && 和 || 运算符。
        A&&B,如果A=0,就没有必要继续执行&&B了;A非0,就需要继续执行&&B。
        A||B,如果A非0,就没有必要继续执行||B了,A=0,就需要继续执行||B。

        fork()对于父进程和子进程的返回值是不同的,按照上面的A&&B和A||B的分支进行画图,可以得出5个分支。

        

         加上前面的fork和最后的fork,总共4*5=20个进程,除去main主进程,就是19个进程了。

    三、fork高阶知识

            这一块我主要就fork函数讲一下操作系统进程的创建、死亡和调度等。因为时间和精力限制,我先写到这里,下次找个时间我争取把剩下的内容补齐。

    参考资料:

     

          http://blog.csdn.net/dog_in_yellow/archive/2008/01/13/2041079.aspx

          http://blog.chinaunix.net/u1/53053/showart_425189.html

          http://blog.csdn.net/saturnbj/archive/2009/06/19/4282639.aspx

          http://www.cppblog.com/zhangxu/archive/2007/12/02/37640.html

          http://www.qqread.com/linux/2010/03/y491043.html

          http://www.yuanma.org/data/2009/1103/article_3998.htm



    展开全文
  • 进程

    万次阅读 2019-03-28 21:04:35
    就绪态 -> 执行态:系统按某种策略选中就绪队列中的一个进程占用处理器,此时就变成了运行态 进程调度 上下文切换 线程/进程的上下文(以下统称为:上下文)主要包含两个部分:寄存器(尤其是 PC)和操作系统需要的...

    进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体。它不只是程序的代码,还包括当前的活动,通过程序计数器的值和处理寄存器的内容来表示。

    进程的概念主要有两点:

    进程是一个实体,每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。
    进程是一个“执行中的程序”,程序是一个没有生命的实体,只有处理器赋予程序生命时,它才能成为一个活动的实体,我们称其为进程。
    进程的基本状态

    阻塞态:等待某个事件的完成;
    就绪态:等待系统分配处理器以便运行;
    执行态:占有处理器正在运行。

    执行态 -> 阻塞态:往往是由于等待外设,等待主存等资源分配或等待人工干预而引起的。
    阻塞态 -> 就绪态:则是等待的条件已满足,只需分配到处理器后就能运行。
    执行态 -> 就绪态:不是由于自身原因,而是由外界原因使运行状态的进程让出处理器,这时候就变成就绪态。例如时间片用完,或有更高优先级的进程来抢占处理器等。
    就绪态 -> 执行态:系统按某种策略选中就绪队列中的一个进程占用处理器,此时就变成了运行态
    进程调度

    上下文切换

    线程/进程的上下文(以下统称为:上下文)主要包含两个部分:寄存器(尤其是 PC)和操作系统需要的特定数据(PCB)。上下文切换(context switch),是一个存储和重建 CPU 的过程,完整的上下文会涉及到这两部分的切换,旧的上下文被保存,新的上下文被加载。

    当系统发生中断或者 OS 进行线程调度时会进行上下文切换。

    调度种类

    高级、中级和低级调度作业从提交开始直到完成,往往要经历下述三级调度:

    高级调度:又称为作业调度,它决定把后备作业调入内存运行;
    中级调度:又称为在虚拟存储器中引入,在内、外存对换区进行进程对换。
    低级调度:又称为进程调度,它决定把就绪队列的某进程获得CPU;
    非抢占式调度与抢占式调度

    非抢占式:分派程序一旦把处理机分配给某进程后便让它一直运行下去,直到进程完成或发生进程调度进程调度某事件而阻塞时,才把处理机分配给另一个进程。

    抢占式:操作系统将正在运行的进程强行暂停,由调度程序将CPU分配给其他就绪进程的调度方式。

    调度策略的设计

    响应时间:从用户输入到产生反应的时间
    周转时间:从任务开始到任务结束的时间
    平均周转时间:周转总时间除以作业个数
    CPU任务可以分为交互式任务和批处理任务,调度最终的目标是合理的使用CPU,使得交互式任务的响应时间尽可能短,用户不至于感到延迟,同时使得批处理任务的周转时间尽可能短,减少用户等待的时间。

    调度算法

    FCFS:调度的顺序就是任务到达就绪队列的顺序。对短作业不公平。

    SJF:最短的作业(CPU区间长度最小)最先调度。

    HRN:最高响应比优先法,是 FCFS 和 SJF 的综合平衡,响应比R定义如下: R =(W+T)/T 。

    优先权调度:每个任务关联一个优先权,调度优先权最高的任务。

    Round-Robin(RR):设置一个时间片,按时间片来轮转调度

    多级队列调度

    按照一定的规则建立多个进程队列
    不同的队列有固定的优先级(高优先级有抢占权)
    不同的队列可以给不同的时间片和采用不同的调度方法
    多级反馈队列:在多级队列的基础上,任务可以在队列之间移动,更细致的区分任务。可以根据“享用”CPU时间多少来移动队列,阻止“饥饿”。

    进程同步

    临界资源与临界区

    在操作系统中,进程是占有资源的最小单位(线程可以访问其所在进程内的所有资源,但线程本身并不占有资源或仅仅占有一点必须资源)。但对于某些资源来说,其在同一时间只能被一个进程所占用。这些一次只能被一个进程所占用的资源就是所谓的临界资源。典型的临界资源比如物理上的打印机,或是存在硬盘或内存中被多个进程所共享的一些变量和数据等(如果这类资源不被看成临界资源加以保护,那么很有可能造成丢数据的问题)。

    对于临界资源的访问,必须是互斥进行。也就是当临界资源被占用时,另一个申请临界资源的进程会被阻塞,直到其所申请的临界资源被释放。而进程内访问临界资源的代码被成为临界区。

    对于临界区的访问过程分为四个部分:

    进入区:查看临界区是否可访问,如果可以访问,则转到步骤二,否则进程会被阻塞
    临界区:在临界区做操作
    退出区:清除临界区被占用的标志
    剩余区:进程与临界区不相关部分的代码
    解决临界区问题可能的方法:

    一般软件方法
    关中断方法
    硬件原子指令方法
    信号量方法
    信号量

    信号量是一个确定的二元组(s,q),其中s是一个具有非负初值的整形变量,q是一个初始状态为空的队列,整形变量s表示系统中某类资源的数目:

    当其值 >= 0 时,表示系统中当前可用资源的数目
    当其值 < 0 时,其绝对值表示系统中因请求该类资源而被阻塞的进程数目
    除信号量的初值外,信号量的值仅能由 P 操作和 V 操作更改,操作系统利用它的状态对进程和资源进行管理

    互斥锁:同一时间只能有一个线程访问加锁的数据。
    自旋锁:互斥锁的一种实现,如果自旋锁已经被别的执行单元保持,调用者就一直 循环等待 是否该自旋锁的保持者已经释放了锁。
    读写锁:一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数相关),但不能同时既有读者又有写者。
    阻塞锁:与自旋锁不同,改变了线程的运行状态。让线程进入阻塞状态进行等待,当获得相应的信号(唤醒,时间) 时,才可以进入线程的准备就绪状态,准备就绪状态的所有线程,通过竞争,进入运行状态。
    在Java中synchronized,ReentrantLock,Object.wait() / notify()都属于阻塞锁。
    可重入锁:也叫做递归锁,指的是同一线程上该锁是可重入的,对于不同线程则相当于普通的互斥锁。
    公平锁:加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得。
    非公平锁:加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待。ReentrantLock中的lock()默认就是非公平锁。
    悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。加锁的时间可能会很长,也就是说悲观锁的并发访问性不好。
    乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。乐观锁不能解决脏读的问题,可以通过添加时间戳和版本来来解决。
    死锁

    死锁是指多个进程因循环等待资源而造成无法执行的现象。死锁会造成进程无法执行,同时会造成系统资源的极大浪费(资源无法释放)。

    死锁产生的四个必要条件

    互斥使用:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。

    不可抢占:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。

    请求和保持:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。

    循环等待:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。

    死锁避免

    银行家算法:判断此次请求是否造成死锁若会造成死锁,则拒绝该请求。

    CAS

    比较并交换(compare and swap, CAS),是原子操作的一种,可用于在多线程编程中实现不被打断的数据交换操作。该操作通过将内存中的值与指定数据进行比较,当数值一样时将内存中的数据替换为新的值。

    在使用上,通常会记录下某块内存中的旧值,通过对旧值进行一系列的操作后得到新值,然后通过CAS操作将新值与旧值进行交换。如果这块内存的值在这期间内没被修改过,则旧值会与内存中的数据相同,这时CAS操作将会成功执行使内存中的数据变为新值。如果内存中的值在这期间内被修改过,则一般来说旧值会与内存中的数据不同,这时CAS操作将会失败,新值将不会被写入内存。

    进程间通信

    本地进程间通信的方式有很多,可以总结为下面四类:

    消息传递(管道、FIFO、消息队列)
    同步(互斥量、条件变量、读写锁、文件和写记录锁、信号量)
    共享内存(匿名的和具名的)
    远程过程调用(Solaris门和Sun RPC)
    子进程

    在 Unix 和类 Unix 系统中,子进程通常为系统调用 fork 的产物。调用 fork 后的父子进程会运行在不同的内存空间中,当 fork 发生时两者的内存空间有着完全相同的内容,对内存的写入和修改、文件的映射都是独立的,两个进程不会相互影响。除此之外,子进程几乎是父进程的完整副本。

    既然父进程和子进程拥有完全相同的内存空间并且两者对内存的写入都不会相互影响,那么是否意味着子进程在 fork 时需要对父进程的内存进行全量的拷贝呢?

    在一些早期的 *nix 系统上,系统调用 fork 确实会立刻对父进程的内存空间进行复制,但是在今天的多数系统中, fork 并不会立刻触发这一过程,而是在内存被修改时,才会进行数据复制(Copy-On-Write)。

    当 fork 函数调用时,父进程和子进程会被 Kernel 分配到不同的虚拟内存空间中,所以在两个进程看来它们访问的是不同的内存:

    在真正访问虚拟内存空间时,Kernel 会将虚拟内存映射到物理内存上,所以父子进程共享了物理上的内存空间;
    当父进程或者子进程对共享的内存进行修改时,共享的内存才会以页为单位进行拷贝,父进程会保留原有的物理空间,而子进程会使用拷贝后的新物理空间;
    线程

    线程是 操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

    线程是独立调度和分派的基本单位。线程可以操作系统内核调度的内核线程,如Win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用户进程,如Windows 7的线程,进行混合调度。

    同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈,自己的寄存器环境,自己的线程本地存储。

    线程的属性

    轻型实体:线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述。TCB包括以下信息:

    线程状态。
    当线程不运行时,被保存的现场资源。
    一组执行堆栈。
    存放每个线程的局部变量主存区。
    访问同一个进程中的主存和其它资源。
    用于指示被执行指令序列的程序计数器、保留局部变量、少数状态参数和返回地址等的一组寄存器和堆栈。

    独立调度和分派的基本单位:在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,故线程的切换非常迅速且开销小(在同一进程中的)。

    可并发执行:在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力。

    共享进程资源:在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。

    线程共享的环境包括:进程代码段、进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯)、进程打开的文件描述符、信号的处理器、进程的当前目录和进程用户ID与进程组ID。
    线程是程序执行的一条路径,在多线程的OS中,线程是调度和分配的基本单位,而进程是拥有资源的基本单位。

    进程 vs 线程

    线程本质就是堆栈,当一段程序在执行,能代表它的是他的过去和现在。过去 在堆栈中,现在 则是 CPU 的所有寄存器,如果我们要挂起一个线程,我们把寄存器也保存到堆栈中,我们就具有它的所有状态,可以随时恢复它。

    进程的本质是地址空间,当我们切换线程的时候,同时切换它的地址空间(通过修改MMU即可),则认为发生了进程切换。

    中断

    中断(英语:Interrupt)是指 处理器接收到来自硬件或软件的信号,提示发生了某个事件,应该被注意,这种情况就称为中断。

    通常,在接收到来自外围硬件(相对于中央处理器和内存)的异步信号,或来自软件的同步信号之后,处理器将会进行相应的 硬件/软件 处理。发出这样的信号称为进行中断请求(interrupt request,IRQ)。硬件中断导致处理器通过一个运行信息切换(context switch)来保存执行状态(以程序计数器和程序状态字等寄存器信息为主);软件中断则通常作为CPU指令集中的一个指令,以可编程的方式直接指示这种运行信息切换,并将处理导向一段中断处理代码。中断在计算机多任务处理,尤其是即时系统中尤为有用。

    硬件中断

    由硬件发出或产生的中断称为硬中断,按硬中断事件的来源和实现手段可将中断划分为外中断和内中断:

    外中断:又称为中断或异步中断,是指 来自处理器以外的中断信号,包括时钟中断、键盘中断、外部设备中断等。外中断又分为可屏蔽中断和不可屏蔽中断,各个中断具有不同的优先级,表示事件的紧急程度,在处理高一级中断时,往往会部分或全部屏蔽低等级中断。
    内中断:又称为异常或同步中断(产生时必须考虑与处理器时钟同步),是指 来自处理器内部的中断信号,通常是由于程序执行过程中,发现与当前指令关联的、不正常的或错误的事件。
    软件中断

    软件中断:是一条CPU指令,用以自陷一个中断。由于 软中断指令通常要运行一个切换CPU至内核态(Kernel Mode/Ring 0)的子例程,它常被用作实现系统调用(System call)。

    处理器通常含有一个内部中断屏蔽位,并允许通过软件来设定。一旦被设定,所有外部中断都将被系统忽略。这个屏蔽位的访问速度显然快于中断控制器上的中断屏蔽寄存器,因此可提供更快速地中断屏蔽控制。

    中断尽管可以提高计算机处理性能,但 过于密集的中断请求/响应反而会影响系统性能。这类情形被称作中断风暴(interrupt storm)。

    内核态&用户态

    内核态是一种 CPU 的特权态,CPU 可以在特权态下执行特殊的指令,访问这个特权态才运行访问的资源。上面提到的三种中断形式:外中断、异常中断、软件中断,均可使 OS 从用户态切换到内核态。

    内核态切换

    内核态切换不一定进行上下文切换。先考虑简单的内核态切换——获取当前系统时间。CPU 从用户态切换到内核态,保存用户态的寄存器数据,执行内核代码以获取数据,将数据存储到调用者可访问的存储器或寄存器,恢复用户态的寄存器数据并返回。这里未进行完整的上下文切换,只是涉及用户态到内核态的模式切换。

    现在考虑一个系统调用,该调用会阻塞调用者,直到发生某些事件或数据可用为止。在这种情况下,内核将被迫保存调用者的完整上下文,将其标记为 阻塞态,以便调度程序在该事件或数据到达之前无法运行它。这时调度程序会加载另一个就绪线程/进程的上下文。这里的内核态切换就会导致上下文切换。

    展开全文
  • BSD于1980年前后向Unix中增加的一个新特性,代表一个或多个进程的集合,每个进程属于一个进程组。在waitpid函数和kill函数的参数中都曾使用到。操作系统设计的进程组的概念,是为了简化对多个进程的管理。 当父...
  • 进程关系

    千次阅读 2016-07-17 20:40:11
    9 进程关系9.1 简介进程关系:每个进程都有一个父进程(初始的内核级进程通常是自己的父进程)。当子进程终止时,父进程得到通知并能取得子进程的退出状态。进程关系中主要介绍登录shell和所有从登录shell启动的进程...
  • Linux系统编程——特殊进程之守护进程

    万次阅读 多人点赞 2015-05-25 19:02:32
    守护进程是个特殊的孤儿进程,这种进程脱离终端,为什么要脱离终端呢?之所以脱离于终端是为了避免进程被任何终端所产生的信息所打断,其在执行过程中的信息也不在任何终端上显示。由于在 Linux 中,每一个系统与...
  • Linux进程间关系之守护进程

    千次阅读 2017-02-27 16:05:56
    可认为守护进程目的就是防止终端产生的一些信号让进程退出特点 所有的守护进程都没有控制终端,其终端名(TTY)设置为问号(?)。 自成会话,自成进程组。不与其他会话或进程组相互关联,干扰。所以一般一个守护...
  • 详解僵尸进程进程等待

    千次阅读 多人点赞 2021-03-31 16:17:38
    进程为子进程,而原进程为父进程,fork之后通常要用if分流,返回值为0,则为子进程,返回值为大于0(暨为创建出子进程的pid),则为父进程,返回值小于0则创建子进程失败,父子进程的pcb中有相同的代码段,因此...
  • 进程间通信(IPC,InterProcess Communication):是指在不同进程之间传播或交换信息。 一、简单的进程间通信: 命令行:父进程通过exec函数创建子进程时可以附加一些数据。 环境变量:父进程通过exec函数创建...
  • 守护进程

    万次阅读 多人点赞 2015-08-14 12:15:04
    什么是守护进程? 守护进程(Daemon Process),也就是通常说的 Daemon 进程(精灵进程),是 Linux 中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生...
  • 应用场景:后台图片备份,息屏计步 应用在深圳知名大型互联公司,日活用户2000万 支持系统2.3到6.0 支持大部分设备,包括三星,华为,oppo,nexus,...Android系统会在内存不足的时候去将进程杀死,俗称Low M...
  • #Linux#进程间通信# 进程与线程

    万次阅读 2019-12-27 13:52:24
    进程的概念 进程是操作系统的概念,每当我们执行一个程序时,对于操作系统来讲就创建了一个进程,在这个过程中,伴随着资源的分配和释放。可以认为进程是一个程序的一次执行过程。 进程(Process)是计算机中的程序...
  • Linux进程控制(精讲)

    万次阅读 多人点赞 2021-09-23 21:55:48
    文章目录进程创建fork函数初始fork函数返回值写时拷贝fork常规用法fork调用失败的原因进程终止进程退出场景进程常见退出方法_exit函数exit函数return退出进程等待进程等待的必要性进程等待的方法wait方法waitpid方法...
  • 线程 vs 进程

    万次阅读 多人点赞 2017-01-22 19:51:54
    进程与线程的区别是很重要的一个知识点,也是面试中经常问到的。网上转载博客痕迹明显,且千篇一律。我简单提取下,记录下来,希望能帮到你。另外在 LeetCode 上也有关于此问题的讨论,可以直接浏览...特点多道程序设
  • Linux编写守护进程获取进程信息

    千次阅读 2019-04-07 15:46:14
    记录系统运行期间所有运行的进程,记录信息包括:进程PID,可执行程序名称,用户名,创建时间,撤销时间,开机能自启动
  • 本文是Android进程管理系列文章的第二篇,会讲解进程管理中的优先级管理。 进程管理的第一篇文章:《进程的创建》请跳转至...在Android系统中,进程的优先级影响着以下三个因素: 当内存紧张时,系统对于进程
  • 【Linux】Linux进程的创建与管理

    万次阅读 多人点赞 2018-07-27 19:21:29
    在Linux系统中,除了系统启动之后的第一个进程由系统来创建,其余的进程都必须由已存在的进程来创建,新创建的进程叫做子进程,而创建子进程进程叫做父进程。那个在系统启动及完成初始化之后,Linux自动创建的进程...
  • 进程查看 ps ax : 显示当前系统进程的列表  ps aux : 显示当前系统进程详细列表以及进程用户 ps ax|less : 如果输出过长,可能添加管道命令 less查看具体进程, 如:ps ax|grep XXX(XXX为进程名称)   ...
  • 详解共享内存以及所有进程间通信的特点
  • Oracle 进程详解

    千次阅读 2016-04-29 13:39:29
    Oracle 进程详解参与数据库活动的进程可以分成两大类,分别是用户进程和 Oracle 进程,而后者又可以分成服 务进程(Server Process )和后台进程(Background Process)。(本文地址:...
  • linux守护进程

    千次阅读 2017-12-17 11:40:37
    一、守护进程定义守护进程(daemon)是生存期长的一种进程。它们常常在系统引导装入时启动,仅在系统关闭时才终止。因为它们没有控制终端,所以说它们是在后台运行的。 在linux终端中,我们可以用ps -axj 命令来查看...
  • 守护进程的最大特点就是脱离了中断,Linux提供了一个系统调用daemon(),要想自定义实现的话,主要包括以下六个步骤: 1.第一步是使用umask函数,把所有的文件屏蔽字置0。文件屏蔽字是可以继承的,当你有相
  • 进程进程描述符(task_struct)

    千次阅读 2017-04-18 18:33:21
    一、 进程进程(Process) 计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向...
  • 进程调度算法设计

    千次阅读 2019-08-06 19:10:29
    进程管理是操作系统中的重要功能,用来创建进程、撤消进程、实现进程状态转换,它提供了在可运行的进程之间复用CPU的方法。在进程管理中,进程调度是核心,因为在采用多道程序设计的系统中,往往有若干个进程同时...
  • 进程和线程的关系

    万次阅读 2018-08-19 10:54:35
    **一、进程 1、进程的概念** 进程是操作系统实现并发执行的重要手段,也是操作系统为程序提供的重要运行...以下是从不同角度对进程的解释: a、进程是程序的一次执行 b、进程是可以与其他计算并发执行的计算...
  • 超级进程管家 v1.4.2

    2019-11-04 03:19:37
    超级进程管家是一款功能强大的、...具备了以下几个特性:1、监视进程的动态数据;2、颜色标记进程,让您一目了然;3、对有危害性的进程进行分析;4、定位查找进程的目标位置;5、自定义进程过滤,让您的系统更安全。
  • 1.守护进程的概念,特点,以及创建守护进程的代码实现。 2.两次fork的原因。 3.利用cron进程实现定时向终端打印数据的任务。守护进程的定义:守护进程是一种生存期长的进程。通常在系统引导装入时启动,仅仅在系统...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 294,288
精华内容 117,715
关键字:

以下属于进程的特性是