linux系统编程之进程(二)
今天继续学习进程相关的东东,上节提到了,当fork()之后,子进程复制了父进程当中的大部分数据,其中对于打开的文件,如果父进程打开了,子进程则不需要打开了,是共享的,所以首先先来研究下共享文件这一块的东东:
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> #include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h> #define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while() int main(int argc, char *argv[])
{
signal(SIGCHLD, SIG_IGN);
printf("before fork pid = %d\n", getpid());
int fd;
fd = open("test.txt", O_WRONLY);//父进程打开一个文件
if (fd == -)
ERR_EXIT("open error"); pid_t pid;
pid = fork();
if (pid == -)
ERR_EXIT("fork error"); if (pid > )
{
printf("this is parent pid=%d childpid=%d\n", getpid(), pid);
write(fd, "parent", );//父进程往文件中写入内容
sleep();//睡眠是为了避免孤儿进程的产生,保证子进程执行的时候,父进程没有退出
}
else if (pid == )
{
printf("this is child pid=%d parentpid=%d\n", getpid(), getppid());
write(fd, "child", );//子进程往文件中写入内容
}
return ;
}
先创建一个"test.txt",里面是空内容:
也就是可以说明,子进程是共享父进程打开的文件表项的。
注意:有时候可能会test.txt的内容输出如下:
上面这种输出并没有按照我们的预想,可能的原因是跟两个进程的静态问题造成的,这个问题比较复杂,可以这样理解:也就是还没等子进程执行,父进程就已经结束了,这时子进程的文件偏移量会从0开始,所以之前父进程写入了parent,由于它退出来,子进程从0的位置开始写,所以最终输出就如上图所示了,为了保证如我们预期来输出,可以将睡眠时间加长上些,保证子进程执行时,父进程没有退出,如下:
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> #include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h> #define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while() int gval = ; int main(int argc, char *argv[])
{
signal(SIGCHLD, SIG_IGN);
printf("before fork pid = %d\n", getpid()); pid_t pid;
pid = fork();//这里是用的copy on write机制
if (pid == -)
ERR_EXIT("fork error"); if (pid > )
{
sleep();//它的目的是为了让子进程先对gval进行++操作,以便观察父进程是否会受影响
printf("this is parent pid=%d childpid=%d gval=%d\n", getpid(), pid, gval);
sleep();
}
else if (pid == )
{
gval++;//子进程来改写数据
printf("this is child pid=%d parentpid=%d gval=%d\n", getpid(), getppid(), gval);
}
return ;
}
编译运行:
其原因也就是由于fork()是采用copy on write的机制,下面用图来解析一下上面的结果:
下面将其改为vfork来实现:
编译运行:
这个输出结果,可以表明,vfork产生子进程,当改写数据时也不会拷贝父进程的空间的,父子是共享一份空间,所以当子进程改写的数据会反映到父进程上。
另外这段程序中出现了一个“段错误”,这是因为:
这时,编译再运行,就不会有错误了:
另外执行exec函数也一样,关于它的使用,之后再来介绍。
提示:vfork是一个历史问题,了解一下既可,实际中很少用它!
在演示vfork时,提到“子进程必须立刻执行_exit”,那如果用exit(0)退呢?
编译运行:
我们通常会将return 0 与exit(0)划等号,但如果在vfork()中,还是划等号么?
编译运行:
那exit与_exit有啥区别呢?下面来探讨下,在探讨之前,先来回顾一下进程的五种终止方式:
下面以一个图来说明exit与_exit的区别:
区别一:exit是C库中的一个函数;而_exit是系统调用。
区别二:exit在调用内核之前,做了“调用终止处理程序、清除I/O缓冲”;而_exit是直接操作内核,不会做这两件事。
下面就以具体代码来说明两者的区别:
编译运行:
exit(0)相当于return 0;所以可想将上面return 0换为exit(0)也是一样的能在屏幕上打印出来,那如果换成_exit(0)呢?
这时编译运行:
这时就正常显示了:
另外对于exit来说,它会调用“终止处理程序”,所谓“终止处理程序”,就是指在程序结束的时候会调用的函数代码段,这些代码段,需要我们安装才可以,可以用如下函数:
其中传递的参数是函数指针。
下面用具体代码来进行说明:
编译运行:
如果换成是_exit()呢?
编译运行:
插一句:对于fork函数,有一个问题需进一步阐述一下,以便加深对它的理解:
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> #include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h> #define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while() int main(int argc, char *argv[])
{
signal(SIGCHLD, SIG_IGN);
printf("before fork pid = %d\n", getpid()); pid_t pid;
pid = fork();
if (pid == -)
ERR_EXIT("fork error"); if (pid > )
{
printf("this is parent pid=%d childpid=%d\n", getpid(), pid);
sleep();
}
else if (pid == )
{
printf("this is child pid=%d parentpid=%d\n", getpid(), getppid());
}
return ;
}
输出:
对于上面这段程序,就是上节中学习过的,但是有个问题值得思考一下,为啥fork()之后,不是从"before fork"从main的第一行起输出,而是从fork()之后的代码中去输出,这时因为fork()之后,拷贝了“代码段+数据段+堆栈段+PCB”,也就是两个进程的信息几乎都是一样,而由于堆栈段+PCB几乎是一样的,所以它会维护当前运行的信息,所以每个进程会从fork()之后的代码继续执行,这一点需要理解。
另外,再看一个跟fork()相关的程序:
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> #include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h> #define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while() int main(int argc, char *argv[])
{
fork();
fork();
fork();
printf("ok\n");
return ;
}
这段程序会打印多少行呢?
编译运行:
这是为什么呢?因为第一个fork()时,会产生两个进程,这时这两个进程都会执行它后面的代码,也就是第二个fork(),这时就有四个进程执行第二个fork()了,同样的,这时四个进程就会执行它下面的代码,也就是第三个fork(),这时就再产生四个进程,总共也就是八个进程了,这个比较不好理解,好好想一下!
最后,我们来说明一下execve函数,这个在上面介绍vfork()函数时,已经提到过了,它的作用是:替换进程映像,这时对它进行使用说明:
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> #include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h> #define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while() int gval = ; int main(int argc, char *argv[])
{
signal(SIGCHLD, SIG_IGN);
printf("before fork pid = %d\n", getpid()); pid_t pid;
pid = vfork();
if (pid == -)
ERR_EXIT("fork error"); if (pid > )
{
printf("this is parent pid=%d childpid=%d gval=%d\n", getpid(), pid, gval);
}
else if (pid == )
{
char *const args[] = {"ps", NULL}; execve("/bin/ps", args, NULL);//将子进程完全替换成/bin/ps中的ps进程命令,所以这句话之后的代码就不会执行了,因为是完全被替换了
gval++;
printf("this is child pid=%d parentpid=%d gval=%d\n", getpid(), getppid(), gval);
}
return ;
}
编译运行:
下节会对execve函数更加复杂的用法进行学习,下节见喽!
linux系统编程之进程(二)的更多相关文章
- linux系统编程之进程(一)
今天起,开始学习linux系统编程中的另一个新的知识点----进程,在学习进程之前,有很多关于进程的概念需要了解,但是,概念是很枯燥的,也是让人很容易迷糊的,所以,先抛开这些抽象的概念,以实际编码来熟 ...
- linux系统编程之进程(二):进程生命周期与PCB(进程控制块)
本节目标: 进程状态变迁 进程控制块 进程创建 进程撤消 终止进程的五种方法 一,进程状态变迁 进程的三种基本状态 就绪(Ready)状态 当进程已分配到除CPU以外的所有必要的资源,只要获得处理机便 ...
- linux系统编程之进程(六):父进程查询子进程的退出,wait,waitpid
本节目标: 僵进程 SIGCHLD wait waitpid 一,僵尸进程 当一个子进程先于父进程结束运行时,它与其父进程之间的关联还会保持到父进程也正常地结束运行,或者父进程调用了wait才告终止. ...
- linux系统编程之进程(八):守护进程详解及创建,daemon()使用
一,守护进程概述 Linux Daemon(守护进程)是运行在后台的一种特殊进程.它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件.它不需要用户输入就能运行而且提供某种服务,不是对整个 ...
- Linux系统编程——Daemon进程
目录 Daemon进程介绍 前提知识 Daemon进程的编程规则 Daemon进程介绍 Daemon运行在后台也称作"后台服务进程". 它是没有控制终端与之相连的进程.它独立与控制 ...
- linux系统编程之进程(三):进程复制fork,孤儿进程,僵尸进程
本节目标: 复制进程映像 fork系统调用 孤儿进程.僵尸进程 写时复制 一,进程复制(或产生) 使用fork函数得到的子进程从父进程的继承了整个进程的地址空间,包括:进程上下文.进程堆栈. ...
- linux系统编程之进程(五)
今天继续学习系统编程,学习的主题还是进程,今天主要讨论的是守护进程相关的概念,开始进入正题: 什么是守护进程: 守护进程的创建步骤: 在描述它之前,首先得先了解两个概念:进程组.会话期: ...
- linux系统编程--守护进程,会话,进程组,终端
终端: 在UNIX系统中,用户通过终端登录系统后得到一个Shell进程,这个终端成为Shell进程的控制终端(Controlling Terminal), 进程中,控制终端是保存在PCB中的信息,而f ...
- Linux系统编程之进程概念
注:本文部分图片来源于网络,如有侵权,请告知删除 1. 什么是进程? 在了解进程概念之前,我们需要先知道程序的概念. 程序,是指编译好的二进制文件,这些文件在磁盘上,并不占用系统资源. 进程,指的是一 ...
随机推荐
- 扩展Redis的Jedis客户端,哨兵模式读请求走Slave集群
原 扩展Redis的Jedis客户端,哨兵模式读请求走Slave集群 2018年12月06日 14:26:45 温故而知新666 阅读数 897 版权声明:本文为博主原创文章,遵循CC 4.0 b ...
- nginx+consul-template+consul实现自动负载均衡
所需工具:工具 下载地址 本文使用版本consul https://www.consul.io/downloads.html consul_1.0.7_linux_amd64.zipconsul-te ...
- hihocoder 1566 皇室成员的名字
#1566 : 皇室成员的名字 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Ho正在学习世界历史.他发现历史上很多西方国家的皇室成员的名字都是由英文名字加罗马数字组 ...
- C# 编译器 和 反编译器,你要哪个(歪头)? 我全都要(捏拳)!
前言 从 C# 6.0 开始,C# 编译器就从以前由 C++ 实现的 csc.exe 换成了用 C# 重新实现的开放式 API 式编译服务 Roslyn.这个编译器到现在已经替代了老式编译器,从前 W ...
- Qt去掉treeview项的焦点虚线
项目做到后期,进行局部美化的时候发现了问题,在treeview框选择状态下会有虚线. 其实,不仅是treeview,tableview,listview,乃至button在有焦点的情况下,都会出现虚线 ...
- Java开发笔记(一百三十一)Swing的列表框
前面介绍了选择框的用法,当时为了方便用户勾勾点点,无论是复选框还是单选按钮,统统把所有选项都摆在界面上.倘若只有两三个选项还好办,要是选项数量变多比如超过五个,这么多的选择框一齐在界面罗列,不光程序员 ...
- spring boot + vue实现图片上传及展示
转载:https://blog.csdn.net/weixin_40337982/article/details/84031778 其中一部分对我很有帮助 转载记录下 首先,html页面: <! ...
- shell 学习笔记9-while/until循环语句
一.while循环语句 1.循环语句 循环愈久就是重复执行一条指令或一组执行,知道条件不在满足时停止,shell循环语句包括,while.until.for.select语句 2.while循环 主要 ...
- Spring Spring boot 获取IOC中的bean,ApplicationContext
https://blog.csdn.net/weixin_38361347/article/details/89304414 https://www.jianshu.com/p/9ea13b00b1d ...
- DevExtreme学习笔记(一)treeView(搜索固定、节点展开和收缩)注意事项
var treeConfig1 = dxConfig.treeView(obj_Question.treeDataSource1); treeConfig1.selectionMode = 'sing ...