多进程概念与调试

本文介绍了Linux中的进程状态,如就绪、运行、阻塞、新建和终止态,并探讨了使用`ps`、`top`命令观察和管理进程。还详细讲解了`fork()`函数创建子进程的过程,以及GDB调试多线程程序的方法,包括设置断点和跟随fork模式。
摘要由CSDN通过智能技术生成

进程概述

​ 进程即计算机内处理的多项任务中的一个,它是资源分配的基本单位,引入了线程后,线程是处理机调度的基本单位。基础概念的介绍可看(4条消息) 线程与进程,你真得理解了吗_进程和线程的区别_云深i不知处的博客-CSDN博客。本文主要介绍如何在Linux系统中观察和处理进程。

进程状态

进程状态反映了进程执行过程的变化,这些状态随着进程的执行和外界条件的变化而转化。

在三态模型中,进程状态分为三个基本状态,即就绪态,运行态,阻塞态

在五态模型中加入了新建态,终止态。

◼ 运行态:进程占有处理器正在运行

◼ 就绪态:进程具备运行条件,等待系统分配处理器以便运

​ 行。当进程已分配到除CPU以外的所有必要资源后,只要再

​ 获得CPU,便可立即执行。在一个系统中处于就绪状态的进

​ 程可能有多个,通常将它们排成一个队列,称为就绪队列

◼ 阻塞态:又称为等待(wait)态或睡眠(sleep)态,指进程

​ 不具备运行条件,正在等待某个事件的完成

◼ 新建态:进程刚被创建时的状态,尚未进入就绪队列

◼ 终止态:进程完成任务到达正常结束点,或出现无法克服的错误而异常终止,或被操作系统及

​ 有终止权的进程所终止时所处的状态。进入终止态的进程以后不再执行,但依然保留在操作系

​ 统中等待善后。一旦其他进程完成了对终止态进程的信息抽取之后,操作系统将删除该进程。

在这里插入图片描述

进程相关命令

查看进程

ps aux / ajx

a:显示终端上的所有进程,包括其他用户的进程

u:显示进程的详细信息

x:显示没有控制终端的进程

j:列出与作业控制相关的信息

◼ STAT参数意义

D

不可中断 Uninterruptible(usually IO)

R

正在运行,或在队列中的进程

S(大写)

处于休眠状态

T

停止或被追踪

Z

僵尸进程

W

进入内存交换(从内核2.6开始无效)

X

死掉的进程

<

高优先级

N

低优先级

s

包含子进程

+

位于前台的进程组

◼ 实时显示进程动态

top

可以在使用 top 命令时加上 -d 来指定显示信息更新的时间间隔,在 top 命令

执行后,可以按以下按键对显示的结果进行排序:

M

根据内存使用量排序

P

根据 CPU 占有率排序

T

根据进程运行时间长短排序

U

根据用户名来筛选进程

K

输入指定的 PID 杀死进程

◼ 杀死进程

kill [-signal] pid

kill –l 列出所有信号

kill –SIGKILL 进程ID

kill -9 进程ID

killall name 根据进程名杀死进程

进程号

◼ 每个进程都由进程号来标识,其类型为 pid_t(整型),进程号的范围:0~32767。

进程号总是唯一的,但可以重用。当一个进程终止后,其进程号就可以再次使用。

任何进程(除 init 进程)都是由另一个进程创建,该进程称为被创建进程的父进程,

对应的进程号称为父进程号(PPID)。

◼ 进程组是一个或多个进程的集合。他们之间相互关联,进程组可以接收同一终端的各

种信号,关联的进程有一个进程组号(PGID)。默认情况下,当前的进程号会当做当

前的进程组号。

◼ 进程号和进程组相关函数:

⚫ pid_t getpid(void);

⚫ pid_t getppid(void);

⚫ pid_t getpgid(pid_t pid);

进程创建

/*
    #include <sys/types.h>
    #include <unistd.h>

    pid_t fork(void);
        函数的作用:用于创建子进程。
        返回值:
            fork()的返回值会返回两次。一次是在父进程中,一次是在子进程中。
            在父进程中返回创建的子进程的ID,
            在子进程中返回0
            如何区分父进程和子进程:通过fork的返回值。
            在父进程中返回-1,表示创建子进程失败,并且设置errno

        父子进程之间的关系:
        区别:
            1.fork()函数的返回值不同
                父进程中: >0 返回的子进程的ID
                子进程中: =0
            2.pcb中的一些数据
                当前的进程的id pid
                当前的进程的父进程的id ppid
                信号集

        共同点:
            某些状态下:子进程刚被创建出来,还没有执行任何的写数据的操作
                - 用户区的数据
                - 文件描述符表
        
        父子进程对变量是不是共享的?
            - 刚开始的时候,是一样的,共享的。如果修改了数据,不共享了。
            - 读时共享(子进程被创建,两个进程没有做任何的写的操作),写时拷贝。
        
*/

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main() {

    int num = 10;

    // 创建子进程
    pid_t pid = fork();

    // 判断是父进程还是子进程
    if(pid > 0) {
        // printf("pid : %d\n", pid);
        // 如果大于0,返回的是创建的子进程的进程号,当前是父进程
        printf("i am parent process, pid : %d, ppid : %d\n", getpid(), getppid());

        printf("parent num : %d\n", num);
        num += 10;
        printf("parent num += 10 : %d\n", num);


    } else if(pid == 0) {
        // 当前是子进程
        printf("i am child process, pid : %d, ppid : %d\n", getpid(),getppid());
       
        printf("child num : %d\n", num);
        num += 100;
        printf("child num += 100 : %d\n", num);
    }

    // for循环
    for(int i = 0; i < 3; i++) {
        printf("i : %d , pid : %d\n", i , getpid());
        sleep(1);
    }

    return 0;
}

父子进程代码执行情况

在这里插入图片描述
在这里插入图片描述

子进程会拷贝父进程的虚拟内存空间内容,父进程的值修改不会影响到子进程,两者独立。

实际上,更准确来说,Linux 的 fork() 使用是通过写时拷贝 (copy- on-write) 实现。
写时拷贝是一种可以推迟甚至避免拷贝数据的技术。
内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间。
只用在需要写入的时候才会复制地址空间,从而使各个进行拥有各自的地址空间。
也就是说,资源的复制是在需要写入的时候才会进行,在此之前,只有以只读方式共享。
注意:fork之后父子进程共享文件,
fork产生的子进程与父进程相同的文件文件描述符指向相同的文件表,引用计数增加,共享文件偏移指针。

GDB多线程调试

lin@lin-virtual-machine:~/Linux/lesson09$ gcc hello.c -o hello -g
lin@lin-virtual-machine:~/Linux/lesson09$ ls
hello  hello.c
lin@lin-virtual-machine:~/Linux/lesson09$ gdb hello
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from hello...
(gdb) l
1	#include <stdio.h>
2	#include <unistd.h>
3	
4	int main() {
5	
6	    printf("begin\n");
7	
8	    if(fork() > 0) {
9	
10	        printf("我是父进程:pid = %d, ppid = %d\n", getpid(), getppid());
(gdb) l
11	
12	        int i;
13	        for(i = 0; i < 10; i++) {
14	            printf("i = %d\n", i);
15	            sleep(1);
16	        }
17	
18	    } else {
19	
20	        printf("我是子进程:pid = %d, ppid = %d\n", getpid(), getppid());
(gdb) 
21	        
22	        int j;
23	        for(j = 0; j < 10; j++) {
24	            printf("j = %d\n", j);
25	            sleep(1);
26	        }
27	
28	    }
29	
30	    return 0;
(gdb) b 10
Breakpoint 1 at 0x120e: file hello.c, line 10.
(gdb) b 20
Breakpoint 2 at 0x126a: file hello.c, line 20.
(gdb) i b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000000000000120e in main at hello.c:10
2       breakpoint     keep y   0x000000000000126a in main at hello.c:20
(gdb) r
Starting program: /home/lin/Linux/lesson09/hello 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
begin
[Detaching after fork from child process 19727]
我是子进程:pid = 19727, ppid = 19724
j = 0

Breakpoint 1, main () at hello.c:10
10	        printf("我是父进程:pid = %d, ppid = %d\n", getpid(), getppid());
(gdb) j = 1
j = 2
j = 3
j = 4
j = 5
j = 6
j = 7
j = 8
j = 9
n
我是父进程:pid = 19724, ppid = 19525
13	        for(i = 0; i < 10; i++) {
(gdb) c
Continuing.
i = 0
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9

上述代码中可以看到只触发了父进程中的断点,这是多进程默认优先调试父进程断点。

Breakpoint 1, main () at hello.c:10
10	        printf("我是父进程:pid = %d, ppid = %d\n", getpid(), getppid());

没有触发子进程断点

为了触发子进程断点

我们需要设置

set follow-fork-mode child

我们可以先通过命令来展示调试的是父进程还是子进程

show follow-fork-mode

再一次运行结果如下:

[Inferior 1 (process 19724) exited normally]
(gdb) show follow-fork-mode
Debugger response to a program call of fork or vfork is "parent".
(gdb) set follow-fork-mode child
(gdb) show follow-fork-mode
Debugger response to a program call of fork or vfork is "child".
(gdb) r
Starting program: /home/lin/Linux/lesson09/hello 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
begin
[Attaching after Thread 0x7ffff7fa9740 (LWP 19749) fork to child process 19750]
[New inferior 2 (process 19750)]
[Detaching after fork from parent process 19749]
我是父进程:pid = 19749, ppid = 19525
i = 0
[Inferior 1 (process 19749) detached]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[Switching to Thread 0x7ffff7fa9740 (LWP 19750)]

Thread 2.1 "hello" hit Breakpoint 2, main () at hello.c:20
20	        printf("我是子进程:pid = %d, ppid = %d\n", getpid(), getppid());
(gdb) i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9
c
Continuing.
我是子进程:pid = 19750, ppid = 1223
j = 0
j = 1
j = 2
j = 3
j = 4
j = 5
j = 6
j = 7
j = 8
j = 9
[Inferior 2 (process 19750) exited normally]
(gdb)

设置调试模式

set detach-on-fork [on | off ]

默认为on,表示调试当前进程的时候,其他进程继续执行,如果为off,调试当前进程时,其他进程将被GDB挂起

查看调试的进程:info inferiors

切换当前调试的进程:inferior id

= %d\n", getpid(), getppid());
(gdb) i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9
c
Continuing.
我是子进程:pid = 19750, ppid = 1223
j = 0
j = 1
j = 2
j = 3
j = 4
j = 5
j = 6
j = 7
j = 8
j = 9
[Inferior 2 (process 19750) exited normally]
(gdb)


**设置调试模式**

set detach-on-fork [on | off ]


默认为on,表示调试当前进程的时候,其他进程继续执行,如果为off,调试当前进程时,其他进程将被GDB挂起

查看调试的进程:info inferiors

切换当前调试的进程:inferior id

是进程脱离GDB调试:detach inferiors id
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值