《unix环境高级编程》 读书笔记 (7)

process control


1 process identifiers

每一个进程都有一个唯一的非负整型做为标识符。

#include <unistd.h>

pid_t getpid();

pid_t getppid();

pit_t getuid();

pit_t geteuid();

pit_t getgid();

pit_t getegid();

getpid: 返回进程ID

getppid: 返回父进程ID

getuid: 获得进程的real user ID

geteuid: 获得进程的effective user ID

关于real user ID和effective user ID:

Advanced Programming in the UNIX Environment (2) 第三节,链接:

http://blog.csdn.net/alex_my/article/details/39184461

getgid: 获取进程的real group ID

getegid: 获取进程的effective group ID

它们的用法在本文的后续章节中出现。


2 fork function

#include <unistd.h>

pid_t fork(void);

创建一个新的子进程,这个函数一次调用,两次返回,在原进程(即未来的父进程)中,返回的值为子进程ID,在子进程中,返回的值为0。子进程只能拥有一个副进程,可以通过getppid获取到父进程的ID。

理论上来说,子进程会复制一份父进程的数据空间,堆,栈。但是,往往在程序中,执行fork之后,常常在子进程中调用exec,该子进程完全由新的程序替换,覆盖了原先子进程复制的这部分数据空间,堆,栈,使得之前的复制白费力。因此,在大部分的实现中,子进程并不立马拷贝一份,而是使用一种"copy-on-write",当哪个进程要改变某一小部分内容的时候,就将小部分内存拷贝一份,供其使用。

至于子进程具体继承了什么,不继承什么,可以查询man 2 fork。

继承部分:

real user ID, effective user ID, real group ID, effective group ID

supplementary group IDs

process group ID

session ID

controlling terminal

ser-user-ID and set-group-ID flags

current working directory

root directory

file mode creation mask

signal mask and dispositions

the close-on-exec flag for any open file descriptors

environment

attached shared memory segments

memory mappings

resource limit

不继承部分:

child‘s tms_utime, tms_stime, tms_cutime, and tms_cstime are set to zero

file locks

pending alarms are cleared for the child

the set of pending signals for the child is set to the empty set

程序用例:

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <errno.h>

#include <string.h>

int main(int argc, char* argv[])

{

int n = 1;

printf("process id: %d \n", getpid());

printf("test stdio buffer ");

pid_t pid = fork();

if(pid < 0)

{

printf("fork failed, error[ %d ] \t [ %s ]\n", errno, strerror(errno));

exit(EXIT_FAILURE);

}

if(pid == 0)

{

printf("children, pid: %d \t ppid: %d \n", getpid(), getppid());

++n;

}

else

{

printf("parent, pid: %d \t ppid: %d \n", getpid(), getppid());

}

printf("n: %d \n", n);

return 0;

}

输出:

process id: 7328

test stdio buffer parent, pid: 7328 ppid: 4766

n: 1

test stdio buffer children, pid: 7329 ppid: 7328

n: 2

在fork()前调用的语句printf("test stdio buffer ")被执行了两次。这是因为,当标准输出是终端设备的时候,是行缓冲的,否则,是全缓冲。

当把"test stdio buffer "填入输出缓冲区的时候,并没有flush到终端,接下来子进程复制了这部分,因此会输出两次。

当句子改为:printf("test stdio buffer \n")的时候,仅输出一次,因为换行引起flush。


3 vfork function

vfork产生一次新进程,与fork不同的是:

-1:父子进程共享数据空间,在程序用例中有体现

-2: 子进程先运行,直到子进程调用exec或者退出后,父进程才会运行,如果子进程在运行过程中,需要等待父进程接下来运行的某个状态或者数值,则会死锁。

修改下fork的程序用例:

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <errno.h>

#include <string.h>

int main(int argc, char* argv[])

{

int n = 1;

printf("process id: %d \n", getpid());

pid_t pid = vfork();

if(pid < 0)

{

printf("fork failed, error[ %d ] \t [ %s ]\n", errno, strerror(errno));

exit(EXIT_FAILURE);

}

if(pid == 0)

{

printf("children, pid: %d \t ppid: %d \n", getpid(), getppid());

++n;

printf("n: %d \n", n);

_exit(0);

}

else

{

printf("parent, pid: %d \t ppid: %d \n", getpid(), getppid());

printf("n: %d \n", n);

}

exit(EXIT_SUCCESS);

}

输出:

process id: 7893

children, pid: 7894 ppid: 7893

n: 2

parent, pid: 7893 ppid: 6718

n: 2

由于父子进程共享数据空间,因此,父进程中的n也为2(其实是同一个n)

需要注意的是,子进程中终止进程采用的是_exit(0)。由前面的章节得知,_exit并不会flush标准流。

如果子进程中,调用exit(0),而系统实现exit中会关闭标准I/O流,由于二者共享空间,那么父进程调用printf的时候,就不会产生输出。

不过,现在大多是的实现中,不会在exit中关闭标准I/O流,因为进程即将关闭,内核将关闭所有打开的文件描述符,因此,不必在exit中多此一举。所以,如果子进程中使用exit终止进程,编译运行,也会正常。


4 exit

exit相关已经在BOOKS: Advanced Programming in the UNIX Environment (6)第一节有所描述。

进程退出,释放所占用的资源,包括打开的文件描述符,申请的内存等,但是仍然保留了一定的信息,包括进程号,终止状态,运行时间等。直到父进程调用wait/waitpid来获取才会释放。

如果一个进程,大量产生子进程,等待子进程结束之后,缺不主动调用wait/waitpid去释放子进程的保留信息,就会造成不良后果,比如进程号被占用(系统所能使用的进程号是有限的),积累到一定程度后,会造成进程由于进程号限制而创建失败。

有一些退出的情况:

-1: 当父进程在子进程之前退出,即子进程失去了父亲,成为了孤儿进程,这时候,init进程就会收养这些孤儿进程,并且循环调用wait来释放已经推出的子进程。

-2: 当子进程先于父进程退出,释放一定的资源后,还会留下一些信息,此时的子进程称为僵尸进程。这些信息由父进程调用wait/waitpid后才会释放。子进程在退出的时候,会发送SIGCHLD信号给父进程,父进程接到信号后,在信号处理函数中调用wait/waitpid处理这些僵尸进程。默认情况下,父进程是忽略这些信号的。

程序用例在下一节中。


5 wait and waitpid functions

无论一个进程是正常结束还是异常终止,内核都会发送SIGCHLD信号给其父进程。子进程的终止是一个异步事件,可能发生在父进程运行的任何时候。内核通过信号异步通知父进程,父进程接到该信号后,可以选择忽略和处理。默认情况下是忽略该信号。父进程在信号处理函数中,可以调用以下两个函数之一来释放子进程退出后还保留的信息。

#include <sys/wait.h>

pid_t wait(int* stat_loc);

pid_t waitpid(pid_t pid, int* stat_loc, int options);

wait: 阻塞当前进程,直到有信号来或者子进程结束,如果在调用时子进程已经结束,则wait()会立即返回子进程的结束状态值。子进程的状态值由stat_loc返回。返回值为已结束的子进程ID。

waitpid: 阻塞当前进程,直到有信号来或者子进程结束,子进程状态值由stat_loc返回。

参数pid可选值:

< -1: 等待进程组识别码为pid绝对值的任何子进程

= -1: 等待任何子进程,相当于wait()

= 0 : 等待进程组识别码与当前进程相同的任何子进程

> 0 : 等待指定进程ID的子进程

参数options可选值:

0           :

WCONTINUED  : 如果指定pid从作业控制暂停中继续,则返回。

WNOHANG     : 如果指定Pid的进程没有结束,也不阻塞,立即返回。

WUNTRACED   : 如果子进程进入暂停状态,则马上返回,不理会其结束状态。

对于返回的状态值,可以调用以下宏进行进一步判断(真,非零值):

WIFEXITED   : 如果为正常结束的子进程返回的状态,则为真

WEXITSTATUS : 对于正常结束的子进程,可以取得子进程退出状态的低八位

WIFSIGNALED : 如果是信号结束的子进程状态,则为真

WTERMSIG    : 对于信号结束的子进程,可以取得使子进程结束的信号编码

WIFSTOPPED  : 如果当前子进程是暂停而返回的,则为真

WSTOPSIG    : 对于暂停而返回的子进程,可以取得使子进程暂停的信号编码

WIFCONTINUED: 如果当前子进程是从一个作业控制暂停中继续的,则为真

程序用例1:父进程先退出

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <errno.h>

#include <string.h> // strerror

int main(int argc, char* argv[])

{

pid_t pid = fork();

if(pid < 0)

{

printf("fork failed. error[%d] \t %s \n", errno, strerror(errno));

exit(EXIT_FAILURE);

}

if(pid == 0)

{

printf("child process, pid: %d \t ppid: %d\n", getpid(), getppid());

sleep(5);

printf("child process, pid: %d \t ppid: %d\n", getpid(), getppid());

exit(EXIT_SUCCESS);

}

else

{

printf("father process\n");

printf("father exit\n");

}

exit(EXIT_SUCCESS);

}

输出:

father process

father exit

child process, pid: 10471   ppid: 10470

child process, pid: 10471   ppid: 1

父进程退出后,子进程被托管给了init(process ID为1)

程序用例2:子进程先退出,父进程不处理

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <errno.h>

#include <string.h> // strerror

int main(int argc, char* argv[])

{

pid_t pid = fork();

if(pid < 0)

{

printf("fork failed. error[%d] \t %s \n", errno, strerror(errno));

exit(EXIT_FAILURE);

}

if(pid == 0)

{

printf("child process, pid: %d \t ppid: %d\n", getpid(), getppid());

exit(EXIT_SUCCESS);

}

else

{

printf("father process, pid: %d\n", getpid());

sleep(10); // 方便查看僵尸进程

printf("father exit\n");

}

exit(EXIT_SUCCESS);

}

输出:

程序放后台运行,比如编译出程序为 test8.3, 则执行:./test8.3 &

当运行时,可以输入ps -l

[1] 10727

father process, pid: 10727

child process, pid: 10731   ppid: 10727

[[email protected] Apue]$ ps -l

F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD

0 S  1000 10344  8837  0  80   0 - 29164 wait   pts/1    00:00:00 bash

0 S  1000 10727 10344  0  80   0 -  3122 hrtime pts/1    00:00:00 test8.3

1 Z  1000 10731 10727  0  80   0 -     0 exit   pts/1    00:00:00 test8.3 <defunct>

0 R  1000 10732 10344  0  80   0 - 30315 -      pts/1    00:00:00 ps

father exit

可以发现,子程序退出后,成为了僵尸进程,注意Z符号。

程序用例3:子进程先退出,父进程通过信号处理

clude <stdlib.h>

#include <unistd.h>

#include <errno.h>

#include <string.h> // strerror

#include <signal.h>

#include <sys/wait.h>

// 信号处理函数

static void sig_process(int signo)

{

int status;

pid_t pid = wait(&status);

printf("child[%d] terminated\n", pid);

}

int main(int argc, char* argv[])

{

signal(SIGCHLD, sig_process);

pid_t pid = fork();

if(pid < 0)

{

printf("fork failed. error[%d] \t %s \n", errno, strerror(errno));

exit(EXIT_FAILURE);

}

if(pid == 0)

{

printf("child process, pid: %d \t ppid: %d\n", getpid(), getppid());

exit(EXIT_SUCCESS);

}

else

{

printf("father process, pid: %d\n", getpid());

sleep(10); // 等待子进程退出及ps -l

printf("father exit\n");

}

exit(EXIT_SUCCESS);

}

输出:

[1] 11013

father process, pid: 11013

child process, pid: 11017   ppid: 11013

child[11017] terminated

father exit

[1]+  Done                    ./test8.3

[[email protected] Apue]$ ps -l

F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD

0 S  1000 10344  8837  0  80   0 - 29164 wait   pts/1    00:00:00 bash

0 R  1000 11018 10344  0  80   0 - 30315 -      pts/1    00:00:00 ps

可以看见,没有发现Z开头的僵尸进程了。当然,如果你够快,在子进程结束到父进程处理之前输入ps -l 还是可以发现的。

比如在把父进程退出时间改为20秒,在信号处理函数里边添加一句 sleep(10), 则在子进程退出后10秒内输入ps -l,还是能看见僵尸进程的。


6 waitid function

#include <sys/wait.h>

int waitid(idtype_t idtype, id_t id, siginfo_t* infop, int options);

如同waitpid, 但id意义依赖于idtype。

idtype可选值:

P_PID : id为指定子进程ID

P_PGID: 指定进程组ID,该组下的子进程均符合条件

P_ALL : 任意子进程, id无效

options与waitpid中的参数options相同。

infop结构包含:

si_pid   : process ID

si_uid   : real user ID or 0

si_signo : SIGCHLD

si_status: exit status or signal

si_code  : CLD_EXITED(_exit), CLD_KILLED(killed by signal), CLD_DUMPED(killed by signal, and dump core),

CLD_STOPPED(stopped by signal), CLD_TRAPPED(traced child has trapped), CLD_CONTINUED(child continued by SIGCONT)

程序用例:

将前边代码中的信号处理函数修改如下:

static void sig_process(int signo)

{

sleep(10);

int status;

siginfo_t info;

pid_t pid = waitid(P_ALL, 0, &info, 0);

printf("child process, pid: %d \t ppid: %d\n", getpid(), getppid());

printf("child[%d] terminated\n", pid);

printf("info: si_pid: %d \t si_uid: %d\n", info.si_pid, info.si_uid);

printf("info: si_code: %d\n", info.si_code);

}

然后运行,可以看到结果。

时间: 2024-10-17 20:14:48

《unix环境高级编程》 读书笔记 (7)的相关文章

大型网站技术架构-核心原理与案例分析-阅读笔记4

在第四章案例章节中的淘宝网的架构演化案例分析小节中作者主要分析了淘宝架构的演化,以淘宝网的实例给我们分析介绍了淘宝网的业务发展历程及淘宝网的技术架构演化两个方面,在业务发展中作者写到淘宝的技术是随着淘宝业务一起发展起来的,业务是推动这技术发展的动力,淘宝如今的规模和当初有很明显的变化,在技术架构演化中介绍了架构技术的更新升级,该章节中主要介绍淘宝网的发展的历程,在随着时间的发展不断中网站的架构不断的引用着新的技术,由最初简单的c2c更改过来的网站,放弃了lamp架构转而使用java作为开发平台并

大型网站技术架构-核心原理与案例分析-阅读笔记5

在第四章案例章节中的海量分布式存储系统Doris的高可用架构设计分析的小节中作者主要分析介绍了分布式存储的高可用架构和不同故障情况下的高可用解决两个方面,在两小节前作者给我们介绍了Doris是一个海量分布式KV存储系统,其设计的目的是支持中等规模高可用.可伸缩的Kv存储群.跟主流的NoSQL系统HBase相比,doris具有相似的性能和线性伸缩能力,并具有更好的可用性及更友好的图形用户管理界面.而在分布式存储的高可用架构的小节中作者给我们分析了Doris的整体架构,其系统整体上可分为应用程序服务

大型网站技术架构-核心原理与案例分析-阅读笔记3

在第二章的架构章节中的 随机应变:网站的可拓展架构的篇章中作者介绍了构建网站的可扩展架构.利用分布式队列降低系统的耦合性.利用分布式可复用的业务平台.可拓展的数据结构.利用开放平台建设网站生态圈五个方面,作者在讲述前通过微信的成功发布及其中摇一摇功能的加入的开发的快捷引出来的,其中构建网站的可扩展架构中区分了扩展性和伸缩性的区别,讲到了低耦合性的系统跟容易扩展,并且更容易复用,一个低耦合性的系统也可以让系统更加容易的开发和维护,在如何降低系统的耦合性中,作者主要介绍用分布式消息队列的方法来降低系

大型网站技术架构-核心原理与案例分析-阅读笔记02

在第二章的架构章节中的 瞬时响应:网站的高性能架构的篇章中讲到网站的性能是客观的标准,可以具体的体现在响应时间.吞吐量等技术指标上,同时也是主观的感受.在高性能架构中讲到对于网站性能的测试,性能测试是性能优化的前提和基础,也是性能优化结果的检查和度量标准.在不同的角色响应下网站的性能有不同的标准,也有不同的优化手段.在此基础上作者更深一步的讲解到网站的性能测试,其中又包括了不同视角下的网站性能.性能测试指标.性能测试方法.性能测试报告.性能优化策略五个反面,同时也详细的讲解了这五个方面所具有的内

《大型网站技术架构-核心原理与案例分析》之一: 大型网站架构演化

最近刚刚读完李智慧的<大型网站技术架构-核心原理与案例分析>,对每章重点内容作了一些笔记,以便加深印象及日后查阅. 一.大型网站软件系统的特点 高并发,大流量:需要面对高并发用户,大流量访问. 高可用:系统7X24小时不间断服务. 海量数据:需要存储.管理海量数据,需要使用大量服务器. 用户分布广泛,网络情况复杂:许多大型互联网都是为全球用户提供服务的,用户分布范围广,各地网络情况千差万别. 安全环境恶劣:由于互联网的开放性,使得互联网站更容易受到攻击,大型网站几乎每天都会被黑客攻击. 需求快

大型网站技术架构--核心原理和案例分析 大型网站架构演化(一)

如果把上世纪90年代CERN正式发布web标准和第一个WEB服务的出现当作互联网的开始,那么互联网站的发展之经历了短短20多年的时间.在20多年的时间里,互联网的世界发生了变化,今天,全球有近一半的人口使用互联网,人们的生活因为互联网而产生了巨大的变化.从信息检索到即使通信,从电子购物到文化娱乐,互联网渗透到生活的每一个 角落,而且这种趋势还在蔓延.因为互联网,我们的世界正变得越来越小. 同时我们也看到,在互联网跨越式发展进程中,在电子商务火热的市场背后却是不堪重负的网站架构.某些B2C网站逢促

JAVA读书推荐----《深入分析Java Web技术内幕》--《java多线程编程核心技术》--《大型网站技术架构 核心原理与案例分析》-《Effective Java中文版》

(1)  首先推荐的不是一本书,而是一个博客,也是我们博客园另外一位博友java_my_life. 目前市面上讲解设计模式的书很多,虽然我前面讲了看书是最好的,但是对设计模式感兴趣的朋友们,我推荐的是这个博客.这位博友的设计模式讲得非常非常好,我认为90%的内容都是没有问题且很值得学习的,其讲解设计模式的大体路线是: 1.随便开篇点明该设计模式的定义 2.图文并茂讲解该设计模式中的结构 3.以详细的代码形式写一下该种设计模式的实现 4.补充内容 5.讲解该设计模式的优缺点 对于一个设计模式我们关

《大型网站技术架构核心原理与案例分析》阅读笔记-01

通过阅读该书籍我们能够更加清楚的树立大型网站的的技术发展历程,剖析大型网站技术架构模式,深入的讲述大型互联网架构核心原理,并通过一些典型的技术案例来讲述大型网站开发全景视图,该书籍深入的阐述了各种大型网站面临的各种架构问题及解决方案. 在第一章第一篇大型网站架构演化中了解到与传统企业应用系统相比,大型互联网应用系统具有高并发大流量.高可用性.海量数据.用户分布广泛,网络情况复杂.安全环境恶劣.需求快速变更,发布频繁.渐进式发展等特点:大型网站架构演化发展历程经历了初始阶段的网络架构它的应用程序.

大型网站技术架构-核心原理与案例分析

阿里系的书,也是讲大型网站系统架构的,平常我们总是挂在嘴边的高性能.高可用.易扩展.安全性,这些所谓的系统非功能性指标到底如何实现,书里面讲了这些干货,作为网站架构师或者哪怕是应用系统的架构师,都值得了解,也许不一定都能用上,但是等需要用的那天,你肯定不会迷茫. 1.大型网站架构发展常见历程:应用/数据库分离--->使用缓存--->应用服务器集群--->数据库读写分离--->CDN及反向代理--->使用分布式文件系统和分布式数据库--->NoSQL及搜索引擎--->

【大型网站技术架构 核心原理与案例分析】读书笔记

章节 笔记 1.概述 网站架构模式:分层.分割.分布式.集群.缓存.异步.冗余.自动化.安全. 核心架构要素:性能.可用性.伸缩性.扩展性.安全. 4.高性能 一般重复请求一万次计算总响应时间然后除以一万得到单词响应时间. 测试程序并不是启动多线程然后不停发送请求,而是在两次请求之间加入一个随机等待时间. 吞吐量:每天通过收费站的车辆数目:并发数:正在行驶的车辆数目:响应时间:车速.TPS:每秒事务数:HPS:每秒请求数:QPS:每秒查询数. 性能计数器:System Load(系统负载,最理想