一、冯诺依曼体系结构:

输入设备:鼠标键盘
输出设备:显示器
CPU=运算器+控制器
存储器:内存
CPU获取写入只能从内存进行,CPU执行我们的代码,访问数据。
冯诺依曼体系结构规定程序必须从外设加载到内存,软件运行必须先加载。
数据流动的本质是从一个设备拷贝到另一个设备。
二、操作系统:
1.概念:任何计算机系统都包含⼀个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括:
• 内核(进程管理,内存管理,⽂件管理,驱动管理)
• 其他程序(例如函数库,shell程序等等)
2.设计Linux的目的:

在整个计算机软硬件体系结构,整个过程被设计成层状结构。
所以每层之间都可以联系起来而且一层出现问题可以单独对这层进行解决且不影响其他层,高内聚低耦合。
操作系统不允许用户直接访问内存直接读取进程,直接访问文件,访问操作系统必须使用系统调用,其实就是函数,只不过是系统提供的。
程序被判断要访问硬件就必须贯穿整个软硬件体系结构。
因此操作系统对上为用户程序提供良好的执行环境,对下与硬件交互,管理所有的软硬件资源。
3.核心功能:在整个计算机软硬件体系结构中,操作系统的定位是一款纯正的搞管理的软件。
4.理解管理:
假如学校里有校长、辅导员和学生三种身份,现在校长要管理学生,需要辅导员去执行获取学生数据的操作然后交给校长,校长用execl表格管理起来。管理者可以不用和被管理者见面,管理者根据数据管理被管理者,通过中间层去获取数据。但是假如学生变多了,多了几万名,校长就要对execl表格去从头到尾再遍历五万次,工作量变大了,这时候就换一种方式去管理,不用execl表格去管理而用数据结构去管理。创建学生的结构体,里面有学生的各项指标,还要创建一个指针来链接下一个学生单位,最后形成一个链表。
cpp
struct stu
{
//姓名
//年龄
//学号
struct stu* next ;
}
这样就可以用结构体去描述每一个具体的学生,每个节点都有学生的基本属性,然后进行数据的管理。于是校长管理学生的工作就变成对链表的增删查改。总结一下就是校长管理学生就是先描述再组织。先描述再组织就可以完成现实世界对任何场景的管理建模。类比到操作系统,操作系统可以把网卡、硬盘、其他部分定义一个类,类里面包含每种硬件的各种属性,每个设备都对应操作系统定义的这个类的对象,再由操作系统去管理起来,转换为对硬件的增删查改。操作系统对进程的管理也是先对每一个进程定义类,把进程相关的属性放在结构体里,然后把每个进程链接起来,把对进程的管理转化为对链表的增删查改。这也是C++要提供类和STL,就是为了可以先描述再组织,类进行描述,STL提供方法和容器去组织。任何面向对象的语言都要提供类+容器。
5、理解系统调用:
(1)操作系统要向上提供服务,但操作系统又不相信任何人或用户,于是操作系统就提供了系统调用。未来要获取操作系统内部的数据,设置自己的信息,最终全部都是用系统调用完成的。
(2)操作系统使用C写的,所有提供的系统调用是C语言风格的函数。
三、进程:
1.基本概念:
• 课本概念:程序的⼀个执⾏实例,正在执⾏的程序等
• 内核观点:担当分配系统资源(CPU时间,内存)的实体。
• 当前:进程=内核数据结构(task_struct)+⾃⼰的程序代码和数据
将一个程序从硬盘加载到内存,但是从硬盘加载的程序不止一个,内存中有很多被加载的可执行程序。在没有启动软件时,操作系统就已经在最开始加载到内存了。如下:

这些可执行程序在内存当中都需要申请内存、释放内存,需要被管理和调度。操作系统就要对这些可执行程序进行管理,也就要先描述再组织。

操作系统为程序创建一个结构体类型的对象,将信息填好就有了对应的一个节点,每一个节点都有自己的指针就可以指向自己对应的代码和数据,程序的所有属性都可以在自己的节点里找到,在操作系统内就形成了一个加载到内存当中的程序列表,叫做进程列表。

所以进程不是硬盘里的可执行文件加载到内存的这一段代码数据,而是红色方框圈起来的这一个叫进程。进程=内核数据结构对象+自己的代码数据。上面那个用来描述的结构体在所有操作系统里叫做PCB,中文就叫进程控制块。在Linux下,这个结构体具体叫做task_struct。进程的所有属性都可以直接或间接通过tast_struct找到。于是进程=PCB+自己的代码数据。在操作系统内部管理进程都变成对进程列表的增删查改。
2.task_struct(PCB的一种)
• 标示符:描述本进程的唯⼀标示符,用来区别其他进程。
• 状态:任务状态,退出代码,退出信号等。
• 优先级:相对于其他进程的优先级。
• 程序计数器:程序中即将被执行的下⼀条指令的地址。
• 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
• 上下文数据:进程执行时处理器的寄存器中的数据。
• IO状态信息:包括显示的IO请求,分配给进程的I∕O设备和被进程使用的文件列表。
• 记账信息:可能包括处理器时间总和,使⽤的时钟数总和,时间限制,记账号等。
• 其他信息
- 查看进程:
*历史上执行的所有指令、工具、自己的程序都是进程。
*get pid 获取当前进程的标识符,只要是一个进程就要有自己的id

在运行myprocess时输入命令ps ajx查看当前进程,如果只想要查看myprocess的进程只需要在后面加上 | 跟上对应程序名。
也可以输入head -1取出属性列再打印:

*ctrl C 是杀掉进程的,除了这个还可以用kill -9 对应进程标识符: 
*查进程:ls /proc ,将内存的进程以文件的方式显示出来。proc记录的是当前系统里所有进程的信息,每个数字目录都是进程的pid,每个数字目录的内容都包含这个进程在运行时的动态属性,进程要是运行结束就会自动从proc这里移除,如果再启动就会出现在proc里。

ls /proc/pid -l:获取进程的信息
其中cwd记录了当前程序所在的路径,exe表示可执行文件。这也是为什么C语言时fopen只写文件 名就可以在当前路径下创建文件。
每一次启动父进程id都不变并且这个父进程是bash也就是命令行解释器。

每一次登录云服务器时,系统都会给用户分配一个bash。
光标会卡在这里就是为了等待命令行的输入然后传给bash。
4.代码创建子进程:fork
在myprocess.c中写入如下代码:
cpp
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
printf("父进程开始运行,pid:%d\n",getpid());
fork();
printf("进程开始运行,pid:%d\n",getpid());
}
结果是父进程的会出现两次,子进程出现一次。是因为子进程没有自己的代码和数据,没有程序新加载。

更新一下,让父子进程执行不同代码区块:
cpp
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
printf("父进程开始运行,pid:%d\n", getpid());
pid_t id = fork();
if (id < 0)
{
perror("fork");
return 1;
}
else if (id == 0)
{
//child
while (1)
{
sleep(1);
printf("我是一个子进程 !我的pid:%d,我的父进程id:%d\n", getpid(), getppid());
}
}
else
{
//father
while (1)
{
sleep(1);
printf("我是一个父进程 !我的pid:%d,我的父进程id:%d\n", getpid(), getppid());
}
}
printf("进程开始运行,pid:%d\n", getpid());
}

问题:
1.为什么fork给父子返回不同的返回值:
父进程:子进程=1:n ,未来创建子进程时,可能有多个子进程,要把子进程的pid返回给父进程,父进程要通过pid来区分不同的子进程。
2.为什么一个函数返回两次:
一个函数执行到return就完成了。
fork申请新的PCB,拷贝父进程的PCB给子进程,把子进程放入进程列表当中,最后fork() return id。在返回id之前,子进程已经被创建甚至被调度,于是就有父子两个流,这两个流共享fork()创建完子进程后的语句,由于return id是语句,所以被共享,所以父子都会执行,所以被返回两次。
3.为什么一个变量,既等于0,又大于0,导致if else同时成立:
结论:进程具有独立性。父子在数据层面上默认是共享的,一旦进行数据修改,操作系统把被修改的数据底层拷贝一份,让目标进程修改这个拷贝。返回的本质也是写入变量,哪个先返回就先拷贝,底层发生写时拷贝父子就会拿到不同的变量。