Linux应用(五)——进程基础

写在前面:本节我们学习了进程的相关基础内容,主要包括进程的概述,进程与程序的区别,进程的基本状态,父子进程,以及进程的GDB调试等相关内容;

一、进程概述

1.1程序与进程

程序是包含一系列信息的文件;

进程是正在运行的程序的实例;

        程序是怎么产生的呢?我们可以简单认为,将代码写入一个文件,然后再经过编译、链接之后便可以生成一个可执行程序;但是这个可执行程序的本质还是文件,放在磁盘当中;

        当我们将可执行程序进行运行时,本质上是将程序加载到内存当中,CPU会对程序进行执行,而一旦程序被加载到内存中,我们称为进程,也就是正在运行的程序;

        进程我们认为它是由内核定义的抽象实体操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。

        一个程序可以创建多个进程,一旦进程产生,与之对应的系统就会为进程分配用来执行程序的各类资源。

1.2单道、多道程序设计

单道程序:即在计算机内存中只允许一个的程序运行。

多道程序设计技术:是在计算机内存中同时存放几道相互独立的程序,使它们在管理程序控制下,相互穿插运行,两个或两个以上程序在计算机系统中同处于开始到结束之间的状态, 这些程序共享计算机系统资源。

我们需要注意的是,无论是单道还是多道,在任意某一时刻CPU只能执行一个进程,即使是多道程序,也是多个进程轮流使用 CPU;

时间片:是操作系统分配给每个正在运行的进程微观上的一段 CPU 时间。即便是一台计算机通常可能有多个 CPU,但是同一个 CPU 永远不可能真正地同时运行多个任务,只是轮番穿插地运行,由于时间片通常很短,感觉是多个进程同时在运行;

时间片由操作系统内核的调度程序分配给每个进程。

1.3并行与并发

并行:指在同一时刻,有多条指令在多个处理器上同时执行;

并发:指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行;

目前来说,我们还是对于并发的研究更加深入,因为并行的缺陷明显,需要高数量的多处理器,在一个系统中很难完成多处理器的集成;

并发是两个队列交替使用一台咖啡机。

并行是两个队列同时使用两台咖啡机。

 1.4进程控制块(PCB) 

        为了管理进程,内核必须对每个进程所做的事情进行清楚的描述。内核为每个进程分配一个 PCB(进程控制块,维护进程相关的信息,Linux 内核的进程控制块是 task_struct 结构体。主要包括的内容有:

二、进程状态转换

2.1进程的状态 

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

进程的状态分为:三态模型与五态模型;

三态模型:

        三态模型中,进程状态分为三个基本状态,即就绪态,运行态,阻塞态。在五态模型 中,进程分为新建态、就绪态,运行态,阻塞态,终止态。

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

就绪态:进程具备运行条件,等待系统分配处理器以便运 行。当进程已分配到除CPU以外的所有必要资源后,只要再获得CPU,便可立即执行。在一个系统中处于就绪状态的进 程可能有多个,通常将它们排成一个队列,称为就绪队列。

阻塞态:又称为等待(wait)态或睡眠(sleep)态,指进程 不具备运行条件,正在等待某个事件的完成

五态模型:

        在三态模型的基础上,增加一个新建态于终止态;

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

        终止态:进程完成任务到达正常结束点,或出现无法克服的错误而异常终止,或被操作系统及 有终止权的进程所终止时所处的状态。进入终止态的进程以后不再执行,但依然保留在操作系 统中等待善后。一旦其他进程完成了对终止态进程的信息抽取之后,操作系统将删除该进程。

2.2进程的相关指令与函数

查看进程:

ps aux / ajx

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

u:显示进程的详细信息 x:显示没有控制终端的进程

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

ps aux:

USER :用户;PID:进程的ID; %CPU:CPU的使用率; %MEM :内存的使用率;TTY  :当前进程所属的一个终端; STAT:状态;START:开始的时间,COMMAND:执行的命令;

 ps ajx:

PPID:父进程ID;PID:进程的ID;PGID:进程组的ID;SID:会话的ID;

 实时显示进程动态

top

可以在使用 top 命令时加上-d 来指定显示信息更新的时间间隔,在 top 命令 执行后,可以按以下按键对显示的结果进行排序;

动态进程信息显示

 杀死进程

kill [-signal] pid

kill –l 列出所有信号

kill –SIGKILL 进程ID

kill -9 进程ID

killall name 根据进程名杀死进程

 当前终端的进程无法杀死,kill+id不能杀死,需要用到kill -9 +id;直接杀死ID信号;

 2.3进程号与相关函数

1、每个进程都由进程号来标识,其类型为 pid_t(整型),进程号的范围:0~32767。 进程号总是唯一的,但可以重用。当一个进程终止后,其进程号就可以再次使用。

2、任何进程(除 init 进程)都是由另一个进程创建,该进程称为被创建进程的父进程, 对应的进程号称为父进程号(PPID)。

3、进程组是一个或多个进程的集合。他们之间相互关联,进程组可以接收同一终端的各 种信号,关联的进程有一个进程组号(PGID)。默认情况下,当前的进程号会当做当 前的进程组号。

三、 父子进程

3.1fork函数

      操作系统允许在一个进程中创建新的进程,新的进程称之为子进程,原进程称之为父进程;子进程还可以在创建新的子进程,从而形成进程树模型;

fork函数——一个用于创建子进程的函数;   

     #include <sys/types.h>

       #include <unistd.h>

       pid_t fork(void);

   作用:用于创建子进程;

   返回值:fork的返回值会返回两次,一次是在父进程中,一次是在子进程中;

   如果成功, 在父进程中返回子进程的ID,在子进程返回0;

   如果返回失败,则在父进程中返回-1,子进程没有被创建,在errno中返回错误的值;

   如何判断是父进程还是子进程?通过fork的返回值。

      测试代码:  

 #include <sys/types.h>
       #include <unistd.h>
       #include <stdio.h>
        int main()
       {
        //创建子进程
        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());
        }
        else if(pid==0)
        {
            //当前是子进程;
          printf("i am child process,pid:%d,ppid:%d\n",getpid(),getppid());
        }

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

测试结果:

由上面的测试结果我们可知,此代码文件已运行,产生一个进程,该进程的进程号为:6600,此进程是由终端产生,那么该进程的父进程(终端)的进程号为:6330;

该代码进程中有一个fork函数,会产生一个子进程,产生的子进程的进程号为:6601;该子进程的父进程也就是由中断产生的,父进程号为:6600;

3.2父子进程虚拟地址空间

        父进程与子进程在程序中是如何执行的?,父进程与子进程的相关虚拟地址空间问题;

        在执行了fork函数后,在代码去父进程和子进程的代码是一样的,只是两者执行的是不同的(由于pid的不同);

父进程执行的代码:

子进程执行的代码:

        当调用fork()函数时会复制一份父进程的虚拟地址空间 ,即子进程的用户数据和父进程一样,内核区也会被拷贝过来,但是pid会略有不同; 

 如果在父进程中存在变量的定义,那么克隆的子进程中的变量与父进程的变量有什么关系呢?


       #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(int i =0;i<3;i++)
        {
            printf("i=%d,pid:%d\n",i,getpid());
            sleep(1);
        }
        return 0;
       }

如上图所示, 克隆后的子进程中的变量不受父进程的变量变化影响;

实际上,fork函数的使用是写时拷贝,(写时拷贝是一种推迟甚至避免拷贝数据的技术)也就是说资源的复制是在需要写入的时候才会进行,在此之前,只是以读的方式进行共享;

内核此时并不复制整个进程的地址空间,而是让父子进程共享一个地址空间;只有在需要写入的时候才会复制地址空间,从而使各个进程有各自的地址空间。(父子进程进行写入的时候,都会重新创建一个地址空间);

父子进程之间的关系:

区别

1、fork()函数的返回值不同

        父进程中:>0,返回子进程的ID

        子进程中:=0;

2、PCB(进程控制块)中的一些数据:

        当前进程的ID不同(PID),当前进程的父进程的ID也不同(PPID);

共同点

        在某些状态下,在子进程刚被创建还没有执行写入操作时;

        用户区数据、文件描述符表;

父子进程对于变量是不是共享的呢?

        刚开始是共享的,如果修改了数据,就不进行共享了;

        读时共享,写时拷贝;

 3.3GDB多进程调试

       设置调试对象

        使用 GDB 调试的时候,GDB 默认只能跟踪一个进程,可以在 fork 函数调用之前,通
过指令设置 GDB 调试工具跟踪父进程或者是跟踪子进程,默认跟踪父进程。

测试代码:

#include <stdio.h>
#include <unistd.h>
int main() {
    printf("begin\n");
    if(fork() > 0) {
        printf("我是父进程:pid = %d, ppid = %d\n", getpid(), getppid());
        int i;
        for(i = 0; i < 10; i++) {
            printf("i = %d\n", i);
            sleep(1);
        }
    } else {

        printf("我是子进程:pid = %d, ppid = %d\n", getpid(), getppid());
        
        int j;
        for(j = 0; j < 10; j++) {
            printf("j = %d\n", j);
            sleep(1);
        }
    }
    return 0;
}

GDB默认情况下,调试的是父进程的代码, 

设置调试父进程或者子进程:set follow-fork-mode [parent(默认)| child];

 设置调试模式

设置调试模式:set detach-on-fork [on | off]
默认为 on,表示调试当前进程的时候,其它的进程继续运行,如果为 off,调试当前进
程的时候,其它进程被 GDB 挂起。挂起就不会被执行;

其他调式 

查看调试的进程:info inferiors
切换当前调试的进程:inferior id
使进程脱离 GDB 调试:detach inferiors id 

        本节我们学习的内容基本如上,大家多多练习;

        创作不易,还请大家多多点赞支持!!!

  • 14
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
嵌入式Linux应用开发的学习路线可以按照以下方式进行: 1. 先从学习Linux基础知识开始。掌握Linux系统的基本概念,了解Linux的文件系统、进程和线程管理、信号处理等。可以通过读相关书籍或者参加在线课程来学习。 2. 掌握Linux下的C/C++编程。了解Linux下的编程环境和工具,学习使用Linux下的开发工具链进行编译和调试。可以通过编写一些简单的命令行应用程序来熟悉Linux下的编程环境。 3. 学习Linux应用开发框架。了解Linux下常用的应用开发框架,例如Qt和GTK+,学习使用这些框架进行图形界面的应用开发。可以通过参考相关的教程和示例代码来学习。 4. 深入学习Linux的系统编程接口。了解Linux的系统调用接口,学习如何使用系统调用进行文件操作、进程管理、网络通信等。可以通过阅读Linux的相关文档和参考书籍来学习。 5. 学习嵌入式Linux的设备驱动开发。了解Linux下的设备驱动框架,学习如何编写设备驱动程序,包括字符设备驱动、块设备驱动和网络设备驱动等。可以通过参考Linux内核的源代码和相关的文档来学习。 总的来说,嵌入式Linux应用开发的学习路线是先掌握Linux基础知识,然后学习Linux应用开发框架,最后深入学习设备驱动开发。这样的学习路线能够帮助你逐步掌握嵌入式Linux应用开发所需的技能和知识。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [嵌入式Linux入门指南(一)——学习路线篇](https://blog.csdn.net/weixin_51627076/article/details/122588888)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值