Linux操作进程

前言

这次的主要内容就是进程的实操,主要是进程创建,进程终止,进程等待和进程程序替换,最后我们在手写一个简单的shell

1.进程创建

进程创建就是fork,所以我们就讲一些知识性的就可以了

首先在创建子进程的时候,父进程和子进程都指向同一块区域,而且都是只读的权限,因为因为为可写的权限,肯定就不是指向同一块的,而且先指向同一块,然后有写入的,在分开指向那个写入的,不要一开始就全部分开指向,这样很浪费空间

当我们修改内容之后就会变成这样,首先代码段肯定是只读的,因为原来数据子进程父进程都是只读的,然后你去修改了,但是不会马上报错,而是去判断一下,这里会触发系统错误,然后系统检测判断是什么错误(这个就是缺页中断)如果是这种指向同一块内容的,就会把子进程指向新的一块内容,这块内容是拷贝的,然后在修改,这就是写时拷贝

因为可能是gval++这种情况,这种情况就只能先拷贝,在修改,所以统一先拷贝在修改

2. 进程终止

2.1 错误码

这里增加一点,就是C++语言 makefile用g++,然后加上-std=c++11

main函数的返回值会返回给父进程或者系统(就是类似于bash进程这种)

然后echo $?可以查看上一个程序的返回值

修改一下名字,因为c++嘛



第二个echo就是返回的第一个echo的main函数返回值,因为指令也是C语言写的

一般返回0,表示成功,返回非0就表示错误,而且数字还代表着错误的原因

返回的这个就是错误码

这个就类似与当初的C语言中的错误码errno,对应的函数有perror(传入字符串,然后直接在传入的字符串后面添上错误信息,并一起打印),strerror(传入错误码,根据错误码打印出错误)


errno这个东西会记录上一个错误码

没有错误既就是0

接下来我们打印出所有的错误信息


这样我们就可以看出来了,错误码总共就只有那么130多个

这个错误码和上面的完全对应

但这个就和上面的错误码不对应了

因为上面我们打印的是C语言的错误码,系统的错误码,可以借鉴C语言的,当然也可以自己定错误码

2.2 终止进程

有三个方式

第一个就是return

第二个就是exit

第三个就是_exit

看这个我们就知道了,exit是终止程序的,里面的参数就是错误码,0为正常退出

其实_exit也是这样的,两个的使用没什么区别,稍微有一点区别就是缓冲区的问题



看这个我们就可以看出来了,exit会刷新缓冲区,然后在终止,但是_exit不会刷新缓冲区

这是为什么呢

看这个我们就知道了

exit是C语言的函数,底层会调用_exit系统接口,而_exit就是一个系统接口,这样我们还知道了,缓冲区不是在操作系统里面的,不然_exit也可以刷新缓冲区,所以我们就可以知道了,缓冲区是语言层面的,所以exit才能刷新缓冲区,C语言有C语言的缓冲区,c++也有自己的缓冲区

3. 进程等待

3.1 wait

我们再谈fork

fork如果创建子进程成功的话,那么会给子进程返回0,父进程返回子进程的pid

失败的话,没有子进程,然后返回给父进程-1,然后还会设置错误码


这个程序,子进程会先结束,然后进入僵尸状态等待父进程处理

怎么处理呢,就可以用到wait了

这个就是会在wait这个函数中等待,等待子进程结束,然后处理僵尸,才继续执行后面的状态

其中返回值就是子进程的pid,如果等待错误了就返回-1,然后那个status就是用来接收错误码数据的

这样的话,既可以可看到僵尸状态,然后又可以看到处理

因为处理就是一瞬间的事,如果不休眠10s的话,我们可能看不到僵尸了



看这个我们就知道了wait是可以处理僵尸的,而且子进程不结束,父进程如果运行到了wait那里,就会一直在那里等待,而且这个等待的是所以子进程

3.2 waitpid

接下来讲一下waitpid

这个有三个参数

第一个参数,如果你输入某个子进程的pid,那么就是指定等待某一个子进程,如果为-1的话,就是任意一个子进程

如果waitpid这样写的话,那么就和我们上面写的wait是一个效果

或者这样,因为这里的id就是子进程的


而第二个参数就是用来记录子进程的退出码的

但是我们打印的退出码却不是1,为什么呢,因为这个status其实不止记录了退出码,正常的退出有退出码,但是比如如果访问野指针,就直接异常终止了,这个的话,也是由status记录的,所以说status其实也是一个位图

int总共有32位,,其中

8~15就是记录退出码的

这样我们就可以通过位运算计算出退出码了

这样就可以了,先把status右移8位,然后&上0xFF,就只剩下那退出码的8位了

子进程访问野指针,1/0这种都是程序异常会终止的,怎么异常终止的呢,

有警告先不管

这里可以看出,子进程直接死掉了

这个就直接终止了

这个是怎么终止的呢,其实是系统在运行的过程中遇到了这个,然后产生了一个信号给进程,进程接收到了这个信号,就终止了

信号就是kill的信号

那个野指针就是11号信号

我们这样把子父进程都无限运行下去

这个1/0其实就是运算器溢出了,是8号信号

这个要打出信号,必须是父进程出错了,子进程被kill是不会打出信号的,父进程才会打出信号,因为子进程僵尸了

所以进程结束了

第一return 0----》这个是正常结束,结果正确

第二return 非0-----》结果不正确,有退出码

第三异常终止---》有退出信号

退出信号就在status的0~7位

第8位是一个core dump标志

因为0x7F是0111 1111

看这个,我们用8这个信号杀死,所以就是八号信号

或者我们直接遇到实际异常时也是这个信号

其实这个退出码和信号也是存在task_struct中的

还有就是我们还有其它方法获取退出码,没有必要用位运算,可以用系统提供的宏


这个WIFEXITED(status)是用来看是否正常终止,如果正常终止,则返回真,如果异常终止就是假

WEXITSTATUS(status)就是返回退出码的

3.3 实例

就是像一个vector中插入数据,每插入十个的话,就要创建一个子进程来保存到文件中 ,用这个来记录父进程数据的变化,然后就是子进程不会干扰父进程,就是保存失败也没事






这就可以了

这样也可以删掉那些文件了

一键删除

3.4 阻塞与非阻塞问题

上面这个代码,只能等待子进程结束了,才能继续自己的操作,这个就叫做阻塞

何谓非阻塞呢,就是说,父进程一边运行,一边判断,看等待好了没

但这个逻辑要靠我们实现

waitpid的第三个参数就是阻塞和非阻塞的东西

为0就表示阻塞,为WNOHANG就是非阻塞,这个就会判断那一瞬间

waitpid的返回值,>0就表示等待成功,而且返回的是子进程的pid

==0的话,就表示子进程还没有结束

<0就表示等待失败,比如我们第一个输入的pid根本不存在

这里我们把子进程休眠十秒

然后父进程一直判断,子进程结束没有,没有结束就干自己的事,干完了再来判断,一直这样,当等待成功的时候就可以退出来了

我们再把干自己的事完善一下

4. 进程程序替换

cp是复制一份的意思

底行模式这样写,可以替换

程序替换总共有七个


最后一个是系统调用接口,前面六个的底层实现都是调用了最后一个的

因为底层都是分装为命令行参数表的

4.1 execl


这样就实现程序替换了,一个进程可以执行别人的进程了

第一个参数是我们要执行的路径

后面的参数就是我们要执行写的格式,注意最后一个参数必须是nullptr,因为这些参数相当于是提供的命令行参数,命令行参数列表最后一个必须是nullptr



就这样,我们就可以执行我们自己的可执行程序了

这个是怎么程序替换的呢,其实运行到execl那里,程序就完全替换了

数据和内容都完全替换为execl里面的进程了,这也是写时拷贝

所以说execl后面的程序就不会运行了


虽然程序替换了,但是进程是没有改变的,没有重新创建新的进程,比如说进程的pid就没有变


这样我们就证明了,进程程序替换是不会改变进程的

如果execl替换成功,那么后面的程序就都被替换了,所以返回值也没有用了,所以execl的返回值只有在替换失败的时候才有用,用处就是判断失败了

看这个我们就知道了,替换失败返回的就是-1

这个把数据和代码从磁盘替换到内存的过程就是加载

就这样我们就可以实现子进程和父进程自己干自己不同的事了

4.2 python程序

就这样就运行了

看得出来,其实Python就是一个命令

4.3 脚本


4.4 execv


其实这个就是把原来的第二个参数以及后面的,包装在一个数组中,这个就是命令行参数的表,其实底层也是这样的

巧记

l表示list,所以你传参数的时候就要挨个挨个的像链表一样

v表示vector,就是要打包进去

4.5 execlp

l表示还是分开传

p的意思就是不用写路径,直接写命令就可以了

第一个表示你要运行谁,后面的就是我们写命令行的格式了,所以按理说,第一个和第二个参数是一样的

这个不写路径是怎么实现的呢,这个其实是默认去环境变量PATH去找的命令,所以能找到

4.6 execvp

4.7 execvpe

这个多了一个参数,表示可以传环境变量

当然我们也可以不传环境变量,因为子进程会继承父进程的,就算是程序替换,也是会继承父进程的环境变量的,因为环境变量具有全局属性嘛

,不传就是默认的,但我们如果传了呢


当为p的时候,第一个也可以传路径,这样就不是默认去PATH找了

如果这样传的话,那么子进程的环境变量就完全被传的覆盖了

这样我们就可以使用我们自己定义的环境变量了,因为第一个bash的环境变量也是这样来的

那如果我们不传全新的环境变量,我们要传原来的,然后新增一些怎么搞呢

getenv是获取环境变量,讲过的

putenv就可以新增环境变量了


最后那里就是增加的

当然这个子进程增加的环境变量肯定是不会影响其他进程的,是不会影响它的父进程的,不会影响bash,只会影响自己的子进程

4.8 补充

最后一点,execve是系统接口,其他的都是分装它的,都是C标准库分装的接口,只有execve这个才是真正的系统调用

总结

下一个对应的就是手写简单版shell

相关推荐
秋名山小桃子1 分钟前
Kunlun 2280服务器(ARM)Raid卡磁盘盘符漂移问题解决
运维·服务器
与君共勉121382 分钟前
Nginx 负载均衡的实现
运维·服务器·nginx·负载均衡
岑梓铭8 分钟前
(CentOs系统虚拟机)Standalone模式下安装部署“基于Python编写”的Spark框架
linux·python·spark·centos
努力学习的小廉9 分钟前
深入了解Linux —— make和makefile自动化构建工具
linux·服务器·自动化
MZWeiei12 分钟前
Zookeeper基本命令解析
大数据·linux·运维·服务器·zookeeper
7yewh28 分钟前
嵌入式Linux QT+OpenCV基于人脸识别的考勤系统 项目
linux·开发语言·arm开发·驱动开发·qt·opencv·嵌入式linux
Arenaschi31 分钟前
在Tomcat中部署应用时,如何通过域名访问而不加端口号
运维·服务器
小张认为的测试32 分钟前
Linux性能监控命令_nmon 安装与使用以及生成分析Excel图表
linux·服务器·测试工具·自动化·php·excel·压力测试
waicsdn_haha39 分钟前
Java/JDK下载、安装及环境配置超详细教程【Windows10、macOS和Linux图文详解】
java·运维·服务器·开发语言·windows·后端·jdk
打鱼又晒网40 分钟前
linux网络套接字 | 深度解析守护进程 | 实现tcp服务守护进程化
linux·网络协议·计算机网络·tcp