linux c编程:进程控制(一)
一个进程,包括代码、数据和分配给进程的资源。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编程:进程控制(一)的更多相关文章
- Linux系统编程@进程通信(一)
进程间通信概述 需要进程通信的原因: 数据传输 资源共享 通知事件 进程控制 Linux进程间通信(IPC)发展由来 Unix进程间通信 基于System V进程间通信(System V:UNIX系统 ...
- Linux系统编程@进程管理(一)
课程目标: 构建一个基于主机系统的多客户即时通信/聊天室项目 涉及的理论知识 进程控制:僵尸进程/孤儿进程.进程控制.守护进程... 进程间通信:管道.命名管道.信号... 多线程编程: 锁.信号量. ...
- Linux C 程序 进程控制(17)
进程控制 1.进程概述现代操作系统的特点在于程序的并行执行.Linux是一个多用户多任务的操作系统.ps .pstree 查看进程进程除了进程id外还有一些其他标识信息,可以通过相应的函数获得.// ...
- linux 命令及进程控制
main.c main.o/main.obj main/main.exe 编译 连接 程序运行; 两步: gcc/g++ -c mai ...
- [linux] C语言Linux系统编程进程基本概念
1.如果说文件是unix系统最重要的抽象概念,那么进程仅次于文件.进程是执行中的目标代码:活动的.生存的.运行的程序. 除了目标代码进程还包含数据.资源.状态以及虚拟化的计算机. 2.进程体系: 每一 ...
- linux系统编程-进程
进程 现实生活中 在很多的场景中的事情都是同时进行的,比如开车的时候 手和脚共同来驾驶汽车,再比如唱歌跳舞也是同时进行的: 如下是一段视频,迈克杰克逊的一段视频: http://v.youku.com ...
- linux与Windows进程控制
进程管理控制 这里实现的是一个自定义timer用于统计子进程运行的时间.使用方式主要是 timer [-t seconds] command arguments 例如要统计ls的运行时间可以直接输入t ...
- linux系统调用之进程控制
1 进程控制: fork 创建一 ...
- Linux多任务编程——进程
进程编程常用函数 1--- fork pitd_t fork(void); 创建一个新的子进程,其父进程为调用 fork() 函数的进程: 返回值:成功:子进程返回 0,父进程返回 子进程 PID:失 ...
- Linux系统编程@进程管理(二)
1.创建守护进程(Deamon) 守护进程的概念与作用 后台服务程序 – 系统服务,进程名字往往以’d’结尾,生存周期比较长(系统装入时启动,关闭时候终止.系统装入两种启动方式:1从启动脚本.etc/ ...
随机推荐
- Transform.eulerAngles 欧拉角
var eulerAngles : Vector3 Description描述 The rotation as Euler angles in degrees. 旋转作为欧拉角度. The x, y, ...
- Linux非阻塞IO(四)非阻塞IO中connect的实现
我们为客户端的编写再做一些工作. 这次我们使用非阻塞IO实现connect函数. int connect(int sockfd, const struct sockaddr *addr, sockle ...
- POJ 2029 Get Many Persimmon Trees (二维树状数组)
Get Many Persimmon Trees Time Limit:1000MS Memory Limit:30000KB 64bit IO Format:%I64d & %I ...
- quick-cocos2d-x transition使用方法
Functions transition.newEasing(action, easingName, more) 为图像创造效果 transition.execute(target, action, ...
- JUnit单元测试中的setUpBeforeClass()、tearDownAfterClass()、setUp()、tearDown()方法小结
编写JUnit单元测试的时候,会用到 setUpBeforeClass().tearDownAfterClass().setUp().tearDown()这四个方法,例如用 eclipse新建一个ju ...
- 【翻译自mos文章】asm 归档路径满了
asm 归档路径满了 參考原文: ASM Archive destination is full. (Doc ID 351547.1) 适用于: Oracle Server - Enterprise ...
- Memcached的LRU和缓存命中率
缓存命中率 命中:直接从缓存中读取到想要的数据. 未中:缓存中没有想要的数据,还需要到数据库进行一次查询才能读取到想要的数据. 命中率越高,数据库查询的次数就越少. 读取缓存的速度远比数据库查询的速度 ...
- 用第三方下载工具下载官方XCode独立安装包的方法
用第三方下载工具下载官方XCode独立安装包的方法 下载步骤 下载 aria2 工具配置好并启动 (利用其支持配置Cookie并多线程下载的功能.而迅雷则不支持设置Cookie,所以不支持这种需要登录 ...
- Android Studio 使用笔记:记录使用Gradle配置AndroidAnnotations
系统:Mac Yosemit 10.10 JDK:1.6+ Android Studio:1.2 原来看到有人用AndroidAnnotations,十分羡慕.但是Gradle并不熟悉,现找到了正确的 ...
- man page及info page用法
Linux系统的在线求助man page与info page 先来了解一下Linux有多少命令呢?在文本模式下,你可以直接按下两个[Tab]按键,看看总共有多少命令可以让你用? [vbird@www ...