linux进程概念
冯洛伊曼结构
我们日常所用的计算机,如笔记本,和不常见的计算机,如服务器大部分都遵循冯洛伊曼体系。
截至目前,我们所了解的计算机都是有一个个硬件所组成。
输入单元:鼠标,键盘,扫描仪等。
中央处理器(CPU):含运算器和控制器等。
输出单元:显示器,打印机等。
不考虑缓存的情况下,CPU能且只能对内存(存储器)进行读写,不等访问外设(输入和输出设备)
同样外设也只能对内存进行读写,总结就是所有设备只能与内存打交道。
为什么要有内存呢?
由于 CPU 的处理速度远快于外设(如硬盘、网络接口、打印机等)的响应速度,直接让 CPU 等待外设完成任务会导致效率低下。为了避免这种情况,计算机系统中引入了缓存和其他缓冲机制。内存就是CPU和外设之间一个巨大的缓存!
操作系统(Operator System)
OS本质是一种对硬软件进行管理的程序!
为什么要有OS?
为了解决这个问题,我们需要了解为什么要设计操作系统。
根据上图我们可以看到,操作系统的设计是为了协调硬件和软件之间的关系,管理资源,提高计算机系统的效率和安全性。
总结:操作系统对下进行硬件资源管理,稳定的,高效的,安全的,进行良好的工作(手段)。
对上为用户提供一个安全的,搞笑的,稳定的运行环境(目的)。
操作系统如何管理(顶层理解)
我们举个栗子吧,就像是学校,管理者(比如校长,年级主任等),也有执行者(比如辅导员),还有被管理者(学生)。我们今天的身份是校长,如果我们如何管理学生呢,我们难道要一个一个的见面,然后告诉他们学校的规章制度以及对他们犯的错误进行惩处?当然不需要,我们只需要获取到他们每个人的信息以及一些必要数据,做一决策就好。那由谁来负责我们数据的收集呢,辅导员会对我们所需要的数据进行收集。但是到我们手里的数据会很杂乱,有很多我们并不需要的属性也会掺杂其中,为了解决这个问题,我们就想到能不能把我们所需要的数据属性“描述”一下,比如(学生的姓名,学号,性别等),然辅导员按照我们“描述的模板”进行数据收集,这时候对我们会很方便的进行管理。
操作系统(校长)对底层硬件的管理(学生),也是先描述,用struct结构体,通过驱动(辅导员)获取我们所需的数据,最后将这些获得数据以特定的结构组织管理起来,这就使得我们的操作系统很好的管理我们的软硬件资源。
系统调用和库函数概念
操作系统为什么会给我们提供系统调用接口以及库函数呢,直接让我们自己通过操作系统获取数据,它不香吗?
再举个栗子吧,银行使我们存取钱和办理其他业务的地方,银行里面有行长,办理业务的专员,柜台窗口等,假设我们直接开放银行,拆掉这些柜台,让用户自己办理业务。会发生什么呢?我们要知道群众里面是有坏人的,当我们完全开放后,里面的钱财会丢失,人员的安全也无法得到保障。
当我们不对外开放,而是有专门提供业务的人和窗口,需要办理什么业务只需要跟他们讲就好了。
带入我们的操作系统也是一样的,我们为其提供函数调用和库函数不仅可以防止有心之人的恶意破坏,还可以给用户提供更好的服务。
总结而言呢,统一接口,隐藏复杂性,进行权限管控和错误处理,拥有更好的安全性和稳定性,合理的进行资源分配以及跨平台的兼容和版本控制。
进程
什么叫进程?
基本概念
课本概念:程序是一个实例, 正在执行的实例等
内存概念:担当分配系统资源(CPU时间,内存)的实体
再简单来说,Windows通过双击,linux通过./执行的.exe文件,本质上就是开启一个进程。
直到用户退出才退出的常驻进程和执行完就结束的(如ls,pwd等指令)
进程 = 内核数据结构(task_struct) + 程序代码和数据。
理解CWD(Current Working Directory 即当前的工作目录)
为什么要了解这个呢?
首先我们要知道什么叫做CWD,它是指在文件系统中,用户或进程当前所在的目录。
我们可以通过pwd命令看到工作目录
进程属性 -- PCB
进程控制块(PCB,Process Control Block)是操作系统中用于管理进程的一个重要数据结构。它包含了一个进程在运行时所需的所有信息,帮助操作系统调度和控制进程的执行。每个进程都有一个对应的 PCB,通常包括以下内容:
- 进程状态:进程的当前状态(如运行、就绪、阻塞)。
- 程序计数器:指向进程下一条要执行的指令。
- CPU寄存器:存储进程在执行过程中使用的寄存器值。
- 内存管理信息:如进程的内存分配、页表等。
- 调度信息:包括优先级、调度队列等,用于进程调度。
- 进程标识符(PID):唯一标识进程的编号。
- I/O状态信息:指示进程使用的输入输出设备、I/O操作的状态。
- 父进程ID:如果该进程是子进程,还会保存父进程的ID。
- 等其他信息
操作系统通过维护每个进程的 PCB,能够实现多进程管理、进程调度和上下文切换等功能。
linux的PCB(task_struct)
描述进程-PCB
PCB是什么?
在linux系统中,进程控制模块PCB对应的数据结构是task_struct.
struct task_struct {
volatile long state; // 进程的状态
unsigned int flags; // 进程标志,包含进程的各种状态、条件等
int pid; // 进程的 PID
int tgid; // 线程组 ID(主进程的 PID)
struct task_struct *parent; // 父进程指针
struct list_head children; // 子进程链表
struct list_head sibling; // 同一父进程的兄弟进程
struct mm_struct *mm; // 进程的内存管理信息
struct files_struct *files; // 文件描述符表
struct fs_struct *fs; // 文件系统相关信息(当前工作目录、根目录等)
struct sched_entity se; // 调度实体
struct thread_info *thread_info; // 线程信息(包括 CPU 上下文、栈等)
unsigned long start_time; // 进程的启动时间
unsigned long real_start_time; // 进程创建时的实际启动时间
// ... (还有更多的成员)
};
以上是一些task_struct成员,task_struct是PCB的一种进程描述结构体。
为什么要有PCB
操作系统的作用是对硬件进行管理为软件提供服务,那么如何管理硬件呢?六个字“先描述,在组织”,PCB就是对硬件资源的描述来协助管理。
组织进程
在内核源码中,task_struct是以链表的形式存在内核中的。
查看进程
进程信息可以通过/proc文件夹进行查看
当然也可以使用用户级工具top和ps进行查看。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
while(1){
sleep(1);
}
return 0;
}
通过系统调用获取进程提示符
进程(PID)getpid() 和 父进程(PPID)getppid()
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
printf("pid: %d\n", getpid());
printf("ppid: %d\n", getppid());
return 0;
}
创建进程
系统调用fork创建进程
fork()
是一种系统调用,用于在类 UNIX 系统(如 Linux、macOS)中创建一个新的进程。它的基本语法如下:
#include <unistd.h>
pid_t fork(void);
-
#include <unistd.h>
:fork()
是在unistd.h
头文件中声明的,因此在使用fork()
时需要包含这个头文件。 -
pid_t fork(void);
:pid_t
:这是一个整型数据类型,表示进程 ID(PID)。pid_t
是系统为进程 ID 定义的类型。fork()
:该函数不接收任何参数,调用后会创建一个新的子进程,并返回两次。
返回值:
fork()
会根据不同的情况返回不同的值:
-
成功创建子进程:
- 父进程:返回 子进程的 PID(正整数)。父进程继续执行。
- 子进程:返回 0。子进程继续执行。
-
创建失败:
- 返回 -1,并设置
errno
以指示错误原因。常见的失败原因包括系统资源不足或达到进程数限制等。
- 返回 -1,并设置
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main() {
pid_t pid = fork(); // 调用 fork() 创建子进程
if (pid < 0) {
// 如果 fork() 返回负值,表示创建子进程失败
perror("fork failed");
return 1;
}
if (pid == 0) {
// 子进程执行的代码
printf("This is the child process, pid = %d\n", getpid());
} else {
// 父进程执行的代码
printf("This is the parent process, pid = %d, child pid = %d\n", getpid(), pid);
}
return 0;
}
到这个就有问题了?
fork为什么给子进程返回0,给父进程返回子进程pid?
1.代码分流:让父子进程自fork之后执行不同的代码
2.给子进程返回0表示创建成功,给父进程返回子进程pid,便于跟踪和控制子进程。
一个函数为什么会返回两次?
在fork函数return前,已经创建好了子进程。由于刚开始子进程并没有资源,操作系统不会立即为子进程分配完全独立的空间,而是通过共享页面(CR3寄存器存储的子进程的页面指针指向父进程的内存)与父进程进行资源共享。为了避免父子进程互相影响,操作系统通过页表将共享内存权限设置为只读权限。在子进程进行修改数据的时候进行写时拷贝(当父进程或者子进程尝试修改共享内存时,操作系统会创建一个新的内存页,只给修改方分配新的内存副本)。fork函数在执行完成后会修改子进程task_struct中的pid,进行写时拷贝,故而返回两个值。
fork创建子进程后,系统会多一个进程吗?
会,父子进程是两个独立的进程,有自己独立的PCB和代码数据。
父子进程谁先运行
有调度器决定。
进程状态
操作系统描述一个进程在其生命周期中所处的不同阶段,每个进程都会在不同的时间段处于不同状态,操作系统通过这些状态来调度和资源分配。
linux内核代码
/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 正在运行*/
"S (sleeping)", /* 1 睡眠状态*/
"D (disk sleep)", /* 2 正在等待磁盘操作完成*/
"T (stopped)", /* 4 被停止*/
"t (tracing stop)", /* 8 因调试而被停止*/
"X (dead)", /* 16 已死亡*/
"Z (zombie)", /* 32 僵尸进程*/
};
R 运行态
一个程序只要放在CPU上就一直执行它?
并不是,在每一个进程中都有一个叫时间片的概念。
S 浅度睡眠
我们要知道我们有很多进程,我们的硬件资源(如 CPU、内存、I/O 设备等)是有限的,S状态是等待我们某种硬件资源就绪,就比如我们在调用‘scanf’时,如果我们通过键盘输入,该进程就是S
#include <stdio.h>
int main() {
int num = 1;
scanf("%d", &num);
return 0;
}
D 深度睡眠
进程访问磁盘数据时,操作系统无法将起杀死。为什么呢?
举个栗子吧,有一个进程需要向磁盘写入1G数据,在这个时间段中,操作系统发现进程太多了,严重影响执行速度,这是发现“这个进程”,它“占着茅坑不拉屎”,一直在那等。操作系统决定干掉它。这个时候磁盘写入失败了,就去问进程,再次写入呢还是停止,但是发现这个进程被干掉了。得不到响应,它也不知道接下来该怎么办。
正是为了防止这种情况的发生。
T & t 暂停状态
t:调试暂停
T:可通过向进程发送SIGSTOP/SIGCONT信号使其进入停止状态和退出停止状态
死亡状态 X & 僵尸状态 Z
X:表示执行结束或者被干掉,通常指的是进程的终止状态
Z:
#include <stdio.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if(id < 0){
perror("fork");
return 1;
}
else if(id > 0){ //parent
printf("parent[%d] is sleeping...\n", getpid());
sleep(30);/* 等待子进程退出*/
}else{
printf("child[%d] is begin Z...\n", getpid());
sleep(5);
exit(EXIT_SUCCESS);
}
return 0;
}
while :; do ps aux |head -1&&ps aux|grep 进程pid或名称|grep -v grep;echo "###############################################################";sleep 1;done
可通过以上命令循环查看。
注:使用wait()或者waitpid()来读取子进程退出状态。
孤儿进程
父进程提前退出,子进程退出进入Z状态中,此子进程被称之为“孤儿进程”。孤儿进程会被1号init进程领养并回收!
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if(id < 0){
perror("fork");
return 1;
}
else if(id == 0){//child
printf("I am child, pid : %d\n", getpid());
sleep(10);
}else{//parent
printf("I am parent, pid: %d\n", getpid());
sleep(3);
exit(0);
}
return 0;
}
为什么要被init进程领养?
因为子进程要退出,要被释放!
进程优先级
因为CPU资源有限,进程不止一个,所以必定存在竞争关系,操作系统为了保证良性竞争,确认了优先级。
ps -l
可以查看到进程的优先级
NI:表示这个进程的nice值
nice值表示进程可被执行的优先级的修正值。
PIR=PIR(old)+nice。
当nice为负值时,那么该进程的优先级值会变小,优先级会变高,进程越快被执行。
当然,nice也是有范围的,[-20, 19],共40个级别。
注:进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化。
查看优先级命令 -- top
修改nice值
进入top 按‘r’ -> 输入进程PID -> 输入nice值。
环境变量
原文地址:https://blog.csdn.net/HH_KZ1314/article/details/143151823
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!