Linux System Programming --Chapter Five
这一章中的内容出现在博主的多篇文章中,所以并不对这一章进行详细的说明解释,只是对几个比较重要的概念进行说明
一.写时复制技术
COW技术初窥:
在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,linux中引入了“写时复制“技术,也就是只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。
那么子进程的物理空间没有代码,怎么去取指令执行exec系统调用呢?
在fork之后exec之前两个进程用的是相同的物理空间(内存区),子进程的代码段、数据段、堆栈都是指向父进程的物理空间,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个。当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间,如果不是因为exec,内核会给子进程的数据段、堆栈段分配相应的物理空间(至此两者有各自的进程空间,互不影响),而代码段继续共享父进程的物理空间(两者的代码完全相同)。而如果是因为exec,由于两者执行的代码不同,子进程的代码段也会分配单独的物理空间。
还有个细节问题就是,fork之后内核会通过将子进程放在队列的前面,以让子进程先执行,以免父进程执行导致写时复制,而后子进程执行exec系统调用,因无意义的复制而造成效率的下降。
COW详述:
现在有一个父进程P1,这是一个主体,那么它是有灵魂也就身体的。现在在其虚拟地址空间(有相应的数据结构表示)上有:正文段,数据段,堆,栈这四个部分,相应的,内核要为这四个部分分配各自的物理块。即:正文段块,数据段块,堆块,栈块。至于如何分配,这是内核去做的事,在此不详述。
1. 现在P1用fork()函数为进程创建一个子进程P2,
内核:
(1)复制P1的正文段,数据段,堆,栈这四个部分,注意是其内容相同。
(2)为这四个部分分配物理块,P2的:正文段->PI的正文段的物理块,其实就是不为P2分配正文段块,让P2的正文段指向P1的正文段块,数据段->P2自己的数据段块(为其分配对应的块),堆->P2自己的堆块,栈->P2自己的栈块。如下图所示:同左到右大的方向箭头表示复制内容。
2. 写时复制技术:内核只为新生成的子进程创建虚拟空间结构,它们来复制于父进程的虚拟究竟结构,但是不为这些段分配物理内存,它们共享父进程的物理空间,当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间。
3. vfork():这个做法更加火爆,内核连子进程的虚拟地址空间结构也不创建了,直接共享了父进程的虚拟空间,当然了,这种做法就顺水推舟的共享了父进程的物理空间
通过以上的分析,相信大家对进程有个深入的认识,它是怎么一层层体现出自己来的,进程是一个主体,那么它就有灵魂与身体,系统必须为实现它创建相应的实体, 灵魂实体与物理实体。这两者在系统中都有相应的数据结构表示,物理实体更是体现了它的物理意义。以下援引LKD
传统的fork()系统调用直接把所有的资源复制给新创建的进程。这种实现过于简单并且效率低下,因为它拷贝的数据也许并不共享,更糟的情况是,如果新进程打算立即执行一个新的映像,那么所有的拷贝都将前功尽弃。Linux的fork()使用写时拷贝(copy-on-write)页实现。写时拷贝是一种可以推迟甚至免除拷贝数据的技术。内核此时并不复制整个进程地址空间,而是让父进程和子进程共享同一个拷贝。只有在需要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝。也就是说,资源的复制只有在需要写入的时候才进行,在此之前,只是以只读方式共享。这种技术使地址空间上的页的拷贝被推迟到实际发生写入的时候。在页根本不会被写入的情况下—举例来说,fork()后立即调用exec()—它们就无需复制了。fork()的实际开销就是复制父进程的页表以及给子进程创建惟一的进程描述符。在一般情况下,进程创建后都会马上运行一个可执行的文件,这种优化可以避免拷贝大量根本就不会被使用的数据(地址空间里常常包含数十兆的数据)。由于Unix强调进程快速执行的能力,所以这个优化是很重要的。这里补充一点:Linux COW与exec没有必然联系
二.exit()和 _exit()的不同
#include<stdlib.h>
void exit(int status);
不像fork那么难理解,从exit的名字就能看出,这个系统调用是用来终止一个进程的。无论在程序中的什么位置,只要执行到exit系统调用,进程就会停止剩下的所有操作,清除包括PCB在内的各种数据结构,并终止本进程的运行。请看下面的程序:
/* exit_test1.c */
#include<stdlib.h>
main() www.2cto.com
{
printf("this process will exit!\n");
exit(0);
printf("never be displayed!\n");
}
编译后运行:
$gcc exit_test1.c -o exit_test1
$./exit_test1
this process will exit!
#include<unistd.h>
void _exit(int status);
/* exit2.c */
#include<stdlib.h>
main()
{
printf("output begin\n");
printf("content in buffer");
exit(0);
}
编译并运行:
$gcc exit2.c -o exit2
$./exit2
output begin
content in buffer
/* _exit1.c */
#include<unistd.h>
main() www.2cto.com
{
printf("output begin\n");
printf("content in buffer");
_exit(0);
}
编译并运行:
$gcc _exit1.c -o _exit1
$./_exit1
output begin
三.守护进程
1、概念
守护进程是运行在后台,并且一直在运行的一种特殊进程。它独立于控制终端并且周期性的执行某种任务或等待处理某些事。
Linux的大多数服务器都是用守护进程实现的。
2、C实现Linux守护进程步骤
3、案例
守护进程每10秒钟向/tmp/log.txt中写入日至。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <signal.h>
#include <sys/param.h>
#include <sys/stat.h>void init_daemon(void);int main(void){FILE *fp;time_t t;init_daemon();while(1){sleep(10);if( (fp=fopen("log.txt", "a+")) >= 0){//打开log文件,若没有此文件则创建t=time(0);fprintf(fp, "守护进程正在运行,时间是:%s", asctime(localtime(&t)));fclose(fp);}}return 1;
}void init_daemon(void){pid_t pid;int i;pid = fork();if(pid > 0){ //终止父进程exit(0);}else if(pid < 0 ){perror("创建子进程失败\n");exit(1);}else if(pid == 0){setsid();//子进程中创建新会话chdir("/tmp");//改变工作目录到tmpumask(0); //重设文件掩码for(i=0; i<NOFILE;++i){//关闭文件描述符close(i);}return ;}
}
pid_t setsid(void);
设置新的组进程号
成功返回进程组号GID,失败返回-1,原因存于errno。
案例2
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/wait.h>#include <sys/types.h>#include <fcntl.h>int main() {pid_t pid;int i,fd;char *buf="This is a daemon program.\n";if ((pid=fork())<0) {printf("fork error!");exit(1);}else if (pid>0) // fork且退出父进程exit(0);setsid(); // 在子进程中创建新会话。chdir("/"); // 设置工作目录为根umask(0); // 设置权限掩码for(i=0;i<getdtablesize();i++) //getdtablesize返回子进程文件描述符表的项数close(i); // 关闭这些不将用到的文件描述符while(1) {// 死循环表征它将一直运行// 以读写方式打开"/tmp/daemon.log",返回的文件描述符赋给fdif ((fd=open("/tmp/daemon.log",O_CREAT|O_WRONLY|O_APPEND,0600))<0) {printf("Open file error!\n");exit(1);}// 将buf写到fd中write(fd,buf,strlen(buf)+1);close(fd);sleep(10);printf("Never output!\n");}return 0;}
因为stdout被关掉了,所以“Never ouput!”不会输出。
查看/tmp/daemon.log,说明该程序一直在运行
四.进程组,会话
eg:显示子进程与父进程的进程组id
#include <stdio.h>#include <stdlib.h>#include <unistd.h>int main() {pid_t pid;if ((pid=fork())<0) {printf("fork error!");}else if (pid==0) {printf("The child process PID is %d.\n",getpid());printf("The Group ID is %d.\n",getpgrp());printf("The Group ID is %d.\n",getpgid(0));printf("The Group ID is %d.\n",getpgid(getpid()));exit(0);}sleep(3);printf("The parent process PID is %d.\n",getpid());printf("The Group ID is %d.\n",getpgrp());return 0;}
进程组id = 父进程id,即父进程为组长进程
eg:父进程改变自身和子进程的组id
#include <stdio.h>#include <stdlib.h>#include <unistd.h>int main() {pid_t pid;if ((pid=fork())<0) {printf("fork error!");exit(1);}else if (pid==0) {printf("The child process PID is %d.\n",getpid());printf("The Group ID of child is %d.\n",getpgid(0)); // 返回组idsleep(5);printf("The Group ID of child is changed to %d.\n",getpgid(0));exit(0);}sleep(1);setpgid(pid,pid); // 改变子进程的组id为子进程本身sleep(5);printf("The parent process PID is %d.\n",getpid());printf("The parent of parent process PID is %d.\n",getppid());printf("The Group ID of parent is %d.\n",getpgid(0));setpgid(getpid(),getppid()); // 改变父进程的组id为父进程的父进程printf("The Group ID of parent is changed to %d.\n",getpgid(0));return 0;}
#include <stdio.h>#include <stdlib.h>#include <unistd.h>int main() {pid_t pid;if ((pid=fork())<0) {printf("fork error!");exit(1);}else if (pid==0) {printf("The child process PID is %d.\n",getpid());printf("The Group ID of child is %d.\n",getpgid(0));printf("The Session ID of child is %d.\n",getsid(0));sleep(10);setsid(); // 子进程非组长进程,故其成为新会话首进程,且成为组长进程。该进程组id即为会话进程printf("Changed:\n");printf("The child process PID is %d.\n",getpid());printf("The Group ID of child is %d.\n",getpgid(0));printf("The Session ID of child is %d.\n",getsid(0));sleep(20);exit(0);}return 0;}
在子进程中调用setsid()后,子进程成为新会话首进程,且成为一个组长进程,其进程组id等于会话id
Linux System Programming --Chapter Five相关推荐
- Linux System Programming --Chapter Nine
这一章的标题是 "信号" ,所以本文将对信号的各个方面进行介绍,由于Linux中的信号机制远比想象的要复杂,所以,本文不会讲的很全面... 信号机制是进程之间相互传递消息的一种方法 ...
- Linux System Programming --Chapter Two
首先,需要解释的东西是Linux系统调用 一. 什么是系统调用 在Linux的世界里,我们经常会遇到系统调用这一术语,所谓系统调用,就是内核提供的.功能十分强大的一系列的函数.这些系统调用是 ...
- Linux System Programming --Chapter Eight
内存管理 一.分配动态内存的几个函数 用户空间内存分配:malloc.calloc.realloc 1.malloc原型如下: extern void *malloc(unsigned int num ...
- Linux System Programming --Chapter Seven
文件和目录管理 一.文件与其元数据 我们首先看一下一个简单的文本文件是怎么保存的: 打开vim,编辑一段文本: [root@localhost ~]# vim hello.txt 编辑内容如下: op ...
- Linux System Programming --Chapter Six
这一章的题目是--高级进程管理,这篇文章将以书中所叙的顺序进行讲解 1.让出处理器 Linux提供一个系统调用运行进程主动让出执行权:sched_yield.进程运行的好好的,为什么需要这个函数呢?有 ...
- Linux System Programming --Chapter Four
这一章介绍的主题是--高级文件 I/O 一. 分散--聚集I/O 分散聚集I/O是一种进行输入和输出的方法.通过此方法,单一系统调用可以将缓冲区向量写入单一数据流,或者将单一数据流读取到缓冲区向量.这 ...
- Linux System Programming --Chapter Three
这一章的主题是--缓冲式I/O 一,流与缓冲 流I/O是由C语言的标准函数提供的,这些I/O可以替代系统中提供的read和write函数.事实上流I/O的内 部封装了这两个基本的文件读写 ...
- 书评:Linux System Programming
原文地址:http://books.slashdot.org/article.pl?no_d2=1&sid=08/04/14/1415215 作者:Jon Mitchell "本文略 ...
- Linux System Programming -- Appendix
这本书附录的名字是 "GCC对C语言的扩展" ,一下的内容是对扩展的总结 类型发现 GCC 允许通过变量的引用识别类型.这种操作支持泛型编程.在 C++.Ada 和 Java™ 语 ...
最新文章
- Makefile (2) gdb
- 解决 IntelliJ IDEA 安装后界面消失,再次打开后界面不动
- 【Pytorch神经网络实战案例】03 CIFAR-10数据集:Pytorch使用GPU训练CNN模版-测试方法
- 局域网共享设置——权限问题
- ORACLE 常用函数——日期/时间函数
- mapxtreme java_MapXtreme Java
- SVG 与 Canvas:如何选择
- 吉吉影音官网版 v2.7.2.8_p2p播放器​
- lintcode--报数
- CRT、CER、PEM、DER编码、JKS、KeyStore等格式证书说明及转换
- Cesium之【空间面积】测量
- C2 - Skyscrapers (hard version),Codeforces Round #622 (Div. 2),单调栈
- 关于74HC573使用
- LigerUI初学篇---使用LigerUI制作简洁清爽的界面
- 在报表开发工具Stimulsoft Report数据透视表的新功能介绍
- Python入门学习之个人税务计算练习(if)
- 新静安二手房房价依旧上涨
- 宗镜录略讲——南怀瑾老师——系列4
- 电磁波传播相位是否会变化_浅谈电磁波相位
- 《惢客创业日记》2019.07.02(周二)被误解的花心大叔
热门文章
- XenApp/XenDesktop 7.11中对于视频、图片、文字的优化的说明
- Linux环境下进入MySQL环境报权限问题:Access denied for user 'root@localhost' (using password:YSE)...
- 最新 react 开发框架(webpack2、react15、react-router4、antd-mobile)
- iOS之NSString类中compare方法的陷阱
- beanstalkd消息队列在生产环境的应用
- 凌轩:中国电信在校园市场的困与囧
- hibernate连接池配置
- 验证apk签名方式(V1 || V2)
- 51nod 1270 数组的最大代价
- Java程序员面试中的多线程问题1