Linux系统编程:进程基础

1. 查看进程

1.1 什么是进程

进程:运行中的程序

虚拟存储器/虚拟地址空间

在这里插入图片描述

gdb调试功能
步骤操作意义
1g++ test.cpp -g编译时加 -g
2gdb ./a.out开始调试
3list查看断点
4b 5断点加在第5行
5info proc mapping查看内存区域的映射
6nnext走到下一步
7p handle查看handle的地址
8q退出
进程状态
调度
时间片完
等待
唤醒
创建
就绪
执行
终止
阻塞

就绪:操作系统把程序加载号等待处理
阻塞:等待某一个时间完成后再就绪,如同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

我们发现:

  1. 并发执行:
    两个进程同时跑,互不干扰
  2. 相同但是独立的地址空间:
    两个进程的 n 地址都是一样的,但是数据会自己跑自己的
    因为系统会把虚拟内存拷贝一份出来,两个进程都有自己的堆和栈
  3. 使用 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+cSIGINT中断
ctrl+zSIGTSTP终端的停止信号

先暂停,等待中断信号调用函数

#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查看子进程的释放/回收情况
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值