目录
[gitpid / gitppid](#gitpid / gitppid)
冯诺依曼体系结构(了解)
数据在硬件中的流通
周边知识
- 计算机里几乎所有的设备都有存储数据的能力
- CPU的数据处理能力很强;内存(掉电易失性存储单元);外设,硬盘(ssd固态硬盘),磁盘(永久的存储介质,机械结构)
- 设备交互的本质是数据的拷贝;存储的效率直接决定了拷贝的效率 ,决定了设备和设备间通信的效率
- 数据层面上,当代CPU一般不直接和外设交互,优先和内存交互
- 内存可以理解为一个硬件级别的大的缓存
- 冯诺依曼体系结构的本质是:用较少的钱,做出效率不错的计算机(高性价比)
- 程序运行之必须先加载到内存,使CPU读取其可执行程序的内存和数据(计算机层面都为二进制)
操作系统
- 操作系统是开机第一个加载的软件
- 是一款软硬件资源管理的软件(对下手段),为了给用户提供高效,稳定,安全的运行环境
- 底层硬件采取冯诺依曼的体系结构
- 除了CPU和内存外都需要驱动程序
- ★操作系统内部有大量的数据对象和数据结构
- ★操作系统之上不是用户而是系统调用
如何管理
- 先描述 (硬件的重要属性集合)被管理的对象 ;再通过数据结构将它们组织起来
解释打印
- 往显示器打印的本质是往底层的硬件打印,操作系统不允许用户直接访问操作系统 ,那么更不会允许用户直接访问驱动程序或者对应的硬件 ,几乎用户的所有行为都必须贯彻操作系统,所以会通过printf这个库函数与操作系统的输出功能进行交互,然后由操作系统帮助向硬件打印
- 所有语言中的大部分功能都和系统调用有关,所以printf必定封装了系统调用
- ★系统调用是上层访问下层的唯一通道
★库函数
- 通常调用 系统调用 (因为底层封装了系统调用)来实现更高级别的功能
- 库函数是应用程序调用的函数,通常是由编程语言的标准库或第三方库提供的
★系统调用
- 是最底层的,是操作系统内核提供的接口,是程序与操作系统内核直接交互的接口
- 允许用户的程序请求操作系统执行一些特权操作
- 系统调用通常通过封装在标准库中的函数接口来访问
进程
概念
- 可执行程序被加载到了内存,而操作系统为了更好管理进程,通过创建对应的PCB(进程 = 可执行程序 + 内核数据结构);管理的本质是管理PCB,数据(程序加载到内存的代码段和数据段...(二进制代码和数据))
PCB
- 进程控制块(Process Control Block);进程的结构体对象
- 包含多种属性:id,代码地址,数据地址,进程状态,优先级,链接字段...
- 操作系统通过进程控制块来管理进程
- task_struct是Linux内核中具体实现的PCB
- PCB 是操作系统管理进程的核心数据结构 ,PCB 完全由操作系统内核创建、管理和销毁。因此,PCB 是操作系统内核的一部分
- 描述进程的PCB在排队,而不是程序在排队
结构示意图
task_struct
是用于描述每个进程的通用数据结构,所有进程(无论类型如何)都使用这个结构体- 纠正:如果在task_struct里实现链接,这也是合理的,链接又很多种方式
- ★一个进程的PCB可以在多个链表里,这是操作系统内核管理和调度进程的重要机制之一
- 可以通过cur(list)宏 来得到task_struct的地址
系统调用
监控脚本
- while :; do ps axj | head -1 && ps axj | grep mybin | grep -v "grep"; sleep 1; echo ""; done
- 用于动态观察
- grep命令启动的时候也包含mybin
gitpid / gitppid
- pid:进程标识符,操作系统用来唯一标识每个进程的一个数字
- 通过系统调用拿到pid或者ppid
- kill -9 pid :kill的-9选项可以通过pid杀进程
解释样例
cpp
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("这个进程的pid:%d, ppid:%d\n", getpid(), getppid());
while(1)
{
sleep(1);
}
return 0;
}
- 发现32001是一个叫bash的进程
- bash是shell(命令行解释器)的一种
- 运行的ps axj命令的父进程是bash
- 结论:通过命令行解释器(bash)启动的进程的父进程都是bash(bash在登陆后就不变了)
- 启动进程意味着 进程一般是由其父进程创建
chdir
/proc
- 是一个虚拟文件系统 ,用于提供关于系统和进程的信息,每个正在运行的进程在
/proc
目录下都有一个对应的子目录,该子目录的名称就是该进程的进程ID - 是一个动态的目录结构,存放的是所有存在的进程,目录名就是进程的pid
- ★exe :指向了该进程当前正在执行的可执行文件的路径
- ★cwd:current work directory 当前工作目录
解释样例
cpp
chdir("/home/wzf/Linux");//修改cwd路径
FILE* fp = fopen("test.txt", "w");
- 会在/home/wzf/Linux的工作目录下创建test.txt文件
- 这里的fopen也可以写成./text.txt,就是相对于cwd去比照
- 结论:默认情况下,进程启动时所处的路径就是当前路径
- 结论:每一个进程都要有自己的工作目录
运行起来后删除磁盘中小体积的可执行程序
- 当一个可执行程序被加载到内存中执行时,文件的内容(机器指令,数据段,代码段,堆栈段)已经被操作系统读取并加载到内存中。此时,即使删除了文件系统中的可执行文件,已经在运行的进程不会受到影响
- 如果这个可执行程序不是一次全加载到内存上的,进程当然受到影响
fork
概念
- Linux 系统中,所有进程都是通过 fork() 机制派生出来的
创建子进程的目的
- 让子进程协助父进程完成一些单进程解决不了的任务
- 多进程多在服务端(简单来说需要同时处理多个客户端的请求),客户端较少
工作原理
- 以父进程为模版,复制父进程的虚拟地址空间、文件描述符、寄存器状态等。子进程与父进程几乎完全相同,先全部拷贝到子进程 ,再修改部分信息
程序从上往下运行
- 进程运行时,CPU里有个(指令指针)eip寄存器(x86架构)/pc指针(arm架构),会保存当前正在执行的指令的地址;当处理器执行一条指令时,eip会指向下一条指令的地址;eip寄存器的值 也会被子进程继承
★进程是独立运行的
- ★每个进程有自己独立的上下文 ,包含寄存器状态、内存映射;当进程在 CPU 上运行时,操作系统会加载该进程的上下文到 CPU 中,父进程和子进程虽然共享相同的
EIP
值,但在实际执行时,它们是独立运行的,因为它们拥有各自的进程上下文
样例解释
cpp
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t id = fork();
if(id < 0)
return -1;
else if(id == 0)
{
while(1)
{
printf("子进程的pid:%d, ppid:%d, &id=%p\n", getpid(), getppid(), &id);
sleep(1);
}
}
else
{
while(1)
{
printf("父进程的pid:%d, ppid:%d, &id=%p\n", getpid(), getppid(), &id);
sleep(1);
}
}
}
为什么会fork有两个返回值?
- 因为子进程在fork的return之前就已经创建好了(eip也被拷贝了),所以return语句子进程也要执行,那么就会有两个返回值
fork对于父子进程返回值不一样?
- 返回子进程的 PID,这允许父进程知道子进程已经成功创建,并且可以使用这个 PID 来管理子进程,比如等待它终止或者发送信号
- 在子进程中,
fork
返回0 ;这是为了让子进程识别自己,并执行与父进程不同的操作
为什么id会有不同的值,确又是同一个地址?
- 虚拟地址空间详解
总结
- 了解冯诺依曼结构及周边知识
- 操作系统的了解
- 进程和内核数据结构PCB
- cwd,pwd就是打印出cwd
- ★fork的理解,样例的解释