一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,

也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。

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

在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,

fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。这种父子进程和链表的结构很像,进程形成了链表,父进程的fpid(p 意味point)指向子进程的进程id,因为子进程没有子进程,所以其fpid为0.

来看下示例代码:

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

int global=6;

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

int main()

{

int var;

pid_t pid;

pid_t pid_parent;

var=0;

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

printf("error");

if((pid=fork()) == 0){

global++;

var++;

}

else{

sleep(2);

}

printf("pid=%ld,glob=%d,var=%d\n",(long)getpid(),global,var);

exit(0);

return 0;

}

运行结果如下:从结果中可以看到在父进程和子进程中,glob和var的值不一样。在子进程中对glob和var修改的操作并没有影响父进程中的值。这是因为虽然子进程获取了父进程的数据空间,堆和栈的副本,但是并没有获取存储空间部分。也就是说父进程和子进程并不共享这些存储空间部分。

接下来继续看下fork函数的另一个升级版本,vfork函数。vfork函数和fork函数的区别如下:

1

fork():子进程拷贝父进程的数据段,代码段

vfork():子进程与父进程共享数据段

2

fork(): 父子进程的执行顺序不确定

vfork(): 保证子进程先运行,在调用exit或者exec后父进程才有可能被调用。如果在调用exit和exec前子进程依赖于父进程的进一步动作则会导致死锁。

程序修改如下:

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

int global=6;

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

int main()

{

int var;

pid_t pid;

pid_t pid_parent;

var=0;

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

printf("error");

if((pid=vfork()) == 0){

global++;

var++;

_exit(0);

}

printf("pid=%ld,ppid=%ld,glob=%d,var=%d\n",(long)getpid(),(long)getppid(),global,var);

exit(0);

return 0;

}

运行结果:从结果中可以发现2点:

1 子进程对变量加1的操作,结果改变了父进程中的变量值。因为子进程在父进程的地址空间中运行。

2 只有一个打印,而非两个打印,原因在于子进程退出的时候执行了_exit()操作,在exit中关闭了标注I/O流,那么表示标准输出FILE对象的相关存储区将被清0。因为子进程借用了父进程的地址空间。所以当父进程恢复运行并调用printf的时候,也就不会产生任何输出了。printf返回-1.

wait和waitpid函数:

在前面介绍的父子进程中,当进程退出时,它向父进程发送一个SIGCHLD信号,默认情况下总是忽略SIGCHLD信号,此时进程状态一直保留在内存中,直到父进程使用wait函数收集状态信息,才会清空这些信息. 用wait来等待一个子进程终止运行称为回收进程.当父进程忘了用wait()函数等待已终止的子进程时,子进程就会进入一种无父进程的状态,此时子进程就是僵尸进程. 僵尸进程不占用内存也不占用CPU,表面上可以忽略他们的存在。但是事实上linux操作系统限制了某一时刻同时存在的进程的最大数目。如果程序不能及时清理系统中的僵尸进程。最终会导致进程数过多,当再次需要新进程的时候就会出错。因此我们需要在子进程exit后在父进程中调用wait或waitpid.

进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉的毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,就象下面这样:

pid = wait(NULL);

如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。

代码如下:

void wait_function_test(){

pid_t pr,pc;

pc=fork();

if (pc < 0){

printf("error");

}

else if(pc == 0){

printf("this is child process with pid of %d\n",getpid());

sleep(10);

}

else{

pr=wait(NULL);

printf("I catched a child process with pid of %d\n",pr);

}

exit(0);

}

执行结果如下:在第2行结果打印出来前有10 秒钟的等待时间,这就是我们设定的让子进程睡眠的时间,只有子进程从睡眠中苏醒过来,它才能正常退出,也就才能被父进程捕捉到

如果参数status的值不是NULL,wait就会把子进程退出时的状态取出并存入其中,这是一个整数值(int),指出了子进程是正常退出还是被非正常结束的,以及正常结束时的返回值,或被哪一个信号结束的等信息。由于这些信息被存放在一个整数的不同二进制位中,所以用常规的方法读取会非常麻烦,人们就设计了一套专门的宏(macro)来完成这项工作。

1、WIFEXITED(status) 这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值(请注意,虽然名字一样,这里的参数status并不同于wait唯一的参数---指向整数的指针status,而是那个指针所指向的整数,切记不要搞混了)

2、WEXITSTATUS(status) 当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出,WEXITSTATUS(status) 就会返回5;如果子进程调用exit(7),WEXITSTATUS(status)就会返回7。请注意,如果进程不是正常退出的,也就是说, WIFEXITED返回0,这个值就毫无意义。

代码修改如下:

void wait_function_test(){

pid_t pr,pc;

int status;

pc=fork();

if (pc < 0){

printf("error");

}

else if(pc == 0){

printf("this is child process with pid of %d\n",getpid());

exit(3);

}

else{

pr=wait(&status);

if(WIFEXITED(status)){

printf("the child process %d exit normally\n",pr);

printf("the return code is %d.\n",WEXITSTATUS(status));

}

else{

printf("The child process %d exit abnormally",pr);

}

}

exit(0);

}

父进程准确捕捉到了子进程的返回值3,并把它打印了出来。

下面来看一个wait函数的升级版waitpid. 前面的函数中,如果一个进程有几个子进程,那么只要一个进程终止,wait就返回。如果要等待一个指定的进程终止比如知道某个进程的PID,那么该如何做呢,我们可以调用wait然后将其返回的进程ID和我们期望的ID相比较。如果不相同,则将该进程ID保存起来继续调用wait,反复这样做直到所期望的进程终止。这样比较麻烦,而采用waitpid则可以等待特定的进程函数。

waitpid多出了两个可由用户控制的参数pid和options,从而为我们编程提供了另一种更灵活的方式。下面我们就来详细介绍一下这两个参数:

pid:从参数的名字pid和类型pid_t中就可以看出,这里需要的是一个进程ID。但当pid取不同的值时,在这里有不同的意义。

pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。

pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。

pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。

pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。

options:options提供了一些额外的选项来控制waitpid,目前在Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用,比如:

ret=waitpid(-1,NULL,WNOHANG | WUNTRACED);

如果我们不想使用它们,也可以把options设为0,如:

ret=waitpid(-1,NULL,0);

如果使用了WNOHANG参数调用waitpid,即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去。

返回值和错误

waitpid的返回值比wait稍微复杂一些,一共有3种情况:

1、当正常返回的时候,waitpid返回收集到的子进程的进程ID;

2、如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;

3、如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid就会出错返回,这时errno被设置为ECHILD;

代码如下:

void wait_function_test(){

pid_t pr,pc;

pc=fork();

if (pc < 0){

printf("error");

}

else if(pc == 0){

sleep(10);

exit(3);

}

do{

pr=waitpid(pc,NULL,WNOHANG);

if(pr == 0){

printf("no child existed\n");

sleep(1);

}

}while(pr==0);

if(pr==pc){

printf("successfully get child %d\n",pr);

}

else{

printf("some error occured\n");

}

exit(0);

}

运行结果。可以看到即使没有子进程退出,设置了WNOHANG后,父进程也会立即返回,不会像wait那样永远等下去。

linux c编程:进程控制(一)的更多相关文章

  1. Linux系统编程@进程通信(一)

    进程间通信概述 需要进程通信的原因: 数据传输 资源共享 通知事件 进程控制 Linux进程间通信(IPC)发展由来 Unix进程间通信 基于System V进程间通信(System V:UNIX系统 ...

  2. Linux系统编程@进程管理(一)

    课程目标: 构建一个基于主机系统的多客户即时通信/聊天室项目 涉及的理论知识 进程控制:僵尸进程/孤儿进程.进程控制.守护进程... 进程间通信:管道.命名管道.信号... 多线程编程: 锁.信号量. ...

  3. Linux C 程序 进程控制(17)

    进程控制 1.进程概述现代操作系统的特点在于程序的并行执行.Linux是一个多用户多任务的操作系统.ps .pstree 查看进程进程除了进程id外还有一些其他标识信息,可以通过相应的函数获得.// ...

  4. linux 命令及进程控制

    main.c  main.o/main.obj  main/main.exe          编译                连接 程序运行;      两步: gcc/g++  -c  mai ...

  5. [linux] C语言Linux系统编程进程基本概念

    1.如果说文件是unix系统最重要的抽象概念,那么进程仅次于文件.进程是执行中的目标代码:活动的.生存的.运行的程序. 除了目标代码进程还包含数据.资源.状态以及虚拟化的计算机. 2.进程体系: 每一 ...

  6. linux系统编程-进程

    进程 现实生活中 在很多的场景中的事情都是同时进行的,比如开车的时候 手和脚共同来驾驶汽车,再比如唱歌跳舞也是同时进行的: 如下是一段视频,迈克杰克逊的一段视频: http://v.youku.com ...

  7. linux与Windows进程控制

    进程管理控制 这里实现的是一个自定义timer用于统计子进程运行的时间.使用方式主要是 timer [-t seconds] command arguments 例如要统计ls的运行时间可以直接输入t ...

  8. linux系统调用之进程控制

    1 进程控制: fork                                                                                     创建一 ...

  9. Linux多任务编程——进程

    进程编程常用函数 1--- fork pitd_t fork(void); 创建一个新的子进程,其父进程为调用 fork() 函数的进程: 返回值:成功:子进程返回 0,父进程返回 子进程 PID:失 ...

  10. Linux系统编程@进程管理(二)

    1.创建守护进程(Deamon) 守护进程的概念与作用 后台服务程序 – 系统服务,进程名字往往以’d’结尾,生存周期比较长(系统装入时启动,关闭时候终止.系统装入两种启动方式:1从启动脚本.etc/ ...

随机推荐

  1. 系统重装 JUJUMAO VHD安装WIN7解析

    1 把原有系统分区一个删除(使用第三方分区工具如PM或者PQ)并新建一个系统分区(也可以同时新建其他分区如D盘,E盘,但是一定要把C盘设置为NTFS格式并且活动的!)   2 将JUJUMAO_VHD ...

  2. 全民Scheme(1):数字游戏

    刚刚看到两句话,感觉不错,分享给大家: 1.用户的问题.就是我们的问题 2.做一些用户想要的东西 (define add1 (lambda (x) (+ 1 x))) (define sub1 (la ...

  3. Android SDK安装时出错“android Failed to rename directory”的解决方法

    Android SDK安装时出错"android Failed to rename directory"的解决的方法     安装Android SDK时遇到Failed to r ...

  4. 【我们都爱Paul Hegarty】斯坦福IOS8公开课个人笔记30 ScrollView Demo实战

    在上一话中我们创建了一个通过URL读取图片的Demo,这个Demo是不能拖动和缩放的.如今给它添加选项让它能够手动切换URL,并把图片加入到ScrollView中. 向Storyboard中拖入一个s ...

  5. 【Datastage】Datastage在win10上安装报错:This Application requires one of the following versions of the .NET Framework:v1.1.4322 Do you want to install this .NET Framework version now?

    Datastage在win10上安装报错如下: 这个错误的意思是:.netFramWork的版本不符合要求,于是,我在网上下载了一个版本一致的 下载地址为:http://pan.baidu.com/s ...

  6. Java 基础,小数百分比两种方法

    public static void main(String[] args) { System.out.println(getPercent(1, 2)); } public static Strin ...

  7. 删除数据库时提示数据库正在被使用,无法删除(Cannot drop database databasename because it is currently in use)的问题

    删除数据库时提示数据库正在被使用,无法删除(Cannot drop database databasename because it is currently in use)的问题   删除数据库时提 ...

  8. 监听EditText字数

    editContent.addTextChangedListener(new TextWatcher() { private CharSequence temp;private int editSta ...

  9. Windows下UEFI环境的搭建

    Windows下UEFI环境的搭建 一.环境准备 1. 安装2012及以上VS https://visualstudio.microsoft.com/ 2.下载NASM 2.13.03  http:/ ...

  10. .Vue 文件 ES6 语法 webstorm 中的一个识别Bug

    webstorm 2017 版本中即使安装了vue template file 设置了 js 语言为 es6 语法仍旧会出现识别不了划线的情况,苦寻很久,最后解决方式如下 <script typ ...