第八章(一) 进程控制

来源:互联网 发布:pixhawk导航算法 编辑:程序博客网 时间:2024/05/22 13:32

                第八章    进程控制

进程标志

    每个进程都有一个非负整型表示的唯一的进程 ID, 即PID

    进程ID是可以 复用 的,即一个进程结束后可给另一个新的用,但往往采用的是延迟复用,即新建的ID不同于最近终止的ID。

        ID为 0 的往往是 交换进程

            是内核的一部份,并不执行磁盘上任何程序,为系统进程

        ID为 1 的常是 init 进程    

            在自举过程结束时由内核调用,负责在自举内核后启动一个Unix系统。


函数    getpid、 getppid

    得到进程ID 和 父进程ID


函数  fork ,子进程与父进程

#include <stdio.h>

 

int globvar = 6;

char buf[] = "a write to stdout";

 

int main()

{

         intvar;

         pid_tpid;

        

         var= 88;

         if(write(STDOUT_FILENO,buf,sizeof(buf)-1)!= sizeof(buf)-1)

         {

                   perror();

                   exit(1);

         }

 

         printf("...............");

 

         if((pid=fork())< 0)

         {

                   perror();

                   exit(1);

         }

         elseif(pid == 0)

         {

                   globvar++;

                   var++;

         }

         else

                   sleep(2);

        

         printf("pid= %d ; globvar = %d ; var = %d\n",(long)getpid(),globvar,var);

         return0;

}

 

执行:

 $./a.out

 awrite to stdout

 …………………

 pid= 300 ; globvar = 7 ; var = 89                                子

 pid= 299 ; globvar = 6 ; var = 88                                父

 

 $./a.out > temp

 awrite to stdout

 ………………….

 pid= 302; globvar = 7 ; var = 89                                子

  ………………….

pid = 301 globvar = 6 ; var = 88                                  父

 

乍一看,为什么会多了一行printf 的数据呢?

1、  父进程数据不改变是因为除非是环境变量,父子之间的变量才会相互影响

2、  多了一行数据是因为

        一、write函数无缓冲区,会直接输出

        二、printf函数有缓冲区,当其指向终端时,是行缓冲,其他则是全缓冲

       三、因为此时printf为行缓冲,没有碰到换行符,数据存在缓冲区中,父进程被fork后缓冲区也被复制了,所以有两次缓冲区内容的输出

 

strlen 为函数,计算包含终止null字节的字符串长度

sizeof 则包含null长度

 

 

父进程的所有文件描述符都被<复制>( 说复制,是指父进程和子进程每个相同的文件描述符共享一个文件表项)到子进程中去了,共享偏移量

         看到这里,不禁想到 在第三章 I/O 的最后,我对此有过一些想法,这里正好证实了此想法。

 

 

在fork之后常有以下两种处理文件描述符的方式

1、  父进程等待子进程完成。

              这样,当子进程终止后,它曾读写过的文件描述符的文件偏移量已经做了更新,并传递给父进程继续使用。

2、  父进程和子进程各自执行不同的程序段。

              这种情况下,fork之后父子进程各自关闭他们不使用的文件描述符,不干扰另一方使用描述符。

之前在看管道部分的时候,不明白为什么要父子各自关闭不用的文件描述符,现如今看来,其中一方面可能是因为父子进程的文件描述符共享文件表项,会互相影响偏移量。之前大致知道会影响对方输入输出,现在算是知道了些细节。

 

 

 

这里写了一小串子进程不继承父进程的东西,有几个我不知道的:

         1、子进程不继承父进程的文件锁

         2、子进程的未处理闹钟被清除

         3、子进程的未处理信号集被清除

 

 

Fork也有失败的时候:

       1、  进程太多

       2、  实际用户ID的进程总数超过了系统限制。

用法:

         1、比如父进程接受请求,请求到来后使子进程处理请求,父进程继续等待

         2、一个进程要执行不同的程序

 


函数    vfork
    
        返回值和 fork 相同,但语义不同。(书上说此函数已在某些系统中被删除,若是可移植程序不该使用此函数)

        vfork保证子进程先运行,在其调用exec或exit中的一个函数前,其在父进程空间中运行(即相当于父进程的代码在执行),这两个中的任意一个函数之后,父进程会恢复运行。        

    
        比如这里将 vfork 代替fork 重写上面的 部分代码

#include <stdio.h>

int globvar = 6;

int main()
{

         intvar;

         pid_tpid;        


         var= 88;

         printf("...............");

 
         if((pid=vfork())< 0)

         {

                   perror();

                   exit(1);

         }

         elseif(pid == 0)

         {

                   globvar++;

                   var++;

            _exit(0);

         }


         printf("pid= %d ; globvar = %d ; var = %d\n",(long)getpid(),globvar,var);

         return0;

}

运行:
    $ ./a.out
    $  ..............
    $  pid = ? ; globvar = 7 ; var = 89

    正如以上所述,vfork 保证子进程在 exec 或 exit 之前,子程序在父进程空间运行 从而影响了父进程最终输出,且此期间父进程等待直到 exec 或 exit 之后再被唤醒。


函数     exit

    这里列举了8种进程终止的方式(第七章中的5种正常 3种异常终止)

    但是不管进程最终如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开的文件描述符,释放其所使用的存储器。

    如果父进程在子进程之前终止,子进程将如何?
        
        答案是子进程将由 init 进程收养。 大致过程是:

            在一个进程终止时,内核逐个检查所有活动进程。以判断它是否是正要终止进程的子进程:如果是,则将其PPID 改为 1。

    那如果子进程在父进程之前结束,是否是无声无息呢?

        答案是内核会为每个终止子进程保存一定的信息,父进程可以通过调用 wait 或 waitpid 获得这些信息(进程ID、终止状态、进程使用的CPU时间总量)。内核会释放其使用的所有缓存区,
            关闭所有打开的文件。

    在UNIX中,一个已经终止,但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息,释放其所占用的资源)的进程被称为僵死进程。

        看到这里,我不禁又起了疑惑, wait或waitpid是干嘛的呢? 不是说内核会释放资源的吗? 难道wait的作用不仅仅是得到子进程的终止信息???

        查了下 manpage:
            ... are used to wait for state changes ( be terminated or be stopped by a signal or be resumed by a signal ) in a child of the

 calling process and otbtain information about the child whose state has changed.   In the case of a terminated child, performing a wait allows the

 system to release the resources associated with the child, or this child remains in a "zombie" state.

            即wait之类函数用于等待子进程状态改变,可能是终止状态,中止状态或恢复重启状态; 但是要注意的是, 一个要终止的子进程,若对其使用wait, 可使系统释放其所

占资源; 否则,该子进程将进入 僵死 状态!
    
        可能内核和wait所是否的对象不一样 。。。

        一个子进程一旦被init 进程收养,就不会称为僵死进程。 因为但凡有子进程终止,其都会调用wait函数处理其。




函数     wait、 waitpid
    
    当一个进程正常或异常终止时, 内核就向其父进程发送 SIGCHLD 信号。
    
    调用这两个函数的进程会:
        
        若所有子进程都还在运行, 则阻塞;
        
        若有一个子进程终止,正等待父进程获取其终止状态,立即返回
        
        若没有任何子进程,立即出错返回。

    函数返回值:
        
        若成功返回 子进程ID; 失败返回 0;

    函数区别:
        
        在一个子进程终止前,wait使其调用者阻塞,而waitpid有一选项,可使调用者不阻塞

        waitpid并不等待在其调用之后的第一个终止子进程,有若干的选项可以控制其所等待的进程。

            之前所学过程中,对于waitpid的使用并不多,只是查阅资料的时候看到过。这里说他不等带第一个终止子进程倒是新奇,不知道有什么作用呢?

    waitpid (pid, status, option),pid参数说明
        
        若 pid == -1 ,    等待任一子进程。 与wait等效。如果子进程也创了一个子进程呢,那还算在这个 -1 范围里吗?  不算的。
    
        若 pid > 0    ,    等待进程ID与pid相等的子进程。
    
        若 pid == 0    ,    等待组ID等于调用进程组ID的任一子进程。

        若 pid < -1    ,    等待组ID等于pid绝对值的任一子进程


    option参数说明:(按位或运算)
    
        WCONTINUED        若实现支持作业控制,那么由pid制定的任一子进程在停止后已经继续,但其状态尚未报告,则返回其状态。

        WNOHANG        若由pid指定的子进程并不是立即可用的,则waitpid不阻塞,此时其返回值为0.

        WUNTRACED        若某实现支持作业控制,而由pid指定的任一子进程已经处于停止状态,并且其状态自停止以来还未报告过,则返回其状态

    两个函数再一次比较,waitpid 比 wait 函数多了几项功能:
    
        1、waitpid可以等待一特定的进程。

        2、waitpid 提供一个wait 的非阻塞版本。有时希望获取一个子进程的状态但不想阻塞。

        3、waitpid 通过WCONTINUED 、WUNTRACED 来支持作业控制。

        学到这里,我又有些不清楚了。什么叫支持作业控制呢? 上网查了一下:
    
            作业控制(jobcontrol)是shell的另一个特性,它允许用户同时运行多个作业而产生,并且根据需求可将前后台的作业进行切换。当启动某个作业时,它通常是运行在前

台,因此该作业是与终端相连接的。利用作业控制这一功能,可将正处于前台工作的作业切换到后台去,在后台该作业可继续运行,并且在前台可以监视另一个作业。如果想关

注一下某个正在后台运行的作业,那么可将其切换到前台工作,以使其又一次与终端相连接。


接下来,书本提供了一种既能父进程不等待子进程,子进程又不成为僵死状态的方法:

    不过,首先让我们来捋一捋。
    
        1、 什么时候子进程成为僵死进程?
            
            一个已经终止,但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息,释放其所占用的资源)的进程被称为僵死进程

        2、 那什么时候子进程会被 init 进程收养呢?

            如果父进程在子进程之前终止,子进程将由 init 进程收养。

        3、 子进程在被init进程收养后,还会成为僵死进程吗?
            
            不会了,init会处理好它的 - -。


    那问题就来了, 我只要父进程在子进程之前完成不久可以了吗?
        
        的确,但是父进程有父进程的事情要干,谁能规定他一定要什么时候完成呢?他就是不服气要慢点呢?

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

#define oops(m) {perror(m);exit(1);}

int main()
{
        pid_t pid;

        if((pid = fork()) < 0)
                oops("fork")
        else if(pid == 0)
        {
                if((pid = fork()) < 0)
                        oops("fork")
                else if(pid > 0)
                        exit(0);                        //here is the key point


                //------------------------I am the child process. I know that our parent would become init as soon as
                //our real parent calls exit(0) in the statement above.Here's where we'd continue excuting, knowing that
                //when we're done, init will reap our status.
                sleep(2);
                printf("I am actually a child process, parent id = %ld\n",(long)getppid());
                exit(0);
        }


        if(waitpid(pid,NULL,0) != pid)
                oops("waitpid");

        //------------------I am the parent.I can continue excuting because I knwo I am not the parent of the process above.

        return 0;
}

    根据以上的程序来看,即实现了祖孙一起运行的局面,谁也不用管谁,自个儿走自个儿的就行了。
    但是千万别忘了释放掉爷爷的儿子,不然就有个僵死进程出现了。。。


    

函数    waitid    ---没想到还有一个这种类型的函数
    
            类似于waitpid,但是比waitpid更具灵活性

    函数形式是:    wait(idtype, id, *infop, optiosns)

        参数 idtype:

            P_PID 、 P_PGID 、 p_ALL 分别表示 特定id, 特定组id中任一进程 ,  任一进程忽略id
        
        canshu  options:
        
            WCONTINUED        等待一进程,它以前曾被停止,此后又已经继续,但未报告状态。

            WEXITED        等待已退出的进程

            WNOHAND        如无可用的子进程退出状态,立刻返回而非阻塞

            WNOWAIT        不破坏子进程退出状态,该子进程退出状态可由后续的wait waitpid 或 waitid 调用取得。

            WSTOPPED        等待一进程,它已经停止,但其状态未报告。

0 0