码迷,mamicode.com
首页 > 系统相关 > 详细

进程控制

时间:2015-12-06 20:44:24      阅读:281      评论:0      收藏:0      [点我收藏+]

标签:

exit

一个进程的退出通常是调用 exit() 函数。而在 main 函数中调用 return()exit() 其含义是一致的。进程的退出通常有以下的几种方式:

  1. 从main函数返回
  2. 调用exit
  3. 调用exit或者Exit
  4. 从最后一个线程从其启动例程返回
  5. 从最后一个线程调用pthread_exit

终止的方式:

  1. 调用abort
  2. 接到一个信号
  3. 最后一个线程对取消请求作出响应

三个 exit函数之间存在差异,调用 exit 函数会首先进行一些清理动作然后再返回内核。清理动作包括先调用用户通过 atexit(void (*func)(void)) 函数登记的清理函,然后执行标准IO库的清理关闭操作,这造成输出缓冲中的所有数据都被冲洗。而_exit 还有 _Exit 函数则直接返回内核。它们的共同之处是都可以带一个整形参数,称作退出状态。在 shell 下可以通过 $? 变量来查看。而进程异常终止时,内核会产生一个指示该进程异常终止原因的终止状态。这两个概念稍有差异,正常退出情况下的退出状态也会被内核转换成终止状态,父进程需要调用 wait 函数来获得子进程的 退出状态 以避免子进程成为僵死进程。

fork

fork 函数可以在当前进程中再创建出一个子进程来处理一些事情。其通常的写法如下:

/* the usual usage of fork */

pid_t pid;

if ((pid = fork()) < 0) 
{
    printf("fork error\n");
    return;
}
else if (pid == 0)
{
    /* the child process code */
    // do something

}
else
{
    /* the parent process code */
    // do something

}

/* both parent and child process code */
// do something

 

看上去有些困惑,分不清哪里是父进程执行代码哪里是子进程执代码。要搞清楚的话,必须明白以下两点:

  1. 在调用fork成功之后,子进程即被创建,它沿着fork之后的代码段往下执行;
  2. fork 函数有两个返回值,如果不出错的话,对于子进程而言,它返回0;对父进程而言返回子进程ID,出错返回值小于0;
  3. 子进程与父进程的执行顺序并不确定,虽然父进程可以使用 sleep() 函数来使得子进程先执行,但并不能确保如此,这取决于内核的进程调度;

除了要清楚 fork 之后子进程、父进程各自执行什么样的代码段,在哪里退出等等这些。还必须清楚子进程与父进程之间的关系,首先来说,它们是两个进程,子进程与父进程拥有独立的地址空间;其次,子进程是父进程的副本,它的含义解释如下:

  1. 子进程获得父进程数据空间、堆和栈的副本。
  2. 父进程和子进程共享正文段。
  3. 父进程的所有打开文件描述符都被复制到子进程中,但是它们共享同一个文件表项,这意味着共享文件偏移量,文件操作时特别需要同步操作。

下面的这个例子能很好的加深对子进程与父进程关系的理解

int main()
{
    pid_t pid;
    int val = 5;
    printf("test fork function\n");

    if ((pid = fork()) < 0)
    {    
        printf("fork error\n");
        return(1);
    }

    else if (pid == 0)
    {
        printf("fork success\n");
        val++;
    }
    
    else
        sleep(2);
    
    printf("hello world\n");
    printf("this process id is %d, and it‘s val is %d\n", getpid(), val);

    return 0;
}

 

fork 函数通常有下面的这两种用法:

  1. 网络编程中,父进程通过创建一个子进程来处理连接请求;
  2. 子进程在fork之后调用exec函数,执行其他的程序。这一点在shell中是司空见惯的。

在这篇文章的最后,会用 fork 函数来实现我们自己的 system 函数!

wait watipid

当父进程通过 fork 函数创建出子进程之后,这两个进程退出时时序只有两种情况:

  1. 父进程先于子进程退出。这种情况下子进程会成为孤儿进程而被 init 进程收养;
  2. 子进程先于父进程退出。 无论子进程是正常退出还是异常终止,内核都会向父进程发送 SIGCHLD 信号,父进程可以提供信号处理函数,系统默认的动作是忽略此信号。这个时候子进程虽然退出了,但是仍占有系统资源未被释放,比如进程ID,这个时候的子进程称作 僵死进程。直到父进程调用 wait 函数取得该子进程终止状态。

所以说 wait 函数将会更好的控制我们所编写的程序,更好的利用我们的系统。wait 函数的定义如下:

#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options);

关于 wait 函数的调用的注意点,说明如下:

  1. 如果其所有子进程都还在运行,则阻塞;
  2. 如果一个子进程已终止,正等待父进程获得其终止状态,则取得该子进程的终止状态立即返回;
  3. 如果它没有任何子进程,则立即出错返回;

waitpidwait 函数的区别在于,waitpid 函数可以:

  1. 提供非阻塞的调用
  2. 通过参数来控制它所等待的进程,而非第一个终止的子进程

两个函数的共同之处就是都传入了 int *statloc 参数,它用来保存子进程的终止状态。也可以传入 NULL, 表示父进程并不关心子进程的终止状态。

通过上面的学习至少清楚了 wait 函数的作用还有用法。下面通过这个例子来简单的练习一下,这个例子通过使用一个技巧来避免我们所编写的程序产生 僵死进程:

/* the skill to avoid the dead process */

pid_t pid;
if ((pid = fork()) < 0)
    return;
else if (pid == 0)
{
    if ((pid = fork()) < 0)
        return;
    else if (pid > 0)
        exit(0);

    sleep(2);
    printf("second child, parent pid = %ld\n", (long)getppid());
    
    /* do something in second child process */

    exit();
}

if (waitpid(pid, NULL, 0) != pid)  /* no need to handle the second child process */
    printf("waitpid error\n");

exit(0);

 

exec

fork 函数在创建一个新的进程之后一种常用的用法便是调用 exec 函数以执行新的程序。可是子进程是父进程的副本,子进程应该是执行的父进程代码才是啊! 原来当调用 exec 函数时,该进程执行的程序完全被替换成新的程序,而新的程序则从其 main 函数开始执行。因为调用 exec 函数并不创建新的进程,所以前后进程ID并未改变。exec 只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆还有栈。

一共有七种 exec 函数,记忆起来确实繁琐。如果需要用的话到时候再去翻书好了。无谓就是传入可执行程序的路径名,还有供程序使用的命令行参数,有的还需要传入环境表参数。下面就只列出其中的一种 exec 函数

#include <unistd.h>
int execv(const char *pathname, char *const argv[]);
                                关于返回值: 若出错,返回-1;若成功,不返回

通过 exec 函数我们可以在我们的程序中调用 脚本文件。 这简直是很大的福利,会使得我们的程序处理更加灵活。控制力更强。脚本程序作为一种解释器文件,最终的解释以及执行都依靠 bash 程序。只要在 exec 函数调用 /bin/sh ,再传入脚本文件参数即可。下面是一个例子:

int main()
{
    pid_t pid;
    char *cmd[] = {"./date.sh", (char*)0};

    if ((pid = fork()) < 0)
    {    
        printf("fork error\n");
        return -1;
    }

    else if (pid == 0)
    {
        if (execv("/bin/sh", cmd) < 0);
        {
            printf("execv error\n");
            return -1;
        }
    }
    
    else
        sleep(2);
    
    return 0;
}

 

system

上面的例子演示了在程序中如何调用脚本问价,有的时候我们需要在程序中执行一些简单的 shell 命令或者脚本文件,使得我们程序的控制力更强。但如果每次都需要 fork 以及 exec 的话,显然是太繁琐。unix提用了 system 函数,我们通常可以像这样调用: system("date > file1") 。下面我们通过上面的函数来自己实现 system 函数,也算是一种练习!

/* to release ourself system() funtciotn */

void system(const char *cmd)
{
    pid_t pid;
    int status;

    if (cmd == NULL)
        return;

    if ((pid = fork()) < 0)
    {
        printf("fork error\n");
        return -1;
    }

    else if (pid == 0)
    {
        if (execv("/bin/sh", &cmd) < 0)            
        {
            printf("execv error\n");
            return -1;
        }
    }

    else
    {
        while (waitpid(pid, &status, 0))
            ;
    }

    return status;
}

 

进程控制

标签:

原文地址:http://www.cnblogs.com/Gru--/p/5022451.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!