进程控制(1)

进程标识符

每一个进程都有肺腑的整形表示唯一的进程ID。按一个进程终止后,其进程ID就能够再次使用了。例如以下是几个典型进程的ID及其类型和功能。

ID     进程名        中文名                 类型                 作用

0     swapper      交换进程              系统进程        它是内核一部分,不运行磁盘上的程序,是调度进程。

1    init                init进程               用户进程        永远不会终止,启动系统,读取系统初始化的文件。

2    pagedaemon页精灵进程          系统进程        虚存系统的请页操作

除了进程ID,每一个进程另一些其它的标识符。下列函数返回这些标识符:

#include <sys/types.h>

#include <unistd.h>

pid_t getpid(void);                      //返回值:调用进程的进程ID

pid_t getppid(void);          //返回值:调用进程的父进程ID

uid_t getuid(void);                      //返回值:调用进程的实际用户ID

uid_t geteuid(void);                   //返回值:调用进程的有效用户ID

gid_t getgid(void);                      //返回值:调用进程的实际组ID

gid_t getegid(void);                   //返回值:调用进程的有效组ID

fork函数

#include <unistd.h>

pid_t fork(void);

一个现有进程能够调用fork创建一个新进程。

返回值:子进程中返回0,父进程中返回子进程ID,出错返回-1。例如以下

子进程是父进程的副本。比如:子进程获得父进程数据空间、堆和栈的副本。父子进程不共享这些存储空间部分。父子进程共享正文段。

因为fork之后常常归属exec,所以如今非常多实现并不运行一个父进程数据段、栈和堆的全然复制。作为你替代,使用了写时复制(Copy-On-Write)技术。这些区域由父子进程共享,并且内核将他们的訪问权限改变为仅仅读的。假设父子进程中的任一个试图改动这些区域,则内核仅仅为改动区域的那块内存制作一个副本。

以下的程序演示了fork函数,从中能够看出子进程对变量所作的改变并不去影响父进程中该变量的值。

#include <unistd.h>
#include <stdio.h> int glob = 6; /* externalvariable in initialized data */
char buf[] = "a write to stdout\n"; int
main(void)
{
int var; /* automatic variable on the stack */
pid_t pid; var = 88;
if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1)
perror("write error");
printf("before fork\n"); /* we don't flush stdout */ if ((pid = fork()) < 0) {
perror("fork error");
}else if (pid == 0) { /* child */
glob++; /* modifyvariables */
var++;
}else {
sleep(2); /* parent*/
} printf("pid = %d, glob = %d, var = %d\n", getpid(), glob,var);
exit(0);
}

运行及输出结果:

chen123@ubuntu:~/user/apue.2e$./a.out

awrite to stdout

beforefork

pid= 2755, glob = 7, var = 89

pid= 2754, glob = 6, var = 88

chen123@ubuntu:~/user/apue.2e$./a.out > temp.out

chen123@ubuntu:~/user/apue.2e$cat temp.out

awrite to stdout

beforefork

pid= 2758, glob = 7, var = 89

beforefork

pid= 2757, glob = 6, var = 88

一般来说fork之后父进程和子进程的运行顺序是不确定的,这取决于内核的调度算法。在上面的程序中,父进程是自己休眠2秒钟,以使子进程先运行。

程序中fork与I/O函数之间的关系:write是不带缓冲 的,由于在fork之前调用write,所以其数据仅仅写到标准输出一次。标准I/O是缓冲的,假设标准输出到终端设备,则它是行缓冲,否则它是全缓冲。当以交互方式执行该程序时,仅仅得到printf输出的行一次,由于标准输出到终端缓冲区由换行符冲洗。但将标准输出重定向到一个文件时,由于缓冲区是全缓冲,遇到换行符不输出,当调用fork时,其printf的数据仍然在缓冲区中,该数据将被拷贝到子进程中,该缓冲区也被拷贝到子进程中。于是父子进程的都有了带改行内容的标准I/O缓冲区,所以每一个进程终止时,会冲洗其缓冲区中的数据,得到第一个printf输出两次。

文件共享

fork的一个特性是父进程的全部打开文件描写叙述符都被拷贝到子进程中。父子进程的每一个同样的打开描写叙述符共享一个文件表项。如果一个进程有三个不同的打开文件,在从fork返回时,我们有例如以下所看到的结构:

在fork之后处理的文件描写叙述符有两种常见的情况:

1.      父进程等待子进程完毕。在这样的情况下,父进程无需对其描写叙述符做不论什么处理。当子进程终止后,子进程对文件偏移量的改动已运行的更新。

2.       父子进程各自运行不同的程序段。这样的情况下,在fork之后,父子进程各自关闭他们不须要使用的文件描写叙述符,这样就不会干扰对方使用文件描写叙述符。这样的方法在网络服务进程中常常使用。

父子进程之间的差别:

1.      fork的返回值

2.      进程ID不同

3.      具有不同的父进程ID

4.      子进程的tms_utime、tms_stime、tms_cutime及tms_ustime均被设置为0

5.      父进程设置的文件锁不会被子进程继承

6.      子进程的未处理闹钟被清除

7.      子进程的未处理信号集被设置为空集

fork有以下两种使用方法:

1.      一个父进程希望复制自己,使父子进程同一时候运行不用的代码段。比如,父进程等待client请求,生成子进程来处理请求。

2.      一个进程要运行一个不同的程序。比如子进程从fork返回后,调用exec函数。

fork调用失败的原因:

1.      系统中有太多的进程

2.      实际用户的进程数超过了限制

vfork函数

vfork函数的调用序列和返回值与fork同样,但两者的语义不同。

vfork用于创建一个新进程,而该新进程的目的是exec一个新程序。vfork与fork都创建一个子进程,但它不将父进程的地址空间拷贝到子进程中,由于子进程会马上调用exec,于是不会存訪问该地址空间。相反,在子进程调用exec或exit之前,它在父进程的空间中执行,也就是说会更改父进程的数据段、栈和堆。vfork和fork还有一差别在于:vfork保证子进程先执行,在它调用exec之后父进程才可能被调度执行。

以下是vfork的使用程序:

#include"unistd.h"
#include"stdio.h" int glob = 6; /* external variable in initialized data*/ int
main(void)
{
int var; /* automatic variableon the stack */
pid_t pid; var = 88;
printf("before vfork\n"); /* we don't flush stdio */
if ((pid = vfork()) < 0) {
perror("vfork error");
} else if (pid == 0) { /* child */
glob++; /* modify parent's variables*/
var++;
_exit(0); /* child terminates */
}
/*
* Parent continues here.
*/
printf("pid = %d, glob = %d, var =%d\n", getpid(), glob, var);
exit(0);
}

运行及输出结果例如以下所看到的:

chen123@ubuntu:~/user/apue.2e$./a.out

before vfork

pid = 2984, glob= 7, var = 89

可见子进程直接改变了父进程的变量值,由于子进程在父进程的地址空间中执行。

这里子进程调用_exit是由于_exit并不运行标准I/O缓冲的冲洗操作。假设调用exit,该程序结果不确定,依赖于标准I/O库的实现。由于exit有可能关闭标准I/O流,那么会使父进程不产生不论什么输出。

exit函数

进程有5中正常终止方式,3中异常终止方式。(见上一篇文章)。

对于随意一种终止情形,我们都希望终止进程可以统治父进程它是怎样终止的。对于三个终止函数(exit、_exit和_Exit),实现这一点的方法是,将其退出状态作为參数传送给函数。在异常终止情况下,内核产生一个指示其异常终止原因的终止状态。在随意一种情况下,该终止状态的父进程都能使用wait或waitpid函数取得其终止状态。

在调用_exit时,内核将进程的退出状态转换成终止状态。

对于父进程已经终止的全部进程,他们的父进程都改变为init进程。我们称这些进程有Init领养。一个init的子进程(包含领养进程)终止时,init会调用一个wait函数取得其终止状态。

对于一个已经终止、但其父进程尚未对其进行善后处理(获取终止子进程的有关信息,释放它仍占有的资源)的进程被称为僵尸进程。子进程终止时,尽管不在执行,但它仍然存在与系统中,进程表中代表子进程的表项不会立马被释放,由于它的退出码还须要保存在进程表项中以备父进程今后的wait调用使用,也就是说终止子进程与父进程之间的关联还会保持,直到父进程也正常的终止或父进程调用wait才告结束。

wait和waitpid函数

当一个进程正常或异常终止时,内核就向其父进程发送一个SIGCHLD信号。由于子进程终止是一个异步事件,所以发生这样的信号也是内核向父进程发的异步通知。父进程能够选择忽略该信号,或者提供一个该信号发生时即被调用运行的函数。对于这样的信号的系统默认动作是忽略它。如今须要知道的是调用wait或waitpid的进程可能会发生什么情况:

1.假设其全部子进程都还在执行,则堵塞

2.假设一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态马上返回。

3.假设它没有不论什么子进程,则马上出错返回。

#include<sys/types.h>

#include<sys/wait.h>

pid_t wait(int*status);

pid_twaitpid(pid_t pid, int *status, int options);

假设进程因为接收到SIGCHLD而调用wait,则可期望wait会马上返回。但假设在随意时刻调用wait,则进程可能堵塞。

在一个子进程 终止前,wait使其调用者堵塞,而waitpid有一个选项,可使调用者不堵塞。

这两个函数的參数statloc是一个整形指针。假设statloc不是一个空指针,则终止进程的终止状态就存放在它所指的单元内。假设不关心终止状态,则可将该參数设为空指针。

可用下面宏来检查wait和waitpid所返回的终止状态:

WIFEXITED(status)           若为正常终止子进程返回的状态,则为真。

WEXITSTATUS(status)      若WIFEXITED非零,返回子进程退出码。

WIFSIGNAKED(status)     若为子进程异常终止返回状态(收到一个未捕捉的信号),则为真

WTERMSIG(status)          若WIFSIGNAKED非零,则返回一个信号编号

WIFSTOPPED                      若为子进程意外终止,则为真

WSTOPSIG                          若WIFSTOPPED非零,返回一个信号编号

waiptpid提供了wait没有提供的三个功能:

1.      waitpid可等待一个特定的进程

2.      waitpid提供了一个wait的非堵塞版本号

3.      waitpid支持作业控制



Linux/UNIX进程控制(1)的更多相关文章

  1. Supervisor安装与配置(Linux/Unix进程管理工具)

    原文链接:http://blog.csdn.net/xyang81/article/details/51555473 Supervisor(http://supervisord.org/)是用Pyth ...

  2. Supervisor(Linux/Unix进程管理工具)安装与配置

    参考链接:https://blog.csdn.net/xyang81/article/details/51555473 Supervisor(http://supervisord.org/)是用Pyt ...

  3. linux中进程控制

    1.进程标识 每个进程都有一个非负整型表示的唯一的进程ID.进程ID标识符总是唯一的.  虽然进程ID是唯一的,但某个ID被回收后,ID号是可以复用的. ID为0的进程通常是调度进程(其常常被称交换进 ...

  4. linux&c 进程控制 课后习题

    (声明:本篇博客只是博主自己的理解,加以整理,目的是总结刚学过的进程知识,不一定绝对正确,非常愿意听客官您提出宝贵意见.) Q1:进程中的全局数据段(全局变量),局部数据段(局部变量),静态数据段的分 ...

  5. linux&c 进程控制 课后习题

    (声明:本篇博客只是博主自己的理解,加以整理,目的是总结刚学过的进程知识,不一定绝对正确,非常愿意听客官您提出宝贵意见.) Q1:进程中的全局数据段(全局变量),局部数据段(局部变量),静态数据段的分 ...

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

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

  7. UNIX进程

    UNIX进程控制的博客   http://blog.csdn.net/yang_yulei/article/details/17404021 Linux的概念与体系    http://www.cnb ...

  8. 进程控制之exec函数

    用fork函数创建子进程后,子进程往往要调用一种exec函数以执行另一个程序.当进程调用一种exec函数时,该进程执行的程序完全替换为新程序,而新程序则从其main函数开始执行.因为调用exec并不创 ...

  9. 【linux草鞋应用编程系列】_2_ 环境变量和进程控制

    一. 环境变量     应用程序在执行的时候,可能需要获取系统的环境变量,从而执行一些相应的操作.     在linux中有两种方法获取环境变量,分述如下.   1.通过main函数的参数获取环境变量 ...

随机推荐

  1. Html内容超出标记宽度后自动隐藏

    我们在显示长文本时,往往需要去在C#端去截取字符,但这绝对不是一个好方面,因为我们的长文本往往都是代HTML标记的,你一个载不好,就会出现乱码问题(出现半个HTML标记),而比较好的作法就是通过CSS ...

  2. 如何使用win7自带的备份还原以及创建系统镜像------傻瓜式教程

    对于经常鼓捣电脑的童鞋来说,装系统是一件极其平常的事情,不过系统装多了之后,我们会感到比较烦躁,因为每一次装系统意味着驱动的重新安装,程序的重新安装,每次这么鼓捣几次,半天时间就花在这上面了,效率是在 ...

  3. iOS 数组里面取字典的值

    NSArray *arrData = @[@"1",@"2",@"3",@"4"]; NSArray *arrKey = ...

  4. UberX及以上级别车奖励政策(优步北京第一组)

    优步北京第一组: 定义为2015年6月1日凌晨前(不含6月1日)激活的司机(以优步后台数据显示为准) 滴滴快车单单2.5倍,注册地址:http://www.udache.com/如何注册Uber司机( ...

  5. Rikka with Graph(联通图取边,暴力)

    Rikka with Graph Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) ...

  6. NYOJ-252 01串

    01串 时间限制:1000 ms  |  内存限制:65535 KB 难度:2 描写叙述 ACM的zyc在研究01串,他知道某一01串的长度,但他想知道不含有"11"子串的这样的长 ...

  7. a simple erlang process pool analysis

    a simple erlang process pool analysis 这是一个简单的erlang进程池分析,是learn you some erlang for Great Good 里面的一个 ...

  8. JavaScript之childNodes属性、nodeType属性学习

    1.childNodes属性:在一颗节点树上,childNodes属性可以用来获取任何一个元素的所有元素,它是一个包含这个元素所有子元素的数组. <body> <script typ ...

  9. This project references NuGet package(s) that are missing on this computer.

    Install Nuget. Right click on the solution and select "Enable NuGet Package Restore". Clic ...

  10. android listview 重用view导致的选择混乱问题

    20150526 listview是常用的控件,经常用自定义的adapter,为了提高显示效率,常利用view的重用方式防止重绘,但因为重用利用的是旧的view,常导致显示的数据会由于position ...