网站建设 银川,修改wordpress的样式,网店推广方法,专门做网站网站犯法吗1. 命令行参数 各位可能见过main函数也是有参数的#xff0c;只是我们平时写的代码都比较简单#xff0c;用不到main函数的参数#xff0c;下面我们看一下main函数的参数是什么又是怎么用的 我们看这样一段代码 其编译运行后的效果是这样的 我们将main函数后面的那两个参数叫…1. 命令行参数 各位可能见过main函数也是有参数的只是我们平时写的代码都比较简单用不到main函数的参数下面我们看一下main函数的参数是什么又是怎么用的 我们看这样一段代码 其编译运行后的效果是这样的 我们将main函数后面的那两个参数叫命令行参数列表argc 表示参数个数argv[ ] 数组表示参数的清单。 我们对代码稍加改造 就可以达到这样的效果 同一个程序可以根据命令行参数根据选项的不同表现出不同的功能就比如 ls -a ls -al 这种命令。 main函数的命令行参数传递原理大概就是我们输入的命令是一串字符串这串字符串先由shell拿到然后按空格打散形成一张表(argv[])和元素个数(argc)argv中每个元素都是一个指针指向一个字符串这个效果我们在第一次的实验中也见到了。 2. 环境变量 main函数的参数还有一个env[ ] 表这张表是该进程的环境变量表我们先见一见这个表 env表的最后是空因此循环结束条件这么写没问题。 我们编译运行出来的这24行东西就是这个 ./code 进程的环境变量。 所有环境变量的格式都是keyvalue也就是数字对应属性信息。这些环境变量中有部分是我们认识的比如3号的SHELLbin/bash shell的版本是bin/bash的7号的当前用户9号一大堆的ls配色方案12号pwd 环境变量表都是继承自父进程的每个进程都有环境变量表。 2.1 几个环境变量 下面我们感受几个环境变量的作用效果 如果想查环境变量还可以使用指令 env 2.1.1 PATH 我们知道ls 虽然作为系统命令但是说白了它也是个可执行程序但是它跟我们自己写的可执行程序又有点不一样自己写的程序运行时必须要在程序名前加上 ./ 但是 ls 命令却不用。 这个问题其实我们之前解释过因为想要执行一个程序比如 ls 就要让系统知道这个程序的路径是什么这样才能通过这个路径启动程序因此自己写的程序要加一个 ./ 表示我要执行的程序就在当前目录下。但是 ls 不用指定路径是因为系统知道要去 /usr/bin 目录下找这个程序如果我们把自己写的程序放到 /usr/bin 中后也可以不用 ./ 指定路径了就像系统指令一样执行程序了。 前面说 env指令 可以查所有环境变量如果要查特定环境变量 echo $PATH 指令 这样查出来的信息就是PATH指令特定的环境变量其中每条路径都由分号 : 隔开。 PATH环境变量的作用就是告诉shell应该去哪些路径下查指令当shell要执行一个命令行命令的时候它首先就会按顺序去这些由冒号 : 分割开的多个子路径下查找对应的命令。 也就是说PATH是系统可执行文件的搜索路径集合。也就是说我们现在有两种方案来达到不带路径就可以执行程序的效果。1.将程序拷贝到上述任意一条路径下2.将自己的路径添加到PATH环境变量中去。 添加的命令就是 PATH$PATH : 新增路径 $PATH就是老PATH作为变量放在那里避免我们再将老PATH敲一遍 环境变量是内存级别的变量即使我们这样更改了之后在重启终端之后新增的环境变量更改都会消失。如果说非要保存对环境变量的更改可以尝试到家目录下vim进这两个文件中更改。 这个是系统的配置文件每次启动一个终端申请操作系统服务的时候操作系统都会先从这些配置文件中提取到环境变量如果在这两个文件中修改的话就相当于是在磁盘级别的修改是可以保存修改的了。 2.1.2 HOME 与 工作目录 我们可以echo查home环境变量的信息效果就是展示当前用户的家目录 当用户登录的时候系统要创建对应的bash而准备bash的时候要读取相关环境变量的配置文件在此时用户对应的家目录就被写进了HOME环境变量中因此我们在登录进机器后就直接处在自己的家目录下。 bash作为一个进程一定有自己的cwd也就是当前工作目录我们可以查看一下 先查bash进程的pid再进入proc目录查看bash进程的cwd发现此时工作目录就是/proc/22574再回到家目录下重新查看bash进程的当前工作目录发现变更成了家目录。也就是说bash的当前工作目录是根据当前所在目录变化的而由于子进程的环境变量又是从父进程继承过来的因此一般情况下我们运行程序的时候它的工作目录就是它所在的目录。但实际上一个程序的工作目录是取决于bash的当前工作做目录而bash的工作目录又取决于当前所在的目录。也就是说如果我们在家目录下用绝对路径启动一个进程那该进程的工作目录就是家目录。 2.1.3 SHLL 这个环境变量会记录下来用户登录的时候启动的是哪一个shell由此会把shell相关的一些可执行程序的路径和程序记录下来就比如我们现在shell的版本是bash 2.3.4 PWD 与 代码中获取环境变量 保存当前进程所在工作路径 在程序外我们可以查看PWD环境变量得知当前进程的工作路径那在代码中获知进程的工作路径也是有必要的。之前我们的获取方案是main函数参数获取环境变量 env[ i ] 其实还有一种更简单的方案使用系统调用 char* getenv(const char* ) 我们使用一下这个系统调用 其效果就是这样 可以看到其效果也验证了我们之前在home中所说的工作目录的问题果然是在哪个目录下打开的程序那它的工作路径就在哪里。 2.3.5 USER USER是当前用户的环境变量 可以看到我用atlanteep的身份和root的身份去运行同一份程序时运行的效果是不一样的通过这个效果就可以根据环境变量写出控制权限的程序。 2.3.6 OLDPWD 与 cd - cd - 命令可以返回上次打开的目录 这个功能的实现就是通过环境变OLDPWD实现的这个环境变量会记录下来上次所在的路径 3. 本地变量 除了环境变量之外在Linux中我们还可以设置本地变量本地变量与环境变量一样都是内存级别的变量重启bash进程之后之前的修改就都没了。 查看本地变量使用 set 命令 通过 set命令 我们不仅可以看见环境变量还可以看到我们刚才自己定义的本地变量。 我们还可以使用 explort命令 将本地变量变成环境变量 事实上我们启动机器之后首先会从磁盘中把OS加载到内存中去之后用户登录操作系统就会给该用户分配一个bash命令行解释器进程那么bash进程会维护3张表argv[](命令行参数表) env[](环境变量表) 本地变量表。 我们explort就是将变量直接从本地变量表迁移到环境变量表中。 如果想要移除某个环境变量使用 unset命令 这样刚才那个a就没有了 本地变量一般的作用是在运维的时候写一些自动化的脚本的。环境变量是可以被子进程继承下去的但是本地变量不能被子进程继承。 环境变量因为有这种继承的特性因此我们称全局变量具有全局属性。 为什么要让环境变量具有全局属性呢第一环境变量是系统的配置信息尤其是有指导性的一些功能比如当前工作目录它是系统配置起效的一种表现。第二进程虽然具有独立性但是可以通过环境变量来进行进程间传递数据。 3.1 一种新方案访问环境变量 我们前面介绍了访问环境变量的两种方案接下来我们再介绍第三种 environ全局变量 访问环境变量 它指向环境变量表因此其类型是 char** 的二级指针类型。 因此我们可以通过这个指针来获取环境变量 可以看到通过全局变量environ也可以获取环境变量 4. 进程地址空间 我们先看一个现象 我们定义一个全局变量gval之后从父进程中分出一个子进程两个进程同时打印各自的信息同时子进程会修改gval的值。 因为进程间具有独立性因此子进程改变全局变量的大小但是父进程gval的值还是100这些都符合我们的认知。但是观察gval的地址在子进程和父进程都是一样的这是没道理的同一块物理内存上是不可能存两份不同数据的。因此这说明之前我们在 c/c 中学到的 取地址取出的并不是真正的物理内存地址这个取出来的地址实际上叫做 虚拟地址 或 线性地址 。 内存空间的地址(虚拟地址)分配如下图 由低地址到高地址分别是正文代码、初始化全局数据未初始化全局数据、堆、栈、命令行参数环境变量但是堆和栈之间还有一块很大的空间是共享区。 上图的空间分配方案应该叫进程地址空间它是逻辑上存在的而不是真实的物理内存的分配方案。 这个进程地址空间存在的意义是为了让每一个进程都认为自己是独占机器物理内存的大小进程之间彼此不知道对方的存在从而实现一定程度上的隔离和便于管理。 形象一点讲这个进程虚拟地址空间是操作系统给进程话的饼告诉进程你那些数据我都如上图那样放进物理内存了你要用随时可以来取用。但其实物理内存中根本就不是那么存放的。既然操作系统给进程画了饼那肯定也会给进程一个兑换饼的凭证使进程可以用这个凭证来从逻辑上存在的内存地址中向OS申请取到对应数据。 这个凭证具体来说是一个内核数据结构对象针对Linux操作系统来讲就是struct mm_struct进程地址空间 pcb中会存储这个凭证的指针后面可以通过这个凭证向OS索要物理内存的使用权。 mm_struct中描述的就是进程地址空间的逻辑那进程地址空间又是由各种数据段组成的其区域的划分就是用某区域的起始地址和结束地址存放在 mm_struct 中用来给进程地址空间做变量存放地址的指导注意还是那句话这里划分出来的区域也是虚拟的并不是给真正的物理内存划分。 在mm_struct创建的时候其中具体哪一块空间要开多大都是在可执行程序中写好的但是像栈区堆区这种运行中使用的地址空间就是在运行中申请的。 struct mm_struct作为虚拟地址存放表依靠页表将虚拟地址映射到物理内存的真实地址中。 当我们创建一个子进程的时候不仅仅要从父进程那里把PCB继承过来还要把mm_struct虚拟地址空间继承过来接着再把父进程的页表继承过来。 也就是说在不修改变量的时候子进程的虚拟地址空间、页表、甚至页表映射的物理内存与父进程都是完全一致的也就是说它们的数据代码全都是共享的。 但是如果此时子进程(或父进程)对于某个数据进行了更改此时操作系统就会通过该变量的虚拟地址找到真实物理地址然后将物理地址中的内容拷贝一份再开一份新空间将新数据放到物理内存中然后把页表中的所映射的物理内存更新但是虚拟地址不变。这就是为什么我们一开始看到父进程和子进程的全局变量地址相同但数据不同的原因因为我们看到的地址只是虚拟地址但实际上子进程的相同虚拟地址所映射的真实物理地址却与父进程不同了。这种操作叫做OS的写时拷贝机制。 4.1 页表 我们这里只是粗谈一下页表做一下铺垫之后有章节详说 页表的结构并没有我们刚才画的那么简单并不是简单的提供虚拟地址映射物理内存的路径它里面还有一些标志位这次先说两个 rwx 和 isexists rwx标志很简单就是看这个变量有没有权限读写内存如果没有权限读写却硬要写那OS就直接杀进程。比如一个字符串常量代码跑到了要修改这个常量的地方要修改就要访问内存直接查看页表发现没有写权限那就拒绝写入杀进程。这个拒绝写入的事情是操作系统在做因此编译器检查不出来错误但是为了方便程序员自查于是编译器提供const关键字来提示程序原一个变量在内存中是否有写权限。 isexists标志表示一个变量是否真正加载到物理内存当中去了。比如一个黑猴有130G但是我电脑的内存只有4G那玩的时候不可能把整个黑猴都加载进内存只能一部分一部分的加载比如开局剧情跑完了之后很长一段时间肯定都不会用到这段代码了那就将这段代码踢出内存并在这个标志位标注或者后面很久才会用到的代码也不会加载进内存。这种操作叫分批加载或者分批挂起。