文章内容
1. 查看进程
1.1 什么是进程
进程:运行中的程序
虚拟存储器/虚拟地址空间
gdb调试功能
步骤 | 操作 | 意义 |
---|---|---|
1 | g++ test.cpp -g | 编译时加 -g |
2 | gdb ./a.out | 开始调试 |
3 | list | 查看断点 |
4 | b 5 | 断点加在第5行 |
5 | info proc mapping | 查看内存区域的映射 |
6 | n | next走到下一步 |
7 | p handle | 查看handle的地址 |
8 | q | 退出 |
进程状态
就绪:操作系统把程序加载号等待处理
阻塞:等待某一个时间完成后再就绪,如同scanf和cin的等待输入
常用命令
ps 查看终端进程
ps -aux 查看所有进程 或者 ps -ef
其中最后一行 R+ 为快照,表示运行中
top 实时的进程显示
pstree 进程树形图,使用yum install psmisc
安装
1.2 进程的操作
死循环执行进程
我们先写一个死循环,查看进程信息
#include <iostream>
using namespace std;
int main(){
int i = 0;
for(;;){
++i;
}
}
在 ./a.out 执行后 :
ctrl Z 打入后台暂停
bg 调到前台继续执行但不占用shell, ./a.out&
执行时直接在前台不占用shell
fg 调到前台执行
ps -C a.out 查看对应进程名的信息
ps -p 6264 查看pid多对应进程
pidof a.out 查看对应进程pid
kill 6264 杀掉对应进程
[root@foundation1 C++7.10]# ./a.out
^Z
[1]+ Stopped ./a.out
[root@foundation1 C++7.10]# bg
[1]+ ./a.out &
[root@foundation1 C++7.10]# ps -C a.out
PID TTY TIME CMD
3340 pts/0 00:01:06 a.out
[root@foundation1 C++7.10]# ps -p 3340
PID TTY TIME CMD
3340 pts/0 00:01:22 a.out
[root@foundation1 C++7.10]# pidof a.out
3340
[root@foundation1 C++7.10]# kill 3340
[1]+ Terminated ./a.out
输入/堵塞状态执行进程
我们先写一个输入,查看进程信息
#include <iostream>
using namespace std;
int main(){
int i = 0;
cin >> i;
cout << i;
}
执行过程为:
[root@foundation1 C++7.10]# ./a.out
^Z
[1]+ Stopped ./a.out
[root@foundation1 C++7.10]# bg
[1]+ ./a.out &
[1]+ Stopped ./a.out
[root@foundation1 C++7.10]# fg
./a.out
123
123
进程状态标识:
标识 | 含义 |
---|---|
D | 不可中断 |
R | 正在运行,或在队列中的进程 |
S | 处于休眠状态 |
T | 停止或被追踪 |
Z | 僵尸进程 |
W | 进入内存交换 |
X | 死掉的进程 |
< | 高优先级 |
n | 低优先级 |
jobs 查看后台所有进程 或者 task
其中 fg 2 可以把后台第二条放到前台运行
[root@foundation1 C++7.10]# jobs
[1] Stopped ./a.out
[2]- Stopped ./a.out
[3]+ Stopped ./a.out
[root@foundation1 C++7.10]# fg 2
./a.out
123
123
[root@foundation1 C++7.10]# jobs
[1]- Stopped ./a.out
[3]+ Stopped ./a.out
[root@foundation1 C++7.10]# ps
PID TTY TIME CMD
2992 pts/0 00:00:00 bash
4496 pts/0 00:00:00 a.out
4510 pts/0 00:00:00 a.out
4573 pts/0 00:00:00 ps
[root@foundation1 C++7.10]# pkill -9 a.out
[1]- Killed ./a.out
[3]+ Killed ./a.out
pkill -9 a.out 强制全部删除
或者 kill -9 $(pidof a.out)
1.3 进程操作接口
getpid()
获取当前进程pid
getppid()
获取父进程pid
#include <iostream>
#include <unistd.h> // 进程函数头文件
using namespace std;
int main(){
cout << getpid() << endl; // 获取当前进程pid
cout << getppid() << endl; // 获取父进程pid
int i = 0;
cin >> i;
cout << i << endl;
}
操作过程为:
[root@foundation1 C++7.10]# ./a.out
4140
2992
^Z
[1]+ Stopped ./a.out
[root@foundation1 C++7.10]# ps
PID TTY TIME CMD
2992 pts/0 00:00:00 bash
4140 pts/0 00:00:00 a.out
4149 pts/0 00:00:00 ps
2. 创建进程
2.1 分叉函数 fork()
fork 函数的作用
当程序中加入 fork() 函数
#include <iostream>
#include <unistd.h>
using namespace std;
int main(){
cout << getpid() << endl;
cout << getppid() << endl;
// 5115
fork(); // 分叉函数,调用函数时执行
// 5116
int i = 0;
cin >> i;
cout << i << endl;
}
./a.out 后 ctrl+z
ps 会发现有两个进程,两个进程是父子关系
因为调用函数后开始分叉,出现子进程
[root@foundation1 C++7.10]# ./a.out
5115
2992
^Z
[1]+ Stopped ./a.out
[root@foundation1 C++7.10]# ps
PID TTY TIME CMD
2992 pts/0 00:00:00 bash
5115 pts/0 00:00:00 a.out
5116 pts/0 00:00:00 a.out
5133 pts/0 00:00:00 ps
ps -ef 会发现 fork() 后创建了一个子进程
从程序看 fork 函数的作用
我们编写一个程序
#include <iostream>
#include <unistd.h>
using namespace std;
int main(){
cout << getpid() << endl;
fork();
cout << getpid() << " Hello World" << endl;
}
结果为:
5516
5516 Hello World
5517 Hello World
会发现fork函数会分成两个进程,然后分别执行代码
fork 函数的返回值
我们打印一下返回值
#include <iostream>
#include <unistd.h>
using namespace std;
int main(){
cout << getpid() << endl;
pid_t res = fork(); // fork的返回值给res
cout << getpid() << " Hello World:" << res << endl;
}
结果为:
5718
5718 Hello World:5719
5719 Hello World:0
会发现:
父进程的返回值是子进程的pid
子进程的返回值是0
另一个角度也能看出来
#include <iostream>
#include <unistd.h>
using namespace std;
int main(){
cout << getpid() << endl;
pid_t res = fork();
if(res == 0){
cout << "this is child:" << getpid() << endl;
}else{
cout << "this is parent:" << getpid() << endl;
}
}
结果为:
6016
this is parent:6016
this is child:6017
用途:可以让父子两个进程分别执行对应的事
fork 函数的用途
我们让两个进程分别显示pid和地址,分别进行自加自减的操作
#include <iostream>
#include <unistd.h>
using namespace std;
int main(){
/* static */int n = 0;
cout << getpid() << endl;
pid_t res = fork();
if(res == 0){
for(int i=0;i<20;++i)
cout << "this is child\t" << getpid() << "\t" << &n << "\t" << ++n << endl;
}else{
for(int i=0;i<20;++i)
cout << "this is parent\t" << getpid() << "\t" << &n << "\t" << --n << endl;
}
}
结果为:
5337
this is parent 5337 0x7ffd4d403530 -1
this is parent 5337 0x7ffd4d403530 -2
this is parent 5337 0x7ffd4d403530 -3
this is parent 5337 0x7ffd4d403530 -4
this is parent 5337 0x7ffd4d403530 -5
this is parent 5337 0x7ffd4d403530 -6
this is parent 5337 0x7ffd4d403530 -7
this is parent 5337 0x7ffd4d403530 -8
this is parent 5337 0x7ffd4d403530 -9
this is parent 5337 0x7ffd4d403530 -10
this is parent 5337 0x7ffd4d403530 -11
this is parent 5337 0x7ffd4d403530 -12
this is child 5338 0x7ffd4d403530 1
this is parent 5337 0x7ffd4d403530 -13
this is parent 5337 0x7ffd4d403530 -14
this is child 5338 0x7ffd4d403530 2
this is parent 5337 0x7ffd4d403530 -15
this is child 5338 0x7ffd4d403530 3
this is parent 5337 0x7ffd4d403530 -16
this is child 5338 0x7ffd4d403530 4
this is parent 5337 0x7ffd4d403530 -17
this is child 5338 0x7ffd4d403530 5
this is parent 5337 0x7ffd4d403530 -18
this is child 5338 0x7ffd4d403530 6
this is parent 5337 0x7ffd4d403530 -19
this is child 5338 0x7ffd4d403530 7
this is parent 5337 0x7ffd4d403530 -20
this is child 5338 0x7ffd4d403530 8
this is child 5338 0x7ffd4d403530 9
this is child 5338 0x7ffd4d403530 10
this is child 5338 0x7ffd4d403530 11
this is child 5338 0x7ffd4d403530 12
this is child 5338 0x7ffd4d403530 13
this is child 5338 0x7ffd4d403530 14
this is child 5338 0x7ffd4d403530 15
this is child 5338 0x7ffd4d403530 16
this is child 5338 0x7ffd4d403530 17
this is child 5338 0x7ffd4d403530 18
this is child 5338 0x7ffd4d403530 19
this is child 5338 0x7ffd4d403530 20
我们发现:
- 并发执行:
两个进程同时跑,互不干扰 - 相同但是独立的地址空间:
两个进程的 n 地址都是一样的,但是数据会自己跑自己的
因为系统会把虚拟内存拷贝一份出来,两个进程都有自己的堆和栈 - 使用 static 静态变量,地址会短一些
因为静态变量属于数据区域,地址比较小,而栈的地址较大
g++ -E fork.cpp | grep pid_t 可以查看 pid_t 类型
共享文件
通过打开文件的方式,把两个进程的输出都放到文件中
#include <iostream>
#include <unistd.h>
#include <fstream>
using namespace std;
int main(){
/* static */int n = 0;
cout << getpid() << endl;
ofstream of("./test"/*,ios::out*/); // 打开文件
pid_t res = fork();
if(res == 0){
for(int i=0;i<20;++i)
of << "this is child\t" << getpid() << "\t" << &n << "\t" << ++n << endl; // of 输出到文件
}else{
for(int i=0;i<20;++i)
of << "this is parent\t" << getpid() << "\t" << &n << "\t" << --n << endl;
}
}
结果为:
6089
[root@foundation1 C++7.10]# cat test
this is parent 6089 0x7ffca2419790 -1
this is parent 6089 0x7ffca2419790 -2
this is parent 6089 0x7ffca2419790 -3
this is parent 6089 0x7ffca2419790 -4
this is parent 6089 0x7ffca2419790 -5
// ...... 此处省略,和上面结果类似
并发和并行
概念 | 状态 | 硬件 | 特点 |
---|---|---|---|
并发(concurrency) | 两个或者多个进程同时存在 | 单核 | 进程指令同时或交错执行 |
并行(parallellism) | 两个或者多个进程同时执行 | 多核 | 一起执行 |
2.2 执行函数 exec()
该函数可以把文件中的程序当成执行程序的命令使用
exec函数有哪些:
字符串数组参数: execv() 、 execvp() 、 execve()
可变参数: execle() 、 execlp() 、 execl()
exec函数组名字规律:
v 表示 第二个参数是数组
l 表示 第二个参数是变参
p 表示 第一个参数是文件名
e 表示 最后一个参数是环境变量
exec 的用法
execl()
#include <unistd.h>
int main(){
// g++ exec.cpp -o exec
execl("/bin/g++","g++","exec.cpp","-o","exet",0); // c++路径,编译的操作,0结束
return 0;
}
编译执行后,文件夹中会出现 exet 执行文件
execv()
#include <unistd.h>
int main(){
char* cmd[] = {"g++","exec.cpp","-o","exec2",NULL};
execv("/bin/g++",cmd); // c++路径,数组
return 0;
}
编译执行后,文件夹中会出现 exet2 执行文件
execv()
#include <unistd.h>
int main(){
char* cmd[] = {"g++","exec.cpp","-o","exec3",NULL};
execvp("g++",cmd); // 不用希望
return 0;
}
编译执行后,文件夹中会出现 exet3 执行文件
execve()
#include <unistd.h>
extern char ** environ;
int main(){
char* cmd[] = {"g++","exec.cpp","-o","exec4",NULL};
execve("/bin/g++",cmd,environ); // 路径,数组,环境变量
return 0;
}
编译执行后,文件夹中会出现 exet4 执行文件
exec 的原理
用 execl 编一下查看进程命令
#include <iostream>
#include <unistd.h>
using namespace std;
int main(){
cout << getpid() << endl;
execl("/bin/ps","ps",NULL); // c++路径,编译的操作,结束
cout << getpid() << endl;
return 0;
}
结果为:
9120
PID TTY TIME CMD
4810 pts/0 00:00:00 bash
9120 pts/0 00:00:00 ps
我们会发现,execl
后的程序的不运行的
是因为 exec() 函数运行后,会对用户存储部分覆盖,相当于新的程序进入内存中,将找不到原来的程序,后面的程序不能识别
2.3 系统函数 system
#include <iostream>
#include <cstdlib>
#include <unistd.h>
using namespace std;
int main(){
cout << getpid() << endl;
// system("ps"); // 打印当前进程
system("ps -o pid,ppid,cmd,s"); // 打印当前进程的详细信息,包括id、父id、执行命令、进程状态
cout << getpid() << endl;
}
结果为:
9383
PID PPID CMD S
4810 4801 bash S
9383 4810 ./a.out S
9384 9383 ps -o pid,ppid,cmd,s R
9383
在执行system命令时,他自己启动了一个子进程,后面的程序就和他没关系了
3. 退出进程
方式 | 说明 |
---|---|
return 0 | 在主函数中才是退出整个程序 |
exit() | 一般用在主函数外的函数,可以直接退出整个程序 |
_exit() | 一般用来结束子进程 |
abort() | 一般用来异常退出 |
信号终止 | 终止其他进程 |
执行完程序后echo $
可以看到返回值
4. 停止进程
4.1 休眠
sleep的用法
每隔一秒打印一个数字
#include <unistd.h>
#include <iostream>
using namespace std;
int main(){
for(int i=0;i<10;++i){
cout << i << endl;
sleep(1);
}
}
结果为:
0
1
2
3
4
5
6
7
8
9
usleep()
可以设置毫秒级别
4.2 暂停
pause() 可以停下来等一个信号
等待信号:
快捷键 | 信号 | 说明 |
---|---|---|
ctrl+c | SIGINT | 中断 |
ctrl+z | SIGTSTP | 终端的停止信号 |
先暂停,等待中断信号调用函数
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void test(int sig){
printf("revc a signal %d \n",sig);
}
int main(){
signal(SIGINT,test);
printf("before pause\n");
pause();
printf("after pause\n");
}
结果为:
before pause
^Crevc a signal 2
after pause
5. 特殊进程
概念 | 条件 | 导致结果 | 有无害 |
---|---|---|---|
僵尸进程 | 子进程退出,父进程没有获取子进程的状态信息 | 调用wait或waitpid | 有害,避免出现僵尸进程 |
孤儿进程 | 父进程先于子进程退出 | init进程作为新的父进程 | 无害 |
5.1 僵尸进程
我们设定一种情景:
#include <unistd.h>
#include <sys/wait.h>
#include <iostream>
using namespace std;
int main(){
cout << getpid() << endl;
pid_t pid = fork();
if(pid == 0){ // 子进程
cout << "child:" << endl;
}else{ // 父进程
for(;;){}
}
}
结果为:
[root@foundation1 C++7.10]# ./a.out
11260
child
^Z
[1]+ Stopped ./a.out
[root@foundation1 C++7.10]# bg
[1]+ ./a.out &
[root@foundation1 C++7.10]# ps
PID TTY TIME CMD
4810 pts/0 00:00:00 bash
11260 pts/0 00:00:05 a.out
11261 pts/0 00:00:00 a.out <defunct>
11278 pts/0 00:00:00 ps
[root@foundation1 C++7.10]# ps -o pid,ppid,cmd,s
PID PPID CMD S
4810 4801 bash S
11260 4810 ./a.out R
11261 11260 [a.out] <defunct> Z
11297 4810 ps -o pid,ppid,cmd,s R
子进程退出后父进程还在运行,此时子进程资源还没有释放,这个子进程就叫僵尸进程,僵尸进程是kill不死的,把父进程kill了才行
为了避免僵尸进程的出现:使用等待函数wait(NULL);
看有没有子进程需要回收,等子程序资源释放了,才会继续进行
#include <unistd.h>
#include <sys/wait.h>
#include <iostream>
using namespace std;
int main(){
cout << getpid() << endl;
pid_t pid = fork();
if(pid == 0){ // 子进程
cout << "child" << endl;
}else{ // 父进程
for(;;){
sleep(1);
wait(NULL);
}
}
}
wait后等待子程序运行完释放了资源,再结束
#include <unistd.h>
#include <sys/wait.h>
#include <iostream>
using namespace std;
int main(){
cout << getpid() << endl;
pid_t pid = fork();
if(pid == 0){
sleep(5);
cout << "child:" << getpid() << endl;
}else{
cout << "before wait" << endl;
wait(NULL);
cout << "end wait" << endl;
}
}
结果为:
11524
before wait // 等5秒
child:11525
end wait
先调用父进程,使用wait等待子进程结束后再调用父进程,保证父进程最后结束,用来回收子进程的资源,所以使用了fork,就最好要在父进程中加wait
创建十个子进程,查看释放情况
#include <unistd.h>
#include <sys/wait.h>
#include <iostream>
#include <list>
using namespace std;
int main(){
cout << getpid() << endl; // 进程pid
list<pid_t> pids;
pid_t pid = -1;
for(int i=0;i<10;++i){ // 生成十个进程
if(pid == 0){ // 子进程,用来打印目前存在的进程pid
sleep(5);
cout << "pid:" << getpid() << endl;
}else{ // 主进程,专门用来创建进程
pid = fork(); // 在主进程中创建子进程
if(pid!=0){ // 主进程
pids.push_front(pid);
}
}
}
if(pid!=0){ // 主进程
for(auto pid:pids){
cout << "before wait:" << pid << endl;
waitpid(pid,NULL,0); // 只等对应的子进程
cout << "end wait:" << pid << endl;
}
pause(); // 暂停
}
}
也可以使用给信号的方式
#include <unistd.h>
#include <sys/wait.h>
#include <iostream>
using namespace std;
void handler(int){
pid_t pid = wait(NULL);
// cout << pid << "exit" << endl; // cout带缓存
printf("%d exit\n",pid);
}
int main(){
signal(SIGCHLD,handler);
pid_t pid = -1;
for(int i=0;i<10;++i){
if(pid == 0){
sleep(5);
// cout << "pid:" << getpid() << endl;
printf("pid:%d\n",getpid());
}else{
pid = fork();
}
}
}
5.2 孤儿进程
我们再设置一种情景:
#include <iostream>
#include <unistd.h>
using namespace std;
int main(){
if(fork()){ // 父进程,20秒结束
cout << "parent:" << getpid() << endl;
sleep(20);
cout << "parent:" << getpid() << endl;
}else{ // 子进程,100秒结束
cout << "chile:" << getpid() << endl;
sleep(100);
cout << "chile:" << getpid() << endl;
}
}
结果为:
[root@foundation1 C++7.10]# ./a.out
parent:6063
chile:6064
^Z
[1]+ Stopped ./a.out
[root@foundation1 C++7.10]# bg
[1]+ ./a.out &
[root@foundation1 C++7.10]# ps -o pid,ppid,cmd,s // 查看进程信息
PID PPID CMD S
3016 3007 bash S
6063 3016 ./a.out S // 父进程
6064 6063 ./a.out S // 子进程
6077 3016 ps -o pid,ppid,cmd,s R
[root@foundation1 C++7.10]# parent:6063 // 20秒后父进程执行结束
ps -o pid,ppid,cmd,s // 再查看进程信息
PID PPID CMD S
3016 3007 bash S
6064 2048 ./a.out S // 只剩子进程,注意PPID
6084 3016 ps -o pid,ppid,cmd,s R
[1]+ Done ./a.out
[root@foundation1 C++7.10]# ps -p 2048 -o pid,ppid,cmd,s // 查看该进程信息
PID PPID CMD S
2048 1 /usr/lib/systemd/systemd -- S
[root@foundation1 C++7.10]# chile:6064 // 100秒后子进程也结束了
ps // 查看当前进程,已结束
PID TTY TIME CMD
3016 pts/0 00:00:00 bash
6118 pts/0 00:00:00 ps
父进程退出后子进程还在运行,这时的子进程会到一个新的父进程下,这个父进程就叫孤儿进程,子进程将会被祖父进程回收
每5秒释放一个子进程
#include <iostream>
#include <unistd.h>
using namespace std;
int main(){
pid_t pid = -1;
for(int i=0;i<10;++i){
if(pid==0){ // 子进程:打印进程信息
sleep(5);
cout << i << " child pid:" << getpid() << endl;
}else{ // 父进程:创建进程
pid = fork();
}
}
}
在一个页面运行,在另一个页面ps -ef
查看子进程的释放/回收情况