Linux总结(一)

操作系统

操作系统简称OS,主要作管理工作,是进行软硬件资源管理的软件。

Linux稳定、高效、安全、免费,所以大部分企业都选择Linux作为操作系统,其内核是用C语言写的。

Linux的应用场景:

1.作为企业的服务器后台

2.使用在嵌入式设备

3.新兴的入网家用设备(只能家居)

4.车载系统

5.手机操作系统(安卓)

任何一个计算机都离不开操作系统

Linux的目录和文件的组织结构是多叉树的形式,大部分操作系统都是树形结构,因为树形结构便于查找。

与Windows系统一样,在特定范围内,所有的文件都必须得有唯一的标识形式。

计算机里的分层结构

操作系统用来做决策、定规则,驱动程序做执行、定计划、交给硬件。

每个硬件都有自己的驱动程序,有了驱动程序,操作系统才有控制各种硬件的基本接口,才可以做管理。

目标:操作系统对上(用户)提供一个良好的稳定的运行环境,这是操作系统的目标。操作系统对下管理好软硬件,这是操作系统的手段和方式

隐藏文件及.和..

Linux中常将一些配置文件设置为隐藏文件。(默认情况下,以.开头的就是隐藏文件)

Linux的任何目录下,都默认存在两个目录:.和..。(二者都属于隐藏文件。)

其中.表示当前路径,..表示上级路径。

cd ..可以回到当前目录的上级路径

使用.可以限定我们要执行的可执行程序在什么位置。(我们一般在./可执行程序时使用.)

/home/XXX:就叫做XXX这个用户的家目录(工作目录)。

./告诉系统运行的资源在当前目录下,如果省略,部分情况下,默认就是当前目录

绝对路径与相对路径

绝对路径:从根目录开始找文件的路径。很少在日常操作中使用,一般在配置路径、配置文件时使用。
相对路径:不以根目录开始,而是以当前路径为参考点。主要用于日常操作。

Linux系统中,磁盘上的文件和目录被组成一棵目录树,每个节点都是目录或文件。

根目录、路径分割符

路径分隔符是一种标识符,用来标识文件所处的特定位置,以此来读取服务器上面的数据信息。

若只有一个/,叫根目录。Linux由根目录开始。
/有两种用法:根目录和路径分割符

linux中的路径分隔符为:/

windows中的路径分割符为:\(反斜杠)

如果网址带/,则说明其服务器使用的操作系统是Linux。带\则说明操作系统是Windows。

Linux的环境安装有三种:1.虚拟机(不太推荐,安装麻烦,实验不方便)2.双系统(Windows->Linux)特别不推荐3.云服务器(特别推荐)因为它不用安装,网络通畅,便于操作与实验,环境统一

一般云服务器是默认没有桌面的,虚拟机有

文件=内容数据+属性数据

如果我们在磁盘上新建一个文件夹,该文件是会在磁盘上面占空间的。文件的属性是数据。

文件=内容数据+属性数据

Linux基础指令

 

09.mv指令(重要):

mv命令是move的缩写,可以用来移动文件或者将文件改名(move (rename) files),是Linux系统下常用的命令,经常用来备份文件或者目录。
语法: mv [选项] 源文件或目录  目标文件或目录 
功能:
1. 视mv命令中第二个参数类型的不同(是目标文件还是目标目录),mv命令将文件重命名或将其移至一个新的目录中。
2. 当第二个参数类型是文件时,mv命令完成文件重命名,此时,源文件只能有一个(也可以是源目录名),它将所给的源文件或目录重命名为给定的目标文件名。
3. 当第二个参数是已存在的目录名称时,源文件或目录参数可以有多个,mv命令将各参数指定的源文件均移至目标目录中。

mv:转储特定的一个文件或者目录到其他的路径下。对一个文件或目录进行重命名(剪切及改名eg.mv hello.c world.cpp)
常用选项:
-f :force 强制的意思,如果目标文件已经存在,不会询问而直接覆盖 
-i :若目标文件 (destination) 已经存在时,就会询问是否覆盖!
举例:

cat常用选项:
-b 对非空输出行编号 
-s 不输出多行空行

17.find指令:(灰常重要) -name
Linux下find命令在目录结构中搜索文件,并执行指定的操作。
Linux下find命令提供了相当多的查找条件,功能很强大。由于find具有强大的功能,所以它的选项也很多,其中大部分选项都值得我们花时间来了解一下。
即使系统中含有网络文件系统( NFS),find命令在该文件系统中同样有效,只你具有相应的权限。 
在运行一个非常消耗资源的find命令时,很多人都倾向于把它放在后台执行,因为遍历一个大的文件系统可能会花费很长的时间(这里是指30G字节以上的文件系统)。
语法: find pathname -options 
功能:用于在文件树种查找文件,并作出相应的处理(可能访问磁盘) 查找文件是否存在,根据名称进行查找

24.关机
语法:shutdown [选项] ** 常见选项:**
-h    :  将系统的服务停掉后,立即关机。
-r    :  在将系统的服务停掉之后就重新启动
-t sec : -t 后面加秒数,亦即『过几秒

权限

如果别人在我的目录下创建了文件,即使我们没有该文件的任何权限,我们还是可以删掉它

我们设置权限可以保证互相之间的读、写、执行,但是我们阻止不了别人删除。因为这不取决于文件的属性,而是取决于该文件所在的目录对于特定用户的权限。我们不用担心别人删掉我们的文件,因为他们连进都进不来。我们担心的是和别人在系统某一特定路径特定目录里共同维护文件时,这种情况下,我们虽然可以不让别人看,但是阻止不了别人删我们的文件。(即大家所有用户都在一个共同的路径下,且对该目录都具有读写执行权限这种情况。如果我们只是在自己的家目录,别人连进都进不来)

系统中的tmp目录就是系统中存在的所有用户共享的目录,几乎包含所有人形成的临时文件(所有用户)

当tmp取消掉other的w权限后,我们自己都删除不了自己的文件了

当多个用户共享一个目录,需要在该目录下进行读、写、创建删除文件。但是自己只能删除自己的,而不能删除别人的(w:可以互删,但是不满足条件)。为了解决这种情况,实现这种情况,使用粘滞位就可以

最后一位为t,这个t是x的一种特殊情况。设置了之后,就只能自己删除自己的文件了。

注意,粘滞位只能给目录设置

其他用户是不能去掉你的目录的粘滞位的,。

粘滞位一般是谁设置,谁取消(root)

vim

vim是一款多模式的编辑器(也就是只能写代码)。vim默认打开后是命令模式。输入i后变为编辑/插入模式。按esc返回命令模式。命令模式中按shift+;会进入底行模式。esc回退到命令模式。 插入模式无法直接进入底行模式

!是强制的意思。

nyyp:从当前光标所在行复制n行,p代表粘贴

 

删除:dd

删除6-13行:8dd

剪切:8dd p

 shift+~:快速的大小写切换,按住shift,然后按·就可以

ctrl按住不动,快速按两下w,就能在分屏时切换光标所在屏幕

!cmd:不退出vim而执行对应的命令行。按回车即可回到vim

第五节2:44:00vim配置

gcc和g++

 gcc -v查看gcc的版本

gcc的常规编译

上面是直接把我们的文本文件变成了可执行程序。

我们其实可以先-o形成可执行程序(以下两个效果是一样的),然后

gcc -E mytest.c:把处理的结果打印到显示器上。

我们可以这么写,不让它把处理结果打印到显示器,而是放到一个临时文件mytest.i中

 也就是-E代表预处理完成后的结果。-S代表编译完后的结果(即编译成汇编语言)。-c代表汇编完成后的结果。(预处理完后还是C语言)下面的那个.o叫做可重定位目标文件,这个文件在经过汇编后已经变成了二进制

这个二进制文件不能被直接执行

最后一步:把该二进制文件编译成可执行程序(gcc会帮我们自动链接) 

动静态库

第六节00:41:00-第七节00:47:00

make和Makefile

make是一个命令,Makefile是一个文件

其存在的意义是帮助我们自动化的构建项目

先看Makefile:

Makefile可以首字母大写,也可以小写。

编写Makefile。Makefile中最重要的是要包含两种东西:依赖关系和依赖方法。

编写Makefile的目标是为了自动化的构建项目。也就是我们希望通过Makefile帮助我们把我们的源代码编译,自动形成可执行。 

依赖关系:我和我家人。依赖方法:爸给我打钱。总不能别人向我家要钱吧。

要形成mytest,是要依赖test.c的。这个就叫做依赖关系。冒号左侧的叫目标文件,右侧的叫依赖文件列表。第二行以tab键开头,第二行编写的是依赖方法,也就是怎样从test.c形成mytest.c

清理这个名字可以随便写,我们这里写clean。我们一般带一个.PHONY,它是Makefile中的一个类似于关键字的东西,表示clean为伪目标。第八节00:07:00

 Makefile在自上向下扫描时,只会使用一对依赖关系和依赖方法

 

 伪目标最大的特点是:总是被执行的。

 

第八节00:20:00-完没看

yum

yum类似于应用商店

yum是用Python写的一个小工具。以上的是国内的yum源

git

版本控制

简单来说就是不断备份自己曾经的版本,万一以后要用到之前的版本。这就叫做版本控制

能够帮助我们实现版本控制的就叫做版本控制器,一般有git和svn。

git既是客户端,也是服务器

 git仓库其实就是.git文件

git add.就是把当前目录下没有添加到git的全部添加进去

git commit -m "日志(也就是这次提交做了什么改动)"      #这个-m是必须写的,它是我们的提交日志 

本地仓库:就是我们系统中存在的git仓库

我们还需要提交到远端仓库

.gitignore就像黑名单(会被传到本地仓库,但是不可以被传到远端文件)

 删除本地和远端仓库的文件

即删除后重新push一次,因为git只会记录变化部分,其他都是默认覆盖 

gdb

第九节3:09:00-第十节1:05:00

冯诺依曼体系结构 

在冯诺依曼体系结构中:

1.这里的存储器指的(不是磁盘而)是内存

2.不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入/输出设备)

3.外设(输入/输出设备)要输入或输出数据,也只能写入到内存或从内存中读取

即所有被都只能直接和内存打交道 

存储器:内存

输入设备(产生数据):键盘、摄像头、话筒、磁盘、网卡、显卡......

输出设备(保存或显示数据):显示器、音响、磁盘、网卡......

中央处理器(CPU):CPU的核心组件主要有两个,运算器和控制器。

运算器是CPU用来进行算术运算和逻辑运算的。

控制器是CPU用来响应外部事件的。一旦有外部事件到来时,我们可以使用CPU的控制器来完成某些工作。第十节1:27:00

CPU是边运算边收集数据的。CPU可以通过调用控制器,将外部的事件、数据搬到内存里,还可以协调CPU的工作及工作顺序(即通过控制器协调外部就绪事件,比如拷贝数据到内存里)

为什么要有存储器在中间?而不是直接让输入设备产生数据直接到cpu,然后给输出设备显示数据、保存数据呢?即为什么不这样 

如果CPU想要读取(处理)数据(数据+代码),都是要从内存中读取的,所以需要先将外设中的数据加载到内存,CPU才能读取。所以,站在数据的角度,外设只直接和内存打交道,CPU不和外设直接交互(但存在间接交互)。

程序(程序是一个文件,文件都存在磁盘)要运行,必须先被加载到内存中。为什么呢?

因为CPU读取代码和数据只能在内存里读,CPU只能和内存打交道,我们的程序刚开始是文件,文件是在磁盘里,所以当我们想运行时,当想运行程序时,必须把程序加载到内存中,才能被CPU直接读取,CPU才能访问到它的数据。这是冯诺依曼体系结构的特点决定的。

所以我们IO时,都是先I到内存,再从内存中O

这就是硬件决定了软件的一些特性,软件可以用来在硬件级别上对效率做整体的提升。

CPU及其内部的寄存器(CPU内部其实是有很多寄存器的,CPU内比较大的组件有:运算器和控制器)等,运算速度都很快。

CPU&&寄存器 > 内存 > 磁盘/SSD > 光盘 > 磁带    从左到右运算速度越来越慢

既然CPU的运算速度很快,那为什么不直接设计成输入设备->CPU->输出设备这样的体系结构呢?(存储器存在的价值)

虽然CPU的运算速度快,但是其空间小。我们使用内存、磁盘这些,可以有更多的空间。?

虽然内存和cpu运算速度差很多,但是因为有存储器的存在,我们就能在软件上做文章,预先把外设中的数据加载到存储器当中,这样我们的存储器再读取数据时,就可以直接访问存储器了,第十节1:38:50-1:47:30

IO

I(input)指输入设备到存储器(内存)。

O(output)指存储器(内存)输出到输出设备

描述一下两台电脑如何互相发送消息与接收消息,例如:发送数据的一放发出“hello”,数据是如何流向的?先不考虑网络

键盘输入数据进内存,CPU进行计算,CPU计算完后将数据写回内存,(因为是我们发消息,所以会回显到我们的显示器上)再把数据刷新到外设里(网卡)。最终到了另一端主机,一定先是计算机的硬件先收到这个消息,这个硬件一定是输入设备(网卡),数据再从网卡被读到内存里,然后CPU进行运算,再写回内存,再刷新到输出设备(显示器)

就像发快递需要包装,拆快递也需要包装。传文件也是一样,只是输入设备和输出设备改变了而已。

进程

PCB(process control block 进程控制块)

task_struct内容分类(PCB内部属性)

组织进程

查看进程

proc

系统中存在一个proc的目录

ls /proc:proc目录(将当前运行的所有进程显示到文件的系统中。也就是把所有的内存及进程的数据以文件系统的形式展现出来)

ls /proc/18673(即PID)             就能看到该进程的所有属性数据全部写在了这些文件当中

ls /proc/18673(即PID) -al        更详细的信息

其中可能会有一个cwd(当前工作目录,即当前进程的工作目录)

我们所写的代码编译运行后,每个进程都会有其自己的属性,来保存自己所在的工作路径,这个工作路径就是默认的当前路径。这种方法让我们可以以文件的方式查看进程

(默认的)当前路径是由进程决定的,而不是由用户决定的

 大部分情况下都用的是ps命令

让程序获取PID

getpid

进程状态

Linux内核中的一些进程状态

环境变量

有时我们自己定义的变量,本质就是一段内存空间,所以我们可以自己定义一段环境变量,操作系统本身可以自己给自己提供一些变量,操作系统可以将这些变量作为某种参数、数据来使用

初识:系统命令可以直接运行,用户自己写的程序必须带路径。如果不想带呢?

执行程序时,直接带整个路径来执行也是可以的

第十三节2:16:00

为什么我自己的程序想运行得带路径,但系统指令就不需要呢

echo $PATH 

之所以pwd这些系统指令能正常运行,是因为其在环境变量所维护的这些搜索路径下,而我们的myproc.c这些并不在这些路径下,所以得自己带其路径。

如果我们想让它像系统指令一样,我们有两种方法:

1. 把这个写的可执行程序放在上面路径中任一路径下。但是我们不建议这么做

2.把其放在系统环境变量所维护的路径里

给环境变量设置值:

注意,冒号是分隔符 

PATH环境变量决定了我们在Linux操作系统中进行路径搜索时,找特定的可执行程序,我们的搜索路径。

PATH:指定命令的搜索路径

HOME:指定用户的主工作目录(即用户登录到Linux系统中时,默认的目录)

SHELL:当前shell,它的值通常是/bin/bash

查看环境变量的方法:

env:查看系统当中所有的环境变量

echo $NAME//NAME:你的环境变量名称

echo $HOME

echo $SHELL

和环境变量相关的命令

echo:显示某个环境变量值

export:设置一个新的环境变量

env:显示所有环境变量

unset:清除环境变量

set:显示本地定义的shell变量和环境变量

main函数可以带参吗?最多带几个呢?

最多带3个。int main(int argc, char* argv[], char* env[])。char*env[]是环境变量参数。它是每一个进程在启动时,启动该进程的进程传递给它的环境变量信息,都可以以该参数传递进来,其内保存着每一个环境变量的地址,我们可以用循环打印出环境变量。

保存的环境变量被当成字符串再以指针数组的形式(即被char* env[])保存起来,再把对应的数组整体作为参数给对应的main函数传过去。从哪看出它是字符串呢?因为它有/

 C/C++在Linux中,有一个全局变量environ,其包含在头文件<unistd.h>中,该变量会提供一个全局变量,这个变量会帮助我们获取所有的环境变量

 

 实际上,环境变量信息是可以从父进程那里继承下来的,父进程的环境变量信息也是继承下来的,到底(最初)的源头是bash

2:42:00-完     第十三节板书

子进程的环境变量是从父进程来的。默认所有的环境变量都会被子进程继承。环境变量具有全局属性,其本质(因为)是它可以被所有子进程继承。

环境变量通常是具有全局属性的

课件

env只显示环境变量

set:显示的是登录时与bash有关的所有变量

main函数的前两个参数是什么呢?是谁给它们传呢?

argc、argv

命令行参数:在这个程序启动时,我们自己给程序传入的选项

 为什么会存在argv、argc这两个参数呢?

命令行参数的意义在于可以使同样的一个程序通过选项的方式,选择使用同一个程序的不同子功能。即所有命令所带的选项底层都用的是命令行参数完成的,并且命令行参数是由父进程bash先拿到,再给其子进程。也就是说命令行参数也是一种父进程。其实就是一个一个命令和选项。

例如:ls -a -b

ls就是argv[0]。如果只是ls,而什么都没带,argc就是1,其最小是1

程序地址空间(进程地址空间)

 地址空间排布代码

为什么堆栈明明只是申请4个字节,却互相相差10个字节?我们malloc时知道我们申请了几个字节,但是在free时,free怎么知道我们要释放几个字节呢?其实在申请时,申请的比我们要的多,多出来的字节用来记录申请的属性信息,例如时间、大小等等,这部分叫做cookie数据。数据加了static生命周期改变的原因在于它的存储位置发生了变化。只读属性的和字面常量是同一类型,实际上在正文代码中有一部分是字符常量区00:53:00第十五节

第十五节00:49:00如何在vim中注释

栈和堆相对而生

 查看main函数的入口地址。这里用main函数做代码段的代表

 已初始化全局数据区

未初始化全局区

堆区

 

栈区

 

 我们所说的地址空间是内存吗?

不是。

这个地址并不是物理地址,而是虚拟地址(线性地址)。几乎所有的语言,只要它有“地址”的概念这个地址一定不是物理地址,而是虚拟地址,也就是我们之前学的都是虚拟地址。

虚拟地址(线性地址):Linux配合硬件,用软硬件结合的方案为我们创建出来的一种我们对应的地址空间的概念。理解有两种角度:内存级理解和磁盘理解。

我们主要讲内存级理解:例如我们有一个2G的物理内存。其实硬盘、网卡、显卡这种外设,内部也是有各种寄存器的。这些各种寄存器加上内存,统一编制成内存来看,这样我们写入数据时,写入外设的那些数据好像直接写入内存一样。

但是这些内容当中,每一种硬件是不同的,所以我们需要引入虚拟地址。统一将不同硬件和内存编制到一起,地址从0x00000000到0xFFFFFFFF。所以当我们访问某些区域的时候,在虚拟地址这边都像在内存里,在实际中,该在内存的在内存,该在硬件的在硬件。

 $@是冒号前的东西(hello),$^是冒号后的东西(hello.c)

以上这些不同区域,其大小不一定是固定的,有些范围是会变大变小的,是会不断变化的。

所谓范围变化,就是start或end+-某些数进行调整。

每一个进程都有一个指针帮助其和地址空间对应起来。

页表、页匡、页针

地址空间和(用户级)页表是每一个进程都私有一份的,只要保证每一个进程的页表,映射的是物理内存的不同区域,就能做到进程之间不会互相干扰,保证进程的独立性

每个进程都有其对应的地址空间,都有其对应的页表。虚拟地址和物理地址之间用一种映射关系来完成,这种映射关系是被操作系统维护的,是一种表结构,我们称其为页表,此时我们就可以把进程各个区域对应的映射的区域映射到物理内存当中,进而帮我们找到每个区域在物理内存中的各自的数据。(页表即维护映射关系,维护虚拟地址与物理地址的映射关系)

用户级页表:地址空间和页表是每个进程都私有一份

只要保证每个进程的页表映射的是物理内存的不同区域,就能做到进程间互不干扰,保证进程的独立性。

那么现在我们就能回答那个现象,也就是这个(地址一样但值不一样):

 看图:

刚开始创建进程时,只有父进程 ,然后我们创建了子进程,子进程会继承父进程的大多数相关属性,可能会做一些修改,父进程的一个全局变量g_val的虚拟地址映射关系被映射到物理内存中,而它的子进程在刚创建下来时,它们两个的页表对于此变量的映射都同时指向了一个地方,在子进程的虚拟地址中,g_val所处地址和父进程也是一样,它们的值也一样。

但是当子进程修改时,因为要保证进程的独立性,我们的操作系统识别到子进程想要通过页表找到g_val,想修改它的值时,操作系统会让其先不要修改,给它重新开辟一块空间,上面的值有必要的话拷贝过来,然后再把映射关系更改,不要子进程去修改父进程中g_val所指向的值,而是直接让子进程的g_val通过页表映射到操作系统给子进程新开的那个空间。所以在物理内存中,父进程的g_val还是100,没有被修改,子进程的g_val被修改成了200(且其和父进程的g_val不在同一个地方),但是因为它们的虚拟地址不受影响,所以它们的虚拟地址值还是一样的,但是物理地址中的经过页表映射出来的却不一样了,不在同一区域了,所以看到的值便不一样了。

 它们打出的地址一样,内容却不一样,根本原因就是地址一样,打的是同一个虚拟地址(父子进程虽然各自有各自的虚拟空间,但是其同一个地方的该变量的值一样,只是通过页表被映射到了不同区域,所以打印出来的值不同)。

(初识)写时拷贝:当父子进程被创建时,有一个变量经过页表映射后指向同一块物理地址,但是在我们要通过子进程修改其值时发现父子进程的该变量已经不一样了。

难道我们申请内存时真的是在虚拟地址上申请的吗?

当我们的程序在编译的时候,形成可执行程序的时候,没有被加载到内存中的时候,在我们的程序内部,有地址(也就是虚拟地址)吗?其实已经有了

objump:一种反汇编的工具。

VMA:虚拟内存地址

可执行程序编译的时候,内部已经有地址了。地址空间的概念不仅仅是操作系统内部要遵守的,其实编译器也要遵守。即编译器编译代码的时候,就已经给我们形成了各个区域(代码区、数据区......)并且,采用和Linux内核中一样的编址方式,给每一个变量,每一行代码都进行了编址,所以程序在编译的时候,每一个字段早已经具有了一个虚拟地址

第十五节2:36:45。程序内部的地址,依旧用的是编译器编译好的虚拟地址,当程序加载到内存的时候,每行代码,每个变量具有了一个物理地址(外部的,真实的)

当CPU读到指令时,指令内部也有地址,指令内部的地址是虚拟地址,因为我们已经把它加载到了页表当中,通过页表找到了这个指令,CPU拿到以后开始执行这条指令了,相当于我们在编写我们的程序时,就已经按照虚拟地址空间的方案把它读上来了,我们的每一条指令都有其内部地址,不要忘了函数内部可能会发生跳转,所以我们在加载到内存时,是没有改指令内容的,。第十五节笔记2:42:40-完太卡了,鸡不成

 实际上申请空间时,申请空间的大小不只是自己填写的大小,还包含一些空间用来存放变量的属性等等(第15节00:41:30)

为什么局部变量被static修饰后,作用域只能在函数内有效,但是生命周期是全局的。

被static修饰的,编译器视为全局变量。所以static本质是将局部变量转变为全局变量

static本质是将该变量修饰在全局区

vim的一个小技巧:想批量化注释时,进入到命令模式,然后ctrl+v,进入试图模式,然后jkl选中区域,然后输入A,再输入//,再esc即可。如果想取消就u,u不行的话就ctrl+v,再选中,输入d,就可以。

实际上所有的字面常量,都是进代码的。代码是只读的,是不可被写入的,和字符常量是一样属性的,因为它两就在一个区域。其实正文代码上有一个字符常量区,是只读的。所以它们的只读属性不能修改

 用户空间和内核空间

在32位下,一个进程的地址空间,取值范围是从0x00000000~0xFFFFFFFF,我们把0~3GB叫做用户空间,3~4GB叫内核空间。

Linux和Windows

之前的验证代码在Linux和Windows下会有不同的结果,所以之前的结论默认只在Linux下有效

什么是地址空间?

每个人都被预支了所有,互相之间并不影响

地址空间叫进程地址空间。之前打印的所有地址,都是进程在打印地址,所以打印出来的地址一定是程序运行之后打印的(也就是进程。)

在内核中的地址空间,本质将来也一定是一种数据结构,将来一定要和一个特定的进程关联起来。即下图中要把对应的人和饼关联起来。

内存本身是随时可以被读写的。

历史计算机:第15节1:20:00-1:40:00(可以不听)

现代计算机提出了下面的方式:

不再是让用户直接使用物理内存。因为可能不安全。但是数据迟早得在物理内存上。系统存在一种映射机制,其核心工作为

凡是要访问物理内存,需要先进行映射。即让虚拟地址映射为物理地址。通过映射行为,有些非法行为可能会受到约束,就像自己攒压岁钱还是让父母保管压岁钱,花的时候给他们说一声这样,虽然钱是一样的,但是中间有这么一层能更安全(保护时的方式可能是禁止映射),变相的保护了(物理内存)

也就是说,虚拟地址空间有管理的功能,。

虚拟地址空间究竟是什么?

地址空间本质是一种数据结构,用来描述一个进程所能看到的各个区域,默认是全0到全F

映射关系的维护是谁做的?

 如何理解地址空间中的区域划分呢?区域应该怎么理解呢?

 所谓的区域划分本质是在一个范围里,定义出start和end,就叫做区域划分。

每个进程都有地址空间。我们对于地址空间要先描述再组织。

地址空间本质是一种内核数据结构,它里面至少要有各个区域的划分。我们把这种数据结构就叫做地址空间。

区域划分本质就是调整范围,即开始和结束的标志的调整

我们把Linux地址空间内核结构struct mm_struct叫做进程虚拟地址空间。task_struct里有指针指向进程的地址空间的

 

 

进程控制块task_struct指向mm_struct,然后就可以找到这个进程的地址空间了

到16节2:00:00还没看

进程控制

进程的创建、终止、等待、替换

fork

fork基础:fork返回值是什么?为什么有两个返回值?为什么同一个变量会有不同的结果?

为什么同一个变量有两个返回值?

因为它是两个进程,父进程和子进程各自都有地址空间,都有页表,用的虚拟地址是一样的,而物理内存(物理内存是物理地址的一部分)其实是被映射到不同区域的。

请描述一下fork创建子进程,操作系统都做了什么?

fork创建子进程,系统里会多出一个进程,具体需要:进程的PCB结构体,对应的地址空间,对应的页表,并将自己进程中所对应的代码和数据加载到内存,构建映射关系,然后将该进程的PCB放到运行队列里,等待CPU或操作系统调度,一旦其被调度,此时可以通过虚拟地址空间+页表,找到该进程的相关代码,然后从上往下按顺序语句循环判断,函数跳转,来进而在这个进程内部执行这个进程内的代码完成某种业务。

进程=内核数据结构(这个是操作系统维护的)+进程代码和数据(这个一般从磁盘中来,也就是C/C++程序,加载之后的结果)

 创建子进程,给子进程分配对应的内核结构(这个必须是子进程独有的(私有的)),但是因为进程具有独立性,理论上子进程也要有自己的代码和数据。可是一般而言我们没有加载的过程,也就是说,子进程没有自己的代码和数据。所以子进程只能“使用”父进程的代码和数据。

但是说好的独立性呢?

代码:都是不能被写的,只能读取,所以父子共享没有问题。大家都在读而已,互相不怕干扰代码

数据:是可能被修改的,所以必须分离。

如何分离?

对于数据而言:

第一种:创建进程的时候,数据直接拷贝过去,二者互不影响。但是这样的话,可能拷贝到子进程根本就不会用到的数据空间,即使有些用到了,也可能只是读取了一下,只是读取的话其实没必要非得拷贝过来再读,浪费空间(和时间)。有些数据即便以后会使用,子进程也不是一定是要立马使用某些被拷贝过来的数据。

所以创建子进程时,不需要将不会被访问的或者只会读取的数据拷贝一份。那么我们如何判断什么样的数据值得被拷贝呢?将来会被父或字进程写入的数据才值得被拷贝。但是在系统层面,我们很难判断,一般而言,即使是操作系统,也很难提前知道哪些空间可能会被写入。

针对以上问题,操作系统选择使用写时拷贝技术,来将父子进程的数据进行分离。保证在用的时候才拷贝用的部分,其他情况下不拷贝。

操作系统为何要选择写时拷贝的技术对父子进程进行分离?

因为它需要这样:

1.需要使用的时候再给你分配,是高效使用内存的一种表现

2.操作系统无法在代码执行前预知哪些空间会被访问

为什么要使用写时拷贝:

所以操作系统在你想要修改的时候才去拷贝。写时拷贝本质是一种延时申请技术,可以提高整机内存的使用率。

写时拷贝是这么实现的:fork之后,子进程以父进程为模板,把父进程的相关字段拷贝过来。拷贝过来之后,默认情况下它两的指向的内容是完全一样的。并且对页表的权限设置为只读权限。如果有人想修改页表当中的内容或者物理内存中的内容时,比如说修改子进程的。当我们想修改时,会有一个工作,就是把内存中曾经所共享的这块内存区域拷贝一份给子进程,然后修改页表中的页表项,修改完毕后,让我们的子进程的页表指向申请的新空间的地址,这样在数据层面上,两个进程就实现了分离。注意:这个工作时随时随地都有可能发生的,任何父/子进程尝试写入都是会发生拷贝的。只要我们把它分开后,各自页表项当中只读权限去掉了,也就是说父子进程的数据已经分开,互不干扰了,就可以各自进行写入了。

因为有写时拷贝技术的存在,所以父子进程得以彻底分离。完成了进程独立性的技术保证。这就是为什么要写时拷贝(写时拷贝的好处)

写时拷贝是一种延时申请技术,可以提高整机内存使用效率

fork之后,父子代码是共享的(是所有的都是共享的,即fork的前和后都是共享的,那为什么在显示中显示出来的并没有前面代码呢?)

1.我们的代码汇编后,会有很多行代码,而且每行代码加载到内存之后都有对应的地址。

2.因为进程随时可能被中断(可能并没有执行完),下次回来,还必须从之前的位置继续运行(注意,不是从最开始的位置运行哦),就要要求CPU必须随时记录下当前进程执行的位置,所以,CPU内有对应的寄存器(这个寄存器叫EIP,有些也叫它pc指针。point count。该寄存器为程序计数器,用来记录当前正在执行代码的下一行代码的地址)数据,用来记录当前进程的执行位置。

像一些计算以及调度,是软硬件一起来做的,起核心作用的是软件(例如操作系统),是操作系统帮忙一起做的。

CPU做的事:取指令,分析指令,执行指令。例如问EIP该执行谁,然后从对应的地址取东西来做取指令,分析指令,执行指令,周而复始。

函数调用这些的,当前地址+正在处理的指令长度=下条指令的地址。

如果发生了函数跳转,没有连续去访问,很简单,把函数的地址填到EIP里,下一次CPU就跑过去执行对应的进程内的函数了。也就是EIP指哪CPU就打哪

寄存器在CPU内,(硬件)只有一份(注意,不是一个,是只有一套),但是寄存器内的数据是可以有多份的。寄存器内的数据叫进程的上下文数据,该数据创建时要给子进程,虽然父子进程各自调度,各自会修改自己的EIP,但是已经不重要了,因为子进程已经认为自己的EIP起始值就是fork之后的代码。第16节,2:51:00所以是从这时候跑,但是不代表没有。而且,如果上下文数据都执行的话,不就是死循环吗,而且它在不断地创建子进程

fork的常规用法

1.一个父进程希望复制自己,使父子进程同时执行不同的代码段(即各自执行同一份代码的不同区域,在编写网络服务中很常用)。例如:父进程等待客户端请求,生成子进程来处理请求。

2.一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。即例如让子进程执行其他新程序,要让它做一些完全不一样的事情。

补充:第17节00:13:30

1.fork之后程序加载这块在程序替换时再讲。

2.一般而言,父子进程在实际使用时这两种场景,在生活中?

fork调用失败的原因:

1.系统中有太多的进程

2.实际用户的进程数超过了限制

进程创建本质是要消耗内存资源的,只要创建进程,系统中一定会存在大量的数据结构、程序的代码和数据,这些都是要花内存的。程序太多,导致系统内存资源不足,操作系统不让它创建了。

用户的进程数是有限制的。

进程终止

1.进程终止时,操作系统做了什么?

创建进程时,操作系统需要管理进程,需要为进程创建其内核数据结构task_struct(PCB),还需要为其创建对于的地址空间mm_struct,还要为该进程创建页表,构建映射关系,并且一定程度上,还要将该进程所对应的代码和数据加载至内存。这些行为都需要消耗操作系统内部的相关资源。所以呢,进程=内核数据结构+该进程对应的代码和数据。终止的时候,当然要释放进程申请的相关内核数据结构和对应的数据和代码。所以当一个进程终止时,其对应的数据结构和对应的代码和数据会被操作系统释放掉。释放的本质是释放系统资源(主要是内存)。

2.进程终止的常见方式

分三种情况

1.代码跑完,结果正确,跑完,进程就终止了

2.代码跑完,结果不正确

3.代码没跑完,程序崩溃了(主要在信号部分讲,这里涉及到一点点)

对于1和2,我们以(main函数的返回值)作为切入点。

main函数的返回值(return 0)的意义是什么?为什么不返回1呢?

并不是总是return 0,main函数的返回值叫做进程退出码。用来表示进程是正确还是不正确返回。如果返回0,就是正确,非0表示运行结果不正确。

我们可以通过echo $?来获取最近一次一个进程执行完毕的退出码。

main函数的返回值的意义:返回给上一级进程,用来评判该进程执行结果用的,可以忽略。不管它也没问题,只不过你的进程在退出时的返回值就会被上级读到,读到之后可以根据你的退出码来知道你的程序运行结果是对的还是不对

我们如何知道自己的代码运行结果正确不正确呢?

我们可以直接判断main函数中我们所写的代码运行结果正确不正确,然后再自己写return。

例如:

不同的非0值可以表示不同的错误原因。给我们的程序再运行结束之后,结果不正确时,方便定位错误的原因细节。

我们可以在string.h中找到错误码都是对应的什么。

以下是如何查看0~149代表什么 

 我们发现在Linux下到134开始就不认识了,也就这些了

 我们自己可以使用这些退出码和含义,但是如果我们想自己定义,也可以自己设计一套退出方案。

我们可以看到Linux返回的错误信息的错误码和我们所查询到的错误码是一样的。 都是no such file or directory错误码为2

为什么不是3呢?因为这是其自己定义了一个,其实应该是3,但是其是自己给设置成1了。

 程序出异常了,出现了无意义错误码。

程序崩溃的时候,退出码无意义,因为一般退出码对应的return语句没有被执行。

程序为什么会崩溃?

3.如何用代码终止一个进程?

如何去正确的终止一个进程。

1.return语句

return后被系统拿到,进而被父进程通过某种方式拿到,拿到之后充当了进程的退出码。

return语句就是终止进程的,return+退出码。

其他函数return叫函数返回,只有main函数的return才表示进程退出。

2.exit函数(是C语言提供的)

终止进程,头文件:stdlib.h 该函数的参数为status    函数原型:void exit(int status);

和main函数return效果很像,但是exit在任何地方被调用,都直接表示终止进程。

3._exit、_Exit接口(操作系统提供的)

和exit几乎一样。 

区别:

这个代码是立即执行printf,再执行sleep,再退出。如果去掉\n,因为没有\n,所以数据没有立即刷新,说明这个数据当前一定在相关的缓冲区内部,它最终程序退出时,它的数据会被刷新给系统

 说明C语言上的exit,打印的时候数据在缓冲区里,当它终止进程时,它不像我们想的那样直接终止,而是也会把我们的缓冲区数据刷新到我们的显示器上,让我们看到,然后才执行退出。

如果是_exit,程序退出码正常,但是printf没有被打印出来,因为_exit是一个系统调用。

推荐用exit

库函数与系统接口

函数与系统接口是一种层状接口,库函数在上,系统接口在下。

库函数之上为语言、应用,系统接口之下为操作系统。

exit是库函数,_exit是系统接口,exit底层其实就是调用的_exit。

printf+\n数据是保存在缓冲区中的,那么这个缓冲区在哪里?指的是什么?是谁维护呢?(以后讲,现在了解一点)

缓冲区一定不在操作系统内部,因为如果在操作系统内部,那么_exit也能刷新,效果就像exit一样,所以缓冲区一定不在操作系统内部,缓冲区其实是在C标准库的。具体怎样维护,以后说。

进程等待

子进程退出,父进程不管子进程,子进程就会处于僵尸状态。这样会导致内存泄漏,因为你的PCB长期不回收,会占用内存资源等等

父进程创建了子进程,是要让子进程办事的,那么子进程把任务完成的怎么样(用错误码来标识),父进程需要关心吗?如果需要,如何得知,如果不需要,该怎么处理?

回答这些需要我们知道进程等待的核心内容(为什么要进行进程等待):

1.回收子进程,释放子进程的相关内存资源

2.父进程通过等待,获得子进程的退出结果,进而指导它的进一步工作。且需要了解其原因。

进程等待的必要性

1.子进程退出,若父进程不管不顾,就可能造成僵尸进程的问题,进而造成内存泄漏

2.进程一旦变成僵尸状态,连kill -9页无能为力,因为我们无法杀死一个已经死去的进程

3.父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出

4.进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。

如何进行进程等待?等待的是什么?

两个接口

1.wait  ---做一个基本验证,回收僵尸进程问题

2.waitpid --- 获取子进程退出结果的问题

做一个基本验证,回收僵尸进程问题。获取子进程退出结果的问题。

实际上只调用它们其中之一就会把两件事都做了。

以下代码,子进程退出而父进程依旧在运行,此时就会出现僵尸状态

 监测代码:

 我们发现,进程跑着跑着,子进程变成了僵尸状态

 

 调用wait接口回收它

在man中 /return val查看返回值

 我们修改一下父进程的代码

父进程是阻塞式的等待的。比如说把父进程的PCB链入到子进程的等待队列里,相当于等子进程退出,当子进程退出时,由操作系统再把父进程唤醒,放到运行队列里,让父进程去调用wait再返回。意味着子进程不退出,父进程也不退出,直到子进程状态发生变化。

子进程一死,父进程立马就回收了(我们这里缓了两秒)。第十七节2:21:40

以后我们编写多进程,基本写法就是fork+wait/waitpid

pid等于0呢?以后说 

options:默认为0,表示阻塞等待。当其为WNOHANG时,表示父进程非阻塞等待

WNOHANG(wait no hang 夯住了 其实就是卡住了 在系统层面上就是这个进程没有被CPU调度。 有时候我们切换两个大进程时,会感到卡顿,这就是CPU切换进程的那个时间被用户感知到了。卡住时,进程要么是在阻塞队列中,要么是等待着被调度)

Linux是用C语言写的,其系统调用接口就是操作系统自己提供的接口,这个接口其实就是C语言函数,在系统提供的一般大写的标记位例如WNOHANG其实是宏定义

其实就是

#define WNOHANG 1

进程阻塞的本质是进程阻塞在系统函数的内部。父进程被挂起,后面的代码还执行吗?不执行了。当条件满足的时候,父进程被唤醒,是从哪里唤醒的呢?是waitpid重新调用还是从if的后面呢?再被唤醒时其实是在挂起位置那里,该进程的pc指针(EIP寄存器)指向的是这行代码。当进程被唤醒时,它重新恢复继续向后执行,为什么被唤醒?不就是PCB被放入运行队列里了 第十七节1:17:00有关下图的都得重新看几遍

阻塞等待和非阻塞等待

阻塞等待:一个进程把自己的运行状态由R改成S或D,然后把当前自己的进程放到某种等待队列,这些操作都是操作系统干的。所有的设置状态放在某些队列里,其实就是对PCB、对进程结构体对象放在某些队列里,底层就是对数据结构的操作。(阻塞等待一般都是在内核中阻塞,等待被唤醒。在系统中卡着呢,其实就是进程的PCB放在了所谓的等待队列中等)

非阻塞等待:父进程通过调用waitpid来等待,如果子进程没有退出,waitpid这个系统调用立马返回。(收到信号等于WNOHANG,并且子进程还没有退出,我就不挂起自己,直接return了,return之后继续执行wait后面的代码)

scanf、cin这些底层必定封装了系统调用。

一般一个进程阻塞了,都伴随着被切换。一旦阻塞了,虽然没有返回,但是进程会被切换,切换了这个进程当然就不运行了,所以它就是阻塞了,调用就卡住了,在上层呈现出来的软件就卡住了。

waitpid的实现是属于操作系统的,父进程调用其检测子进程退出状态,查进程的task_struct中子进程的运行信息

轮询检测:不断地、重复地、过一小段时间就检测一次(打电话)

子进程退没退出父进程压根不知道,除非父进程查了才知道

status:是一个输出型参数, 可以设为空,表示不关心,如果要关心,填&status,最终系统会帮助我们填充status,把子进程的退出结果填到status里

waitpid(pid, NULL, 0) == wait(NULL)

看到2:30:00

&status:拿到子进程的退出结果。因为操作系统会自动填充

我们可以看到,收到status的值并不是105

status并不是按照整数来整体使用的,而是按照比特位的方式,将32个比特位进行划分,我们只学习低16位

status的构成: 

最低7个比特位表示进程收到的信号。core dump标志只占一个比特位,我们在gdb调试崩溃程序时(信号部分再说)。普通信号只有1~31.

status的次低8位表示子进程的退出码(退出状态)。

 这里是对的,只是我们看不懂而已

如果我们想得到子进程的能看懂的退出结果,需要我们这么写

 

子进程有无意义, 首先看子进程收到的信号编号

子进程收到的信号编号为0,说明子进程是正常跑完的。退出码代表着结果对还是不对。不对,是什么原因,也是由退出码决定。

程序异常,不单单是内部代码有问题,也可能是外力直接杀掉(kill -9),子进程代码在被杀掉时跑完了吗?是不确定的 

进程(程序)异常退出或崩溃,本质是操作系统杀掉了进程。

操作系统如何杀掉进程呢?(信号部分详细说)

 本质是通过发送信号的方式

1.父进程通过wait/waitpid可以拿到子进程的退出结果。为什么要用wait/waitpid函数呢?直接全局变量不行吗?

定义成全局变量是拿不到的,因为进程具有独立性,数据是发生写时拷贝的,父进程无法拿到,而且,如果是异常,那信号怎么办呢?

2.既然进程是具有独立性的,那进程退出码,不也是子进程的数据吗?父进程又凭什么拿到呢?wait/waitpid究竟干了什么呢?

僵尸进程:至少会保留该进程的PCB信息(这样wait/waitpid可以有途径来释放该资源),task_struct里面保留了任何进程退出时的退出结果信息。

wait/waitpid本质其实是读取子进程的task_struct结构,也就是说一个进程退出时,它的退出码和退出信号是会写到自己进程的PCB结构体里面,然后别人来读

别忘了task_struct是内核数据结构对象,wait/waitpid有权利读退出码和退出信号的权利吗?

当然有,因为它是系统调用,系统调用不就是操作系统吗,也就是说调用wait/waitpid时,就是操作系统帮我去读取task_struct内的对象,从task_struct结构体里读取,帮助我们填充到status那个图里。不就拿到退出码和退出结果了吗。父进程没权利拿到,但是父进程可以调操作系统帮它拿到,所以wait/waitpid的本质是操作系统帮我们拿子进程退出结果的东西。

内存泄漏的问题

如果一个进程退出了,曾经其内存泄漏的问题还在不在(即我们写了一个C++代码,new了一块空间,我们没有释放,但是该程序走完了,那么内存泄漏问题还在不在),会没,操作系统会自动回收,这也就是为什么我们在用一些杀毒软件等关闭一些进程,系统立马就快的原因。它一旦退出,曾经这块空间立马就被释放了。但是,子进程退出,子进程已经死亡,其处于僵尸状态时,它的僵尸状态造成的内存泄漏,有没有虽系统退出呢?没有。

因为它们的差别在于:

new和malloc是用户自己在堆上申请的空间,而内存泄漏本质是内存数据结构,属于操作系统级别的数据结构,要泄漏也不属于软件的内存泄漏,其属于操作系统的内存泄漏,也就是说,如果子进程退出了,父进程不等,最后可能会导致操作系统层面上它的数据一直占空间。

父进程默认是在阻塞状态等待子进程状态变化(就是等子进程退出)

只有子进程退出的时候,父进程才会为waitpid函数进行返回。父进程依旧还活着呢

waitpid和wait可以在目前的情况下,让进程退出有一定的顺序性。也就是说现在的情况总是让子进程先退出,父进程最后退出。这样将来可以让父进程进行更多的收尾工作。

之所以要把阻塞和非阻塞了解清楚,是因为我们未来的编写代码的内容,主要是网络代码,而网络代码当中,大部分都是IO类的,我们会不断面临阻塞和非阻塞的接口。

如何把自己的代码改成非阻塞

第18节1:36:25-2:00:00

进程替换

进程替换是什么?

我们之前说fork之后,父子各自执行父进程代码的一部分,父子代码共享,数据写时拷贝各自一份。如果子进程就想执行一个全新的程序,根本不想执行父进程给我分的那段代码,想有自己的代码呢?

我们可以使用进程的程序替换来完成上述的功能。

概念:程序替换是通过特定的接口,加载磁盘上的一个全新的程序(这个程序指的是对应程序的代码和数据),加载到调用进程的上下文中(地址空间中),以达到让子进程可以执行其他程序的目的。

原理:进程替换是怎么做到的呢?

把用来替换的代码也加载到物理内存中,重新映射,左侧几乎不发生变化,右侧这边让磁盘上的程序加载到内存,并和当前进程的页表重新建立映射。我们就可以把原先执行myproc的进程变成执行other程序的进程。第18节2:26:00

 这些工作用操作系统的相关接口即可完成。

以上过程就叫进程替换。

 进程替换会重新启用新的栈和堆?

进程替换有没有创建新的子进程呢?没有 

如何理解所谓的将程序放入内存中?将程序放入到内存中即加载,操作系统帮我们加载,以后我们所聊的exec系列的函数,本质就是如何加载程序的函数

如何操作?2:36:00-

我们只使用最简单的exec函数来看看

1.不创建子进程的这种类型

 所以,一旦调用(替换)成功,后续所有代码,全部不会执行。如果替换失败了,后续代码还回继续执行。

ececl为什么调用成功,没有返回值呢?(其失败了返回-1。)

因为execl本身也属于main函数内部的代码,只要成功了,也就会把包括execl本身及包括其对应的返回值全都替换了。所以execl根本不需要进行函数返回值判定。第18节3:00:00

2.创建子进程的这种类型

 

 子进程先执行完,跑完之后,父进程才从wait等待函数里退出,即父进程一定是比子进程结束在子进程之后

在上面的代码中为什么我们要创建子进程呢?在进行程序替换的时候为什么要写子进程呢?

如果不创建,那么我们替换的进程只能是父进程。如果创建了,替换的进程就是子进程,而不影响父进程本身(为什么?)原因是因为,进程替换替换的是子进程的代码和数据,并不影响父进程的代码和数据,不影响的原因是因为进程具有独立性。

还有一个原因:

因为替换的是子进程, 父进程就可以专注的去创建子进程,解析任务,派发任务,父进程专注于fork就可以,像一个老板一样派活就可以,不用自己干很多的活

所以为什么要创建子进程,是为了不影响父进程,我们想让父进程聚焦在读取数据,解析数据,指派进程执行代码的功能。

加载新程序之前,父子的数据和代码之间的关系?

共享,数据写时拷贝 。

当子进程加载新程序的时候,不就是一种“写入”吗?也就是把代码写入。那么代码要不要写时拷贝呢?代码要不要写时拷贝,其实就是要不要将父子的代码分离?要分离。因为要分离,所以代码也是需要写时拷贝的。

那么这样的话,父子进程在代码和数据上就彻底分开了,虽然曾经并不冲突。

详细展开其他函数的用法

execl我们已经搞定了

exec系列有7个,几乎都以NULL结尾

第十九节 00:37:30execv代码

 execlp

要执行程序,必须先找到程序。不带路径能找到程序吗?能,例如PATH

那么为什么要传两个ls呢?

不一样,第一个是执行谁,第二个是想怎么执行它

execvp

带p/不带p:不用带全路径/带全路径

v/l:两种传参方式,v代表传指针数组,l代表传单个选项

e(envp):环境变量

1.如何同时执行其他我自己写的C/C++二进制程序(第十九节55:30-)也就是说我现在想形成两个可执行程序,然后用一个程序去调用另一个我写的程序,而不是去调用系统指令

这样写make

因为Makefile从上往下扫描时,只是形成扫到的第一个目标文件。但是我们是想一次形成两个可执行,我们这样写

all叫做伪目标。

这个all呢不需要依赖方法,只需要维护依赖关系。

all依赖的是:exec和mycmd

被扫描时第一个遇到的是all,知道all依赖的是exec和mycmd,然后会分别推导式的执行下面两句代码,把它两可执行程序执行完,依赖条件具备了,然后想执行all的依赖方法,可是all没有,然后就结束了(依赖方法:即exec的gcc -o $@ $^,mycmd的gcc -o $@ $^,all下面没写,所以all没有依赖方法),注意删除也要改

成功 

那么如何用./exec执行./mycmd呢

 

 以下这样也可以

 

2.如何执行其他语言的程序

实际上,我们一般的其他语言,是有对应的解释器的,我们要(第十九节1:09:30 - 1:31:20并不难,只是比较乱 )

exec系列的函数,功能其实就是加载器的底层接口

注意,环境变量是具有全局属性的。可以被子进程继承下去

 这六个接口确实本质是系统调用,但是其严格意义上讲它们并不是系统直接提供给我们的

系统直接提供给我们的,类似于程序替换的接口,上面那几个都不是,而是execve。这个是真正的系统调用,上面的那6个接口,其实底层调用的都是execve,这几个每一个对它收到的各种参数内部做合并,变成execve的三个参数,调用execve实现自己的功能。

上面6个是系统提供的基本封装,之所以提供上面6个,是为了满足我们在不同调用场景下的调用和使用。

exec系列函数命名的理解:

l(list):表示参数采用列表

v(vector):参数用数组

p(path):有p自动搜索环境变量PATH

e(env):表示自己维护环境变量

写一个简易的shell

第十九节1:45:00-2:48:00 + 第二十节

为什么要程序替换?

程序替换和应用场景有关,我们有时候必须要求子进程执行新的程序

(第二十节 0:00:00 - 2:18:00 一段很重要的复习)

复习

myshell的优化

shell的环境变量是从哪来的呢?环境变量是写在配置文件中的,shell启动的时候,通过读取配置文件来获得起始环境变量

所有的访问硬件的,系统级的都包含了系统调用。只能通过系统调用来操作系统

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

列宁格勒的街头

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值