【Linux探索学习】第十二弹——初识进程:进程的定义、描述和一些简单的相关操作

embedded/2024/11/14 20:26:28/

Linux学习笔记:

https://blog.csdn.net/2301_80220607/category_12805278.html?spm=1001.2014.3001.5482

前言:

在前面经过那么多篇的铺垫后,今天我们正式进入Linux学习的第一个重难点——进程,理解进程对于我们学习操作系统的其它部分,尤其是多文件处理和资源管理十分重要,下面我们正式进入进程的第一篇讲解

目录

一、进程概念

二、进程描述

三、查看进程

四、通过系统调用获取进程标识符

五、通过系统调用创建进程——初识fork

六、总结


一、进程概念

进程是一个正在执行的程序的实例。它不仅包括程序的代码,还包括程序的当前活动、寄存器、程序计数器、堆栈及其所有与执行相关的资源。简单来说,进程是一个程序在运行时的一个动态实体。

需要注意的一点是进程的程序是一定被加载在内存中的,因为进程是系统将要进行处理的数据,而CPU是从内存中获取数据的,所以说进程的程序一定被加载在内存中的,比如我们vim写的一个.c的C语言程序,它在操作系统下的本质就是一个文件,是存放在外设中的,当运行起来时,我们就会将它的相关数据存放到内存中去,以便于CPU直接获取

二、进程描述

一个操作系统可能可以同时进行多个进程,比如我们可以让多个程序同时进行,我们的电脑可以同时跑多个软件,为了避免进程执行起来互相干扰,所以我们要对进程进行管理

一般进行管理的过程就是:先描述+再组织

所以我们要进行进程描述:任何一个进程,在被加载到内存,形成真正的进程时,操作系统都要先创建描述进程的结构体对象——PCB,也叫做进程控制块,可以理解为进程属性的集合,操作系统是C语言写的,所以PCB一定是一个struct结构体,PCB中会包含进程如下的重要信息:

  • 进程ID(PID):唯一标识一个进程的编号。
  • 进程状态:当前进程的状态。
  • 程序计数器:指向当前执行指令的地址。
  • CPU寄存器:进程在执行时的寄存器内容。
  • 内存管理信息:如页表和段表。
  • 进程优先级:调度时的优先级信息。

此外为了方便管理进程,处理进程与进程之间的关系,进程在内存中是以队列的形式存在的,具体点来讲就是链表(双链表),进程在内存中的存在形式可以抽象为下图:

由于PCB中包含着进程的所有信息,所以对进程管理的本质其实就是对进程的PCB做管理,进程在操作系统又通过队列进行链接,所以对进程的管理,其实就是对链表的增删改查

这里的PCB是针对所有操作系统而言的,在我们的Linux中我们往往习惯称呼这个概念为task struct

三、查看进程

在上面我们讲到进程的许多属性,包括进程编号、进程状态等等许多内容

首先我们可以通过查看/proc/文件,来查看我们目前正在执行的全部进程

ls /proc/

这些数字就是进程的PID,每个进程都会有一个对应的PID,PID就是我们上面所说的进程ID,也叫做进程标识符,我们可以通过这些进程标识符来查看每个进程具体的信息,比如查看1号进程

ls /proc/1

除了上面的这个方法外,我们还可以通过下面这个指令,不仅可以看到所有的进程,还可以看到它们的进程的属性信息:

ps axj

我们节选一部分:

执行结果的第一行就是我们的进程属性信息的列名,下面就是每个进程对应的属性信息,我们可以只打印出一行来看一下进程属性的内容(需要借助之前的知识:管道 | 和打印行数head)

ps axj | head -1

对于这些属性信息中,我们先记住前两个就行了,PPID指的是父进程标识符,PID知道是当前进程标识符

目前我们自己创建的可执行文件有test

我们可以查看下我们自己创建的这个进程的相关信息(注意只有当我们的程序在跑着的时候它才叫进程,所以我们可以将我们的程序写成一个死循环,然后让它执行起来)

ps axj | head -1 && ps axj | grep test

观察这个执行结果,我们可以发现有两个相关进程,会出现第二条的原因就是执行查找test进程的命令本身也会成为一个进程,而这个进程中含test,所以会把自身也带上

如果不想要,可以在后面加上 | grep -v grep,这个-v选项我们在前面讲指令的时候是讲过的,是反向匹配的意思

ps axj | head -1 && ps axj | grep test | grep -v grep

四、通过系统调用获取进程标识符

除了上面获取进程标识符的方法外,我们还可以通过系统调用的方式来获取表示符,系统接口为getpid和getppid,我们可以通过man手册来查看这个接口

man 2 getpid

具体方法如下:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{printf("pid: %d\n", getpid());printf("ppid: %d\n", getppid());return 0;
}

多次执行这个程序,我们会发现pid一直在变化,而ppid一直不变,也就是说子进程编号一直在变化,而父进程一直没变,为什么会出现这个现象呢?

这是因为,我们在打开Linux时,会首先创建一个bash进程,形成对话框,这个bash进程也是其它所有进程的子进程,所以一般代码重新运行时,它的子进程编号会变,而父进程编号不会变


我们可以创建一个监视窗口方便观察(了解):

while :; do ps axj | head -1 ; ps axj | grep test | grep -v grep; 
echo "----------------------------"; sleep 1 ; done

五、通过系统调用创建进程——初识fork

我们可以通过fork手动创建进程,我们可以通过man手册查看一下fork

man fork

我们先来看下面的一个小程序:

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>int main()
{printf("before test");fork();printf("after test");return 0;
}

运行结果:

我们注意到在fork()函数之后的第二行打印语句执行了两次,说明在fork()之后一个进程变成了两个进程

此外,fork函数还有一个重要知识就是它是有两个整形返回值的,这点与我们之前所学的C语言中的函数差别很大,因为我们之前所学的函数都是只有一个返回值,fork的两个整形返回值中,大于0代表父进程,等于0是子进程

我们下面来看这样一个程序来验证一下:

  1 #include<stdio.h>2 #include<sys/types.h>3 #include<unistd.h>4  5 int main()6 {7     pid_t id=fork();8     if(id>0)9     {10         //父进程11         printf("I am parent process, pid:%d, ppid:%d\n",getpid(),getppid());12     }13     else if(id==0)14     {15         //子进程16         printf("I am child process, pid:%d, ppid:%d\n",getpid(),getppid());17     }18     printf("hello linux\n");19     return 0;20 }

在这个函数中我们尝试将父子进程分开,并且在最后有一个公共代码区,执行结果:

我们可以看到子进程的ppid就是父进程的pid,所以也印证了它们的父子关系,而且最后一个打印代码父子进程都执行了

相信不少同学对上面的问题已经有了很大的疑惑了,比如fork为什么要给子进程返回0,给父进程返回子进程pid呢?其实这就是为了区分父子进程,让不同的执行流执行不同的代码

一般而言fork之后的代码是共享的,这也就是为什么上面的  "hello linux"  打印了两遍的原因,因为父子进程都执行了它,那么如果此时子进程对共享数据进行操作了,我们就需要对额外操作的数据开辟新空间,这就是写时拷贝,这我们会在后面详细讲解

至于为何pid_t id中的id可以取两个值,这也需要我们后面讲到进程空间地址的问题时再提,现在只需要也简单地理解为写时拷贝就可以了

六、总结

以上就是今天讲解的进程的基础内容,篇幅较长,文字较多,相信认真看完的你会有所收获,后面我们就将开启进程知识的深度讲解


感谢各位大佬观看,创作不易,还请各位大佬点赞支持!!!


http://www.ppmy.cn/embedded/136896.html

相关文章

leetcode82:删除排序链表中的重复节点||

给定一个已排序的链表的头 head &#xff0c; 删除原始链表中所有重复数字的节点&#xff0c;只留下不同的数字 。返回 已排序的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,3,4,4,5] 输出&#xff1a;[1,2,5]示例 2&#xff1a; 输入&#xff1a;head [1,1,1,2…

爬虫入门urllib 和 request(二)

文章目录 1、urllib介绍2、urllib的基本方法介绍2.1 urllib.Request2.2 response.read() 3、urllib请求百度首页的完整例子4、小结 1、urllib介绍 除了requests模块可以发送请求之外, urllib模块也可以实现请求的发送,只是操作方法略有不同! urllib在python中分为urllib和url…

重新认识HTTPS

一. 什么是 HTTPS HTTP 由于是明文传输&#xff0c;所谓的明文&#xff0c;就是说客户端与服务端通信的信息都是肉眼可见的&#xff0c;随意使用一个抓包工具都可以截获通信的内容。 所以安全上存在以下三个风险&#xff1a; 窃听风险&#xff0c;比如通信链路上可以获取通信…

[asdf] 管理erlang 版本--ubuntu 16.04

部分网址 asdf 官网&#xff1a;快速入门 | asdf elang 插件网址&#xff1a;https://github.com/asdf-vm/asdf-erlang rebar 插件网址&#xff1a;https://github.com/Stratus3D/asdf-rebar 安装asdf 先安装依赖,默认装了git 可只安装curl apt install curl git 2、安装as…

C语言笔记(字符串函数,字符函数,内存函数)

目录 前言 1.字符串函数 1.1.strlen 1.2.strcpy 1.3.strcat 1.4.strcmp 1.5.strncpy 1.6.strncat 1.7.strncmp 1.8.strstr 1.9.strtok 1.10.strerror 2.字符函数 2.1字符分类函数 2.2字符转换函数 3.内存函数 3.1.mencpy 3.2.memmove 3.3.memcmp 前言 本文重…

解决:使用EasyExcel导入Excel模板时出现数据导入不进去的问题

解决&#xff1a;使用EasyExcel导入Excel模板时出现数据导入不进去的问题 在Java中&#xff0c;当我们用EasyExcel导入Excel时&#xff0c;可能会出现数据导入不进去的问题。例如&#xff1a; 这种异常等。 问题原因1&#xff1a;这个1代表从第几行开始&#xff0c;你的exce…

界面控件DevExpress WPF中文教程:Data Grid——卡片视图设置

DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序&#xff0c;这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。 无论是Office办公软件…

如何在自己的云服务器上部署1Panel

打开官网 https://1panel.cn/ 点击下载安装 复制命令 curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_start.sh && sh quick_start.sh到自己的云服务器上&#xff0c;打开webssh 登录后&#xff0c;执行命令&#xff0c;即可自动…