回顾
对于依赖关系和依赖方法,我们做一个练习回顾一下,如果进行make 操作,目标文件的生成顺序是从上到下还是从下到上? 揭晓答案:从下到上,根据执行顺序,Linux 一开始想要生成mycode 目标文件,但是发现其依赖关系mycode.o 还没生成,所以要先去生成mycode.o ,但是mycode.o 的依赖关系mycode.s 又不存在,以此类推,最终需要先生成mycode.i 。所以是从下到上。
新符号的引入
在Linux 中,为了便捷,提供了两种符号。(其实我一直没记住,Linux 要记得太多了实在是。) $^ :源文件 $@ :目标文件 所以根据这两种符号,在Makefile 文件中就可以用符号代替了。
在运行的时候$^ 和 $@ 会自动被替换成源文件和目标文件
stdout,stdin,stderr
一般的语言都会默认打开三个输入输出流: 1.stdout 标准输出 2.stdin 标准输入 3.stderr 标准错误输出
因为计算机在处理数据的时候需要输入数据,输出结果,纠错,所以一般的语言都会默认开启。
缓冲区
在了解缓冲区的概念之前,我们需要重新了解两个名词:回车,换行。
回车 :光标回到当前行的开始 换行 :列不变,光标跳到下一行 在C语言中,\r是回车不换行,而\n是回车换行
接下来我们会通过两段代码来引出缓存区的概念,大家可以根据两段代码的运行结果来找出区别。
??
(
1
)
(1)
(1)代码一
代码一直接就打印出结果了,这对于我们是好理解的。
??
(
2
)
(2)
(2)代码二 没有先打印出Hello Linux! ,但是按理来说不是应该先打印吗?
两段代码的唯一区别就是第一段代码带了回车换行,而第二段代码没有。
问题:难道是因为sleep 比printf 先运行了,所以没有在显示器上显示出来吗?
答案:一定是printf 先运行的,printf 其实已经运行了,只不过运行的结果存储在缓冲区中,还没有刷新到显示器上。
最终可以得出两个结论: 1.显示器设备的刷新策略:遇到\n 立即刷新。 2.如果没有遇到\n ,这种字符串类型的数据会被暂时保存到缓冲区内,待程序执行完以后被刷新到显示器上。
缓冲区:暂时简单理解为一个临时保存数据的地方。
在Linux 中,刷新回车换行,也可以使用函数。 头文件:#include<unistd.h> 格式:fflush(stdout) 功能:进行一次设备刷新
倒计时和进度条练习
在理解了回车,换行,缓冲区以后,我们可以根据这些进行一个练习:
(1)练习一:完成一个倒计时的功能
#include<stdio.h>
#include<unistd.h>
int main()
{
int count=9;
while(count--)
{
printf("%d\r",count);
fflush(stdout);
sleep(1);
}
}
??
(
1
)
(1)
(1)输出结果并回车 ??
(
2
)
(2)
(2)刷新显示设备 ??
(
3
)
(3)
(3)间隔一秒继续运行程序
(1)练习二:完成一个进度条的功能
效果如下:
#include<stdio.h>
#include<string.h>
#include<unistd.h>
int main()
{
char buf[100];
memset(buf,'\0',sizeof buf);
int i=0;
while(i!=100)
{
printf("[%-100s][%-2d%%]\r",buf,i);
fflush(stdout);
buf[i]='#';
i++;
usleep(50000);
}
printf("\n");
return 0;
}
现在也需要知道三个概念: 1.凡是显示到显示器上的内容都是字符 2.凡是从键盘读取的内容都是字符 3.键盘和显示器属于字符设备
git四板斧
格式:sudo yum install git 功能:安装git工具
(1)第一板斧 git clone +gitee仓库的链接 功能:在本地创建远端仓库 (2)第二板斧 格式:git add 文件/目录名 功能:将需要git管理的文件告诉git
(3)第三板斧 格式:git commit -m "本次上传的日志信息" 功能:提交本次上传的日志信息
(4)第四板斧 格式:git push 功能:更新gitee的对应仓库
格式:git status 功能:查看当前提交进行的状态
git log 格式:查看曾经gitee的push日志信息
现在我们进行一次完整的gitee 远端文件上传: git gitee仓库的链接 cp 上传的文件名/目录名 gitee的仓库名
查当前的上传状态,系统告诉你需要add 文件/目录。 git add 上传的文件/目录名 git commit -m "本次上传的日志信息" 初次使用git 会失败,你需要告诉git 你的邮箱和用户名。 所以需要两条命令: 1.git config --global user.name "用户名" 2.git config --global user.email "邮箱" 用户名和邮箱可以随意设置,以后查看日志信息的时候能知道具体提交者的信息就行。 最后输入git push 更新一下仓库的状态,gitee 的对应仓库就会被push进上传的文件。
冯诺依曼体系结构
当代计算机的设计大致都是依照冯诺依曼体系结构进行设计的:
对于冯诺依曼体系结构目前我们有一个大概的了解就行: cpu=运算器+控制器 内存/主存 :内存我们可以类比手机中的运行内存。 辅存/硬盘/磁盘 :辅存也就是我们常说的硬盘,磁盘。 输入设备 :键盘,磁盘,网卡,显卡,话筒,摄像头… 输出设备 :显示器,磁盘。网卡,显卡,音响…
运算器 :进行加减乘除这类算术运算和与或非的逻辑运算。 存储器 :存储数据和程序输入设备 输入设备 :将信息转换成机器能识别的形式 输出设备 :将运行结果转换成人们能够识别的形式 控制器 :指挥程序运行
接下来我们通过一个样例来描述冯诺依曼体系结构的工作过程: 我们用平常qq的聊天进行举例: 背景:大雄准备用qq发一条信息给哆啦a梦。 1.大雄通过键盘输入要发送的信息。 2.信息流入存储器中(qq运行的时候需要加载到内存中,其实写入信息还是写入到内存中)。 3.信息流入cpu中,cpu对其进行计算,进行加密…工作。 4.计算过的信息流回存储器。 5.存储器将信息发至网卡,信息最终从网卡发出。 6.信息经过网络发送到哆啦a梦的网卡。 7.信息从网卡流到存储器。 8.信息进入cpu进行解密…工作。 9.计算过的数据流回存储器。 9.最终信息通过存储器流到哆啦a梦的显示器上。
可以得出一个结论: 计算机的工作中,基本的数据传输一般都得先经过存储器。
初识操作系统
操作系统是什么?
概念:操作系统是一款专门针对软硬件进行管理工作的软件。
操作系统存储在哪里?
磁盘中,当计算机启动时操作系统本身也会被加载到内存中。
操作系统启动的作用:
将软件的数据与代码,加载到内存中
为什么需要操作系统呢?
如果没有操作系统的存在,我们面对的就是一堆硬件,对计算机进行操作就需要和这些硬件进行打交道,这样成本非常高,需要了解硬件的一些原理和组成。所以在很久以前,计算机是一些科学家在使用,操作系统的出现一定程度上也推动了计算机的大众化。
操作系统是如何做到针对软件和硬件进行管理的? 首先我们需要知道管理 这个词的概念。
管理=决策+执行 。
这里我们以学校的管理进行详细的解释。
我们将学校简单看作由三类不同身份的人组成: 1.校长(管理者) 2.辅导员(执行者) 3.学生(被管理者)
所以:我们可以简单的明白三者的关系 校长 :发布决策,通知辅导员。 辅导员 :作为决策的接收者,辅导员需要执行校长发布的决策。 学生 :决策的执行就是对应学生的管理。 问题又来了:你告诉我校长通过决策,间接来管理学生,但是校长又没见学生本人,他又是怎么管理学生然后做出决策的? 辅导员的又一作用出来了:告诉校长学生的详细信息。
所以这时我们可以站在校长的角度上来分析管理 这一操作: 1.对于校长本人来说,校长根本就不用见到学生本人,因为辅导员会汇总学生的所有信息到校长那里,校长只需要根据每个学生的信息对其做出定制 的决策。
举例说明:小明是一个非常努力的学生,这次比赛小明得了国家一等奖,辅导员将这一信息汇总给校长,校长得到这一消息后非常高兴,并决定给小明奖励8000元。对于这个例子,校长本人并没有见到小明,但是校长有小明的信息数据,所以校长可以根据这些信息数据做出决策。
2.一个学校可能有成千上万的学生,信息量也是非常巨大的。每个学生都有自己的信息。所以对于校长来说,其需要对每个学生的信息数据进行聚合,将一个学生的所有信息数据聚合在一起。但是即使如此,每个学生之间的信息还是非常散乱没有联系的。这时候校长就可以通过一些数据结构将学生的聚合数据联系在一起,例如数据结构中的双链表。
现在,我们将这个例子融入计算机中。 校长(操作系统) 辅导员(驱动程序) 学生(硬件)
操作系统作为计算机中硬件的管理者,其不会与硬件直接进行打交道,每个硬件都有自己的驱动程序,操作系统可以根据每个硬件的属性信息做出决策来让驱动程序间接管理硬件。
所以我们最终得出操作系统的作用: 1.对于下层:操作系统需要管理软硬件的资源。 2.对于 上层:给用户提供一共安全,高效,稳定的运行环境。 下层的操作最终是为了上层的安全和稳定。
并且对于计算机中的管理我们可以得出以下结论: 1.管理的本质是先描述后组织。 描述:将一个硬件的所有数据和属性聚合在一起。 组织:通过数据结构让硬件的聚合数据之间产生联系。
2.管理者不直接和被管理者打交道,管理者通过被管理者的信息间接进行管理。
但是,在计算机的使用中,操作系统又是如何执行用户的一些功能的呢? 首先,需要知道一个概念: 操作系统不相信任何一个用户。 因为一旦要是存在恶意用户就会对操作系统本身造成危害。 所以操作系统提供了很多不同功能的系统调用接口。 但是由于系统调用接口比较复杂,所以一些人对这些系统调用接口进行了第三方库封装,通过这些第三方库可以间接的去调用系统调用接口完成相应的操作。 我们以printf("Hello Linux!") 为例,对于C 语言,我们看似是调用了printf 函数,但其实printf 函数的底层还是需要去调用系统调用接口,接着操作系统会为我们进行一系列的操作,最终将内容打印到硬件上,这一过程都是操作系统完成的。
进程
操作系统除了需要管理硬件之外,对软件也需要进程管理,如进程,驱动。
操作系统对于软件的管理和硬件相同: 先描述,再组织
那么,何为进程? 教科书的定义:加载到内存的程序就是进程。
从这句话我们能得到一些信息,进程的组成中包含了程序这一组成部分。 由此我们需要重新了解程序的本质:
下面的这段代码经过编译,链接以后生成的.exe 结尾的可执行程序会被存储在磁盘中,所以程序的本质其实就是文件。
#include<stdio.h>
int main()
{
printf("Hello Linux!");
}
在明白了程序的本质以后,我们就可以开始了解一个程序运行的过程: 1.操作系统会将程序从磁盘加载到内存中。 2.操作系统会为程序对应的进程创建一个PCB (进程控制块)。 这里的PCB 又是什么呢? PCB (进程控制块)描述了这个进程的属性和代码(可以简单看作是程序带有的属性和代码),PCB 在Linux 中的具体名称为task_struct ,其是一个结构体: ps::PCB 是所有进程控制块的总称,而task_struct 是Linux 具体用的一种。
所以: 进程=程序+该进程对应的数据结构
struct task_struct
{
};
操作系统对于进程的管理可以得出两个结论:
操作系统只需要操控task_struct 进程控制块就可以控制进程的运行,并且也可以通过task_strutc 找到对应的进程。为了方便管理,不同的task_struct 之间会使用数据结构进行关联:例如队列。
进程的管理任何和进程对应的程序无关,和进程对应创建的PCB 有关 操作系统不关心程序的代码和数据,这是CPU 需要运行的,操作系统只关心PCB 里存储的进程属性信息。
task_struct里都包含了哪些属性?
task_struct 里包含了很多的属性信息,这里我们进行大致的介绍:
task_struct的内部构成: 标识符PID:标识进程,用来区分不同的进程
状态: 任务状态,退出代码,退出信号等。 对于进程的状态我们后面会再详细了解。 这里我们引入退出码的概念: 平常我们写代码的时候一般会写return 0,0就是退出码,task_struct里包含了退出码这个退出信号,当程序执行到return的时候,操作系统就会得到退出码,最终将退出码给父进程。 通过echo $?命令可以查看最近执行一次命令的退出码
程序计数器: 程序中即将被执行的下一条指令的地址。 在CPU中有一个pc计数器,其指向需要执行的命令,当执行完当前命令以后,其就会自增指向下一条命令。
优先级:进程运行先后的问题。
内存指针:通过task_struct中的指针找到对应进程。
I/o状态信息:一些输入输出的状态信息。 IO的本质:将输入设备的数据读到内存,将数据 输出到输出设备
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
上下文数据: 进程执行时产生的一些临时数据。 在理解上下文数据之前,我们需要先知道一些概念: 时间片:CPU调用进程单次最多进行的时间。 进程切换:单个CPU的计算机在调用进程的时候,每个进程都是具有优先级的,类似排队的过程,操作系统会设置一个时间片,每个进程单次调用时间超过时间片或者提前完成任务以后都会被切换到队尾进行重新排队。接着CPU会调用下一个进程。这就类似我们体育课玩游戏的时候,玩好的人需要到队伍的尾巴继续排队。
CPU 在调用进程的时候会根据时间片进行进程的切换,但是进程在运行的时候会 产生临时数据,这些临时数据的一部分会暂时保存在CPU 的寄存器中,当进行进程切换时,该进程需要将这些寄存器的临时数据拷贝到自己的task_struct 中,当再次轮到该进程以后会将这些临时数据写回CPU ,CPU 就可以根据这些临时数据知道当前应该从哪开始继续调度进程。这就是上下文数据。 其实这就像我们看书的时候,会记录下看到了第几页,下一次看书就直接从那继续看就好,有了上下文数据,进程就不用从头开始被重新调用。
查看进程
格式:ps axj 功能:查看运行的进程 这种方法我们前面讲过,可以搭配管道和grep 进行进程的过滤。 今天我们讲一种新的查看进程的方法。
格式: ls /proc 功能:查看运行的进程 注:proc 的中文解释为进程,在Linux 中存在这样的一个进程目录来存放进程的一些信息。 Linux 下的proc 目录会存放进程,蓝色的编号都是一个个进程的pid ,当我们启动某个进程的时候,Linux 就会在prco 目录下创建一个目录,该目录名就为进程的pid 。 进程运行结束,对应目录也会被删除。
通过ll 命令查看进程信息: 对于进程的这些信息当前我们只关系两个: 1.exe exe 表明了当前运行的程序的存储路径和程序名。 2.cwd cwd 表明当前程序所处的工作目录。
这时我们就可以知道文件创建的一个原理: 我们使用文件操作创建一个文件时,为什么该文件默认创建在当前目录下? 因为每个进程运行起来都有一个cwd ,进程会根据cwd 在此路径下创建文件。
通过查看进程信息还能获取到一些信息: 一个进程是具有父进程的,对此我们后面会详谈。
格式 kill -9 进程的pid 功能:干掉对应进程
对于进程的查看还有一些函数: 头文件:#include<unistd.h> 函数:getpid() 功能:返回当前进程的pid 函数:getppid() 功能:返回父进程的pid
总结
??
(
1
)
(1)
(1)验证
在明白了什么是进程以后,我们可以验证前面的结论: 我们的操作其实都是间接调用系统函数接口,让操作系统帮我们完成操作。 对此我们可以进行验证: 查看test 可执行程序的进程编号和父进程编号。 我们发现其父进程是bash 。我们之前说shell 帮助我们和操作系统进行交互,其实也就是帮助我们调用系统函数接口。
??
(
2
)
(2)
(2)进程总结 进程无处不在,我们对计算机的操作基本都可以看作是一个进程,只不过其可能运行的时间很短,如我们打开文件夹,使用ls 命令,这些都是一个个进程。
。
??
(
2
)
(2)
(2)操作系统 操作系统的管理总是保持一个原则:先描述,再组织 操作系统不会直接管理某个软件或者硬件,都是通过管理存储该软件/硬件属性信息的结构体,对里面的属性信息进行管理,从而达到管理软件/硬件的目的。
|