当前位置: 首页 > news >正文

网站建设推广费怎么做账建设制作外贸网站的公司简介

网站建设推广费怎么做账,建设制作外贸网站的公司简介,优化网站性能,网页设计基础课心得体会2000字文章目录 前言一、操作系统1、概念2、设计OS的目的3、总结 二、进程1、基本概念2、查看进程2.1 使用ps axj命令2.2 通过 /proc 系统文件夹查看 3、通过系统调用获取进程标示符3.1 getpid() 系统调用3.2 getppid() 系统调用 4、通过系统调用fork创建子进程4.1 使用fork创建子进程… 文章目录 前言一、操作系统1、概念2、设计OS的目的3、总结 二、进程1、基本概念2、查看进程2.1 使用ps axj命令2.2 通过 /proc 系统文件夹查看 3、通过系统调用获取进程标示符3.1 getpid() 系统调用3.2 getppid() 系统调用 4、通过系统调用fork创建子进程4.1 使用fork创建子进程4.2 fork之后有两个不同的执行流 5、进程状态6、僵尸进程7、孤儿进程8、进程优先级9、其它概念 三、环境变量1、环境变量1.1 PATH环境变量1.2 其它环境变量1.3 和环境变量相关的命令 2、通过代码如何获取环境变量2.1 通过main函数的第三个参数2.2 通过第三方变量environ获取 3、通过系统调用获取环境变量4、环境变量通常是具有全局属性的4.1 全局环境变量4.2 局部环境变量 5、main函数的第一个和第二个参数 四、程序地址空间1、程序地址空间2、虚拟内存与物理内存3、为什么要有地址空间3.1 有效的保护物理内存3.2 使内存管理模块与进程管理模块分开3.3 使内存分布在进程视角都是有序的 4、重新理解挂起4.1 新建状态4.2 挂机状态 前言 一、操作系统 1、概念 任何计算机系统都包含一个基本的程序集合称为操作系统(OS)。笼统的理解操作系统包括 内核进程管理内存管理文件管理驱动管理 其他程序例如函数库shell程序等等 2、设计OS的目的 与硬件交互管理所有的软硬件资源 为用户程序应用程序提供一个良好的执行环境 3、总结 计算机管理硬件 1. 描述起来用struct结构体 2. 组织起来用链表或其他高效的数据结构 系统调用和库函数概念 在开发角度操作系统对外会表现为一个整体但是会暴露自己的部分接口供上层开发使用这部分由操作系统提供的接口叫做系统调用。 系统调用在使用上功能比较基础对用户的要求相对也比较高所以有心的开发者可以对部分系统调用进行适度封装从而形成库有了库就很有利于更上层用户或者开发者进行二次开发。 二、进程 1、基本概念 在多道程序环境下允许多个进程并发执行此时它们将失去封闭性并具有间断性及不可再现性的特征。为此引入了进程的概念以便更好地描述和控制程序地并发执行实现操作系统的并发性和共享性。 在课本的概念中程序的一个执行实例正在执行的程序等。 在内核观点中担当分配系统资源CPU时间内存的实体。 为了使参与并发执行的每个程序(含数据)都能独立地运行必须为之配置一个专门地数据结构称为进程控制块(Process Control BlockPCB)。系统利用PCB来描述进程的基本情况和运行状态进而控制和管理进程。相应地由程序段、相关数据段和PCB三部分构成了进程实体(又称进程映像)。所谓创建进程实质上是创建进程实体中地PCB而撤销进程实质上是撤销进程的PCB。值得注意的是进程映像是静态的进程则是动态的。 课本上称之为PCBprocess control blockLinux操作系统下的PCB是: task_struct即在Linux中描述进程的结构体叫做task_struct。 task_struct是Linux内核的一种数据结构它会被装载到RAM(内存)里并且包含着进程的信息。 task_ struct内容分类 标示符: 描述本进程的唯一标示符用来区别其他进程。 状态: 任务状态退出代码退出信号等。 优先级: 相对于其他进程的优先级。 程序计数器: 程序中即将被执行的下一条指令的地址。 内存指针: 包括程序代码和进程相关数据的指针还有和其他进程共享的内存块的指针 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子要加图CPU寄存器]。 IO状态信息: 包括显示的I/O请求,分配给进程的IO设备和被进程使用的文件列表。 记账信息: 可能包括处理器时间总和使用的时钟数总和时间限制记账号等。 其他信息 2、查看进程 2.1 使用ps axj命令 我们先写一个test.c文件然后用gcc编译后生成可执行文件test当我们运行这个程序时会循环打印hello world此时就相当于创建了一个进程。 在Linux中可以通过ps axj来查看当前系统中的进程。此命令会将系统中所有的进程信息都显示出来。 ps axj而当我们想要查看刚刚执行test程序创建的进程时可以使用grep 命令来将包含字符串test的进程信息打印出来。 可以看到此时就打印出来了刚刚运行test程序而创建的进程。 我们还可以将每列信息的头部打印出来。 2.2 通过 /proc 系统文件夹查看 进程的信息可以通过 /proc 系统文件夹查看。在Linux中有一个/proc目录/proc 目录是一个位于内存中的伪文件系统。该目录下保存的并不是真正的文件和目录而是一些【运行时】的信息如 CPU 信息、负载信息、系统内存信息、磁盘 IO 信息等。/proc 目录下有很多以数字命名的目录这些目录与进程的 pid 相对应。通过这些目录可以查看进程相关的信息。 此时我们再次将test程序执行然后就会创建一个新进程。我们通过ps axj命令查看test程序创建的进程的PID然后可以看到在/proc目录下就有一个以该PID为名字的目录。 我们可以看到在以进程PID命名的目录下就是该进程的一些信息。 3、通过系统调用获取进程标示符 3.1 getpid() 系统调用 getpid()系统调用可以返回当前进程的PID。在使用该系统调用时需要包含#includesys/types.h和#includeunistd.h头文件。 下面我们在test.c中使用getpid()获得该进程的PID然后显示出来。 当执行了test程序后我们使用ps axj命令查看该程序在系统中的进程的PID可以看到系统调用getpid()返回的PID和我们查看到的PID一致。 此时如果我们想要杀掉该进程可以使用 kill -9 进程PID 这个命令来将PID为这个的进程杀掉。 kill -9 158113.2 getppid() 系统调用 getppid()系统调用可以返回该进程的父进程id。 下面我们在test.c中使用getpid()获得该进程的PID并且使用getppid()获得该进程的父进程的PID然后显示出来。 当执行了test程序后我们使用ps axj命令查看该程序在系统中的进程的PID和该进程的父进程的PID可以看到系统调用getpid()返回的PID和我们查看到的PID一致还有getppid()返回的该进程的父进程的PID也和我们查看的PPID一致。 那么test程序的父进程是什么进程呢我们可以使用ps axj命令查看PID为15101的进程的相关信息。可以看到PID为15101的进程是bash进程。而bash 是 Linux 标准默认的 shell即bash为命令行解释器程序正因为有了这个进程我们才可以在命令行中输入一些命令来与Linux系统进行交互。 并且每一次登录都有一个专属的bash进程被创建我们可以看到在不同的终端中执行test程序创建的进程和该进程的父进程都是不同的。 4、通过系统调用fork创建子进程 4.1 使用fork创建子进程 系统调用fork可以创建一个子进程。fork()的返回值有两种情况 (1). 当创建子进程失败时会返回-1。 (2). 当创建子进程成功过时会返回给父进程该子进程的PID然后给子进程返回0。 我们写出如下代码在该程序执行时会创建一个子进程我们可以看到test程序在执行时打印了两次ret的值其中第一次为test程序打印的而另一次就是test程序使用系统调用fork()创建的子进程打印的。其中可以看到系统调用fork()创建子进程成功时会返回给父进程该子进程的PID然后给子进程返回0。 我们再将上面的程序多打印一些数据将上面的代码改成如下的代码。 我们可以看到PID为17815的进程为PID为27816进程的父进程。即PID为27816的进程就是test程序中使用系统调用fork()创建出来的子进程。 4.2 fork之后有两个不同的执行流 通过上面的分析我们可以知道在fork之后创建了一个子进程所以代码就有了两个不同的执行流我们就可以根据fork对父进程和子进程的返回值不同来进行判断然后让父进程和子进程执行不同的代码。 #includestdio.h #includesys/types.h #includeunistd.hint main() {pid_t id fork();if(id0){//子进程创建失败perror(fork);return 1;}//id在子进程中是0else if(id 0){//child processwhile(1){printf(I am child,pid:%d,ppid:%d\n,getpid(),getppid());sleep(1);}}//id在父进程中是子进程的PIDelse{//parent processwhile(1) {printf(I am father,pid:%d,ppid:%d\n,getpid(),getppid());sleep(1);}}printf(you can see me\n);sleep(1);return 0; } 然后执行下面的指令来一直循环打印进程信息。此时可以看到test程序中将if和else if中的语句都打印出来了。 while :; do ps axj | head -1 ps axj | grep test | grep -v grep; sleep 1; done那么我们就会有疑问了c语言中if和else if不是值执行一个吗为什么上面的程序中if和else if里面的语句都被执行了。 这是因为在fork创建之后就创建了子进程并且在fork之后代码是父进程和子进程共享的。当fork之后fork给父进程的返回值为子进程PID所以在父进程中的id变量的值为子进程的PID而fork给子进程的返回值为0所以在子进程中id变量的值为0。当父进程执行到fork后的代码时因为id0所以就会执行else后的语句而当子进程执行到fork后的代码时因为id 0所以就会执行else if(i0)后的代码。即在fork之后就有了两个不同的执行流。 经过上述的分析我们知道了为什么一份代码中为什么会同时执行if和else if后的语句那么我们又有了一个疑问为什么fork会有两个返回值呢它是怎么实现的给父进程返回子进程pid而给子进程返回0。 这是因为在fork中执行完创建子进程的代码后此时就已经有了父进程和子进程两个进程所以return id;语句父进程和子进程都会执行并且父进程和子进程返回的值不一样这就是为什么fork会有两个返回值。 5、进程状态 我们知道每当有一个新的进程被创建时就会产生一个一个新的task_struct操作系统和cpu运行某一个进程本质就是从task_struct形成的队列中挑选一个tack_struct来执行它的代码。那么CPU依靠什么来选择哪一个进程要进入CPU内开始执行呢并且每个进程不是一进入CPU就可以运行的。这就需要每个进程都需要有自己的状态然后CPU在进程调度时只调度那些状态已经可以运行的进程来进入CPU中执行。 通常进程有以下5种状态前3种是进程的基本状态。 (1). 运行态。进程正在处理机上运行。在单处理机中每个时刻只有一个进程处于运行态。 (2). 就绪态。进程获得了除处理机外的一切所需资源一旦得到处理机便可立即运行。系统中处于就绪状态的进程可能有多个通常将它们排成一个队列称为就绪队列。 (3). 阻塞态又称等待态。进程正在等待某一事件而暂停运行如等待某资源为可用(不包括处理机)或等待输入/输出完成。即使处理机空闲该进程也不能运行。系统通常将处于阻塞态的进程也排成一个队列甚至根据阻塞原因的不同设置多个阻塞队列。 (4). 创建态。进程正在被创建尚未转到就绪态。创建进程需要多个步骤首先申请一个空白 PCB并向 PCB 中填写用于控制和管理进程的信息然后为该进程分配运行时所必须的资源最后把该进程转入就绪态并插人就绪队列。但是如果进程所需的资源尚不能得到满足如内存不足则创建工作尚未完成进程此时所处的状态称为创建态。 (5). 终止态。进程正从系统中消失可能是进程正常结束或其他原因退出运行。进程需要结束运行时系统首先将该进程置为终止态然后进一步处理资源释放和回收等工作。 挂起 我们知道进程的代码和数据都是在内存中存的当CPU通过进程调度算法选择一个进程的task_struct后然后CPU会根据该进程的task_struct里面的信息去找到该进程的代码和数据然后执行。但是内存的空间是有限的当内存中保存了很多进程的代码和数据时此时进程的内存就会不足此时操作系统就会适当的选择一些进程的代码和数据到磁盘中此时进程的状态叫做挂起。 为了弄明白正在运行的进程是什么意思我们需要知道进程的不同状态。一个进程可以有几个状态在Linux内核里进程有时候也叫做任务。下面的状态在kernel源代码里定义 /* * The task state array is a strange bitmap of * reasons to sleep. Thus running is zero, and * you can test for combinations of others with * simple bit tests. */ static const char * const task_state_array[] { R (running), /* 0 */ S (sleeping), /* 1 */ D (disk sleep), /* 2 */ T (stopped), /* 4 */ t (tracing stop), /* 8 */ X (dead), /* 16 */ Z (zombie), /* 32 */ }; R运行状态running: 并不意味着进程一定在运行中它表明进程要么是在运行中要么在运行队列里。 S睡眠状态sleeping): 意味着进程在等待事件完成这里的睡眠有时候也叫做可中断睡眠interruptible sleep。 D磁盘休眠状态Disk sleep有时候也叫不可中断睡眠状态uninterruptible sleep在这个状态的进程通常会等待IO的结束。 T停止状态stopped 可以通过发送 SIGSTOP 信号给进程来停止T进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。 X死亡状态dead这个状态只是一个返回状态你不会在任务列表里看到这个状态。 R运行状态 当我们写一个循环运行时可以看到此时该进程的状态为R态。即表示test01程序在运行队列中R表示该进程为前台进程当执行前台进程时bash命令行就不能执行了。 我们可以在执行程序时在后面加上就可以让进程变为后台进程。此时可以看到进程的状态变为了R。 S睡眠状态 当我们执行下面的代码时由于下面的代码有printf打印则需要用到输出设备所以意味着该程序在执行时需要等待输出设备只有输出设备的资源可以使用了该进程才可以被调到CPU上执行。由于CPU执行该代码的时间是很短的所以我们看到的该进程基本上都是处于等待输出设备的S状态。 当我们将test02执行时加上则表示将该进程变为后台进程此时S就变为了S。 D磁盘休眠状态 我们可以使用kill命令来将刚刚执行处于S状态的进程杀掉。但是如果一个进程的状态为D时操作系统就无法将这个进程杀掉只能等进程自动唤醒自己。 当服务器压力过大的时候操作系统会通过一定的手段杀掉一些进程以此来节省空间但是这就会发生一些错误。例如进程A在运行时需要等待输入设备磁盘输入一些数据此时磁盘就会去找A进程的数据然后进程A等待磁盘时状态变为S状态。然后此时内存的空间被占用的太多了操作系统需要选择一些进程杀掉以节省空间此时发现A进程并没有运行且处于S阻塞态就将A进程杀掉了。但是当磁盘找到A进程需要的数据时此时发现A进程已经没有了那么磁盘刚刚读取的数据就无效了。但是如果将A进程设为D磁盘休眠状态的话当操作系统选择一些进程杀掉时发生A进程是D状态就不会杀掉A进程了。当A进程等待磁盘读取数据完成后A进程会自己将自己唤醒。 T停止状态 我们可以使用如下命令来查看kill命令常用的信号。 kill -l下面为kill命令常用的信号。 1| SIGHUP | 重新加载配置 2| SIGINT | 键盘中断 crtlc 3) | SIGQUIT |退出 9 | SIGKILL | 强制终止 15 | SIGTERM | 终止正常结束缺省信号 18 | SIGCONT | 继续 19 | SIGSTOP | 停止 20 | SIGTSTP | 暂停 crtlz T状态就是进程暂停的状态。可以使用kill -20将进程暂停可以看到当执行了kill -20后该进程的状态从S变为T状态即此时进程就处于暂停状态。 此时可以使用kill -18来将进程继续执行。可以看到当执行了kill -18后进程的状态从T状态变为了S状态即此时进程又恢复了S等待输出设备的状态。 那么这个T状态有什么用呢其实当我们在使用gdb调试程序时当我们设置了一个断点后然后程序运行到断点处时此时这个进程就处于暂停态。 X终止态 X为终止态表示资源可以被回收但是X瞬时性非常强所以我们看不到进程处于X状态。 6、僵尸进程 当一个进程已经退出但是还不允许被操作系统释放时就处于一个被检测的Z状态——僵尸状态。 当一个进程处于此状态时代表可以被回收一般都是会被父进程或者操作系统回收然后该进程就由Z状态变为终止态X状态。 我们写一个如下代码当执行后会创建一个子进程然后3秒后子进程运行结束。此时子进程就会处于Z状态。 僵尸进程危害 进程的退出状态必须被维持下去因为他要告诉关心它的进程父进程你交给我的任务我办的怎么样了。可父进程如果一直不读取那子进程就一直处于Z状态是的 维护退出状态本身就是要用数据维护也属于进程基本信息所以保存在task_struct(PCB)中换句话说Z状态一直不退出PCB一直都要维护是的 那一个父进程创建了很多子进程就是不回收是不是就会造成内存资源的浪费是的因为数据结构对象本身就要占用内存想想C中定义一个结构体变量对象是要在内存的某个位置进行开辟空间 7、孤儿进程 父进程先退出子进程就称之为“孤儿进程”。然后孤儿进程被1号init进程(系统本身)领养并且此时孤儿进程也要由init进程回收。 为什么孤儿进程需要被领养呢 这是因为当以后子进程退出后它的父进程不在了就需要领养的1进程来将该子进程的资源进行回收。 #includestdio.h #includeunistd.h int main() { pid_t id fork(); if(id0) { //child while(1) { printf(hello world\n); sleep(1); } } else { //father int tmp 5; while(tmp) { printf(I am father: %d\n,tmp); tmp--; sleep(1); } } return 0; } 当执行上面的代码后我们可以观察到当5秒后父进程结束了而此时子进程就会变为孤儿进程然后被1进程收养此时孤儿进程的父进程就为1进程。 当右边的进程还在运行时也可以输入kill -9 来杀掉这个进程虽然命令会乱但是系统还是会接收到的。 8、进程优先级 cpu资源分配的先后顺序就是指进程的优先权priority。 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用可以改善系统性能。 还可以把进程运行到指定的CPU上这样一来把不重要的进程安排到某个CPU可以大大改善系统整体性能。 在linux或者unix系统中可以用ps –l命令即可以查看当前环境下与bash相关的的进程信息。 ps -lUID : 代表执行者的身份 PID : 代表这个进程的代号 PPID 代表这个进程是由哪个进程发展衍生而来的亦即父进程的代号 PRI 代表这个进程可被执行的优先级其值越小越早被执行 NI 代表这个进程的nice值 PRI就表示进程的优先级或者通俗点说就是程序被CPU执行的先后顺序此值越小进程的优先级别越高即越先被CPU执行。 NI就是进程的nice值其表示进程可被执行的优先级的修正数值。因为Linux中不可以直接修改进程的优先级所以就使用nice值来修正进程的优先级。 PRI值越小越快被执行那么加入nice值后将会使得PRI变为PRI(new)PRI(old)nice。这样当nice值为负值的时候那么该程序将会优先级值将变小即其优先级会变高则其越快被执行。所以在Linux下调整进程优先级就是调整进程nice值。nice其取值范围是-20至19一共40个级别。 需要强调一点的是进程的nice值不是进程的优先级他们不是一个概念但是进程nice值会影响到进程的优先级变化。 所以当想要修改一个进程的优先级时就需要修改它的nice值。这样该进程的优先级就会变了。 我们可以通过top命令来修改进程的nice值然后改变进程的优先级。 输入top命令后按r然后按回车后输入进程PID然后按回车输入nice值。 可以看到没有修改前bash进程的PRI为80NI为0。 当我们修改bash进程的neice值为10后可以看到此时bash进程的PRI也增加了10。因为PRI的值变大了所以此时进程bash的优先级就变小了。 当我们想修改nice值为负值时会发现操作不允许。这是因为普通用户只能将进程的优先级减小而想要将nice的值为0或负值需要使用sudo top然后才可以将nice的值设为负值即将进程优先级增加。 当我们使用sudo top命令将bash进程的nice值设为-10后发现该进程的PRI变为了70。这是因为PRI(new)PRI(old)nice而PRI(old)每次都是初始的PRI即每次都是80。所以80(-10)70。 使用renice也可以调整进程的nice值。 //将pid为11768的进程的nice值设为10 renice 10 -p 117689、其它概念 竞争性: 系统进程数目众多而CPU资源只有少量甚至1个所以进程之间是具有竞争属性的。为了高效完成任务更合理竞争相关资源便具有了优先级 独立性: 多进程运行需要独享各种资源多进程运行期间互不干扰 并行: 多个进程在多个CPU下分别同时进行运行这称之为并行 并发: 多个进程在一个CPU下采用进程切换的方式在一段时间之内让多个进程都得以推进称之为并发。 进程的切换 三、环境变量 常见环境变量 PATH : 指定命令的搜索路径 HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录) SHELL : 当前Shell,它的值通常是/bin/bash。 1、环境变量 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数如我们在编写C/C代码的时候在链接的时候从来不知道我们的所链接的动态静态库在哪里但是照样可以链接成功生成可执行程序原因就是有相关环境变量帮助编译器进行查找。环境变量通常具有某些特殊用途还有在系统当中通常具有全局特性。 PATH : 指定命令的搜索路径 HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录) SHELL : 当前Shell,它的值通常是/bin/bash。 1.1 PATH环境变量 当我们在命令行中输入ls pwd等命令时系统可以直接执行这些命令但是当我们想要执行自己编译生成的test程序时需要./test才能执行。这其实就是因为ls pwd等命令的路径都在PATH环境变量中而我们自己的程序所在的路径没有在PATH环境变量中。 我们可以使用 echo $PATH查看PATH环境变量的值。 echo $PATH我们可以看到因为pwd gcc等程序都在/usr/bin目录下而PATH的值中包含了/usr/bin这个目录所以当在命令行中输入pwd gcc等命令时就会去PATH的值中这些目录下去找命名为pwd gcc的可执行程序如果找到了就会执行。而我们自己写的程序所在的目录并没有在环境变量PATH中所以当在任意一个目录下执行时系统会找不到这个可执行程序。 那么如果我们也想要将自己的程序可以在任意目录下输入名称都能运行有两种办法。 (1). 将自己的程序拷贝到PATH的任意一个路径下那么当在命令行中执行自己的程序时就会在这些路径下找该程序而自己的程序在这些路径下所以就会直接执行了。但是这个办法会污染系统自带的命令池所以我们不会那么做。 (2). 我们可以将自己的程序所在的路径添加到环境变量PATH中这样在搜索可执行程序时也会来到我们自己的程序所在的路径下搜索。 可以使用export命令添加路径到PATH中。 //将路径/home/drh/linux-learning/test13添加到环境变量PATH中 export PATHPATH:/home/drh/linux-learning/test13可以看到此时/home/drh/linux-learning/test13路径就被添加到了PATH中。 此时直接输入mytest命令就可以执行程序了。 还可以通过在PATH后面直接跟上路径来改变环境变量PATH的值。 注意这样改变是覆盖式的改变。 //直接将环境变量的值变为/usr/bing而不是在原来的值后面添加路径/usr/bin PATH/usr/bin我们还可以将环境变量PATH的值改为根目录/此时输入以前的命令就会显示找不到这个命令。在命令行中修改环境变量的值只在这次登录有效当重新登录时PATH的值又会变回默认的了。如果在配置文件中更改了环境变量那么就会一直生效了。 1.2 其它环境变量 我们可以使用env命令显示所有环境变量 envHOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录。 即HOME就是记录当前登录用户的家目录是什么当登录的用户变了时此时该变量的值就会变。环境变量就是随着环境的改变这个变量的值也会变。 可以看到在不同的用户中环境变量HOME的值不同。 SHELL : 当前Shell,它的值通常是/bin/bash。 SHELL这个环境变量的值就是当前的shell程序一般都是Linux系统默认的bash。 1.3 和环境变量相关的命令 echo: 显示某个环境变量值export: 设置一个新的环境变量env: 显示所有环境变量unset: 清除环境变量set: 显示本地定义的shell变量和环境变量 2、通过代码如何获取环境变量 2.1 通过main函数的第三个参数 当我们在文档中查看main函数时可以看到main函数其实是有3个形参的。 而main函数的第三个参数其实就和环境变量有关可以看到main函数的第三个参数envp为一个指针数组该数组中的元素都为char*类型的指针。 在每个程序执行时都会收到一张环境表环境表是一个字符指针数组每个指针指向一个以’\0’结尾的环境字符串。即main函数的第三个参数envp里面存的就是这个环境表。 当我们使用如下的程序查看指针数组envp的值时可以看到显示了很多环境变量和环境变量的值。 2.2 通过第三方变量environ获取 在libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时 要用extern声明。 可以看到environ指向的环境变量表中打印出来的内容和main函数第三个参数中的基本一致。 3、通过系统调用获取环境变量 我们可以通过系统调用getenv()来获取指定环境变量的值。 //获取环境变量PATH的值 getenv(PATH)4、环境变量通常是具有全局属性的 4.1 全局环境变量 环境变量通常具有全局属性可以被子进程继承下去。 我们可以使用系统调用getenv来得到环境变量PATH的值那么当我们使用getenv来得到一个不存在的环境变量时很显然它会报错。 这是因为该进程的环境变量是继承它的父进程而它的父进程中没有test02环境变量所以会显示错误。所有命令行进程的最终父进程都是bash这个进程所以每个命令行进程的环境变量其实就是继承的bash进程的环境变量。如果我们给bash中添加一个test02环境变量时此时再执行该程序就可以查看到这个环境变量的内容了。所以子进程的环境变量信息都是从父进程继承下来的。 我们可以使用export设置一个新的环境变量。 //添加一个环境变量test02并赋值为新建环境变量 export test02新建环境变量所以子进程的环境变量是从父进程继承来的并且默认所有的环境变量都会被子进程继承。所以环境变量具有全局属性。 4.2 局部环境变量 当我们在设置环境变量时如果不在前面加上export则就会创建一个局部环境变量。此时如果我们在env所有环境变量中查找局部环境变量dong会发现找不到而在set本地定义的shell变量和环境变量中查找局部环境变量dong就可以找到。这是因为env中显示的是全部的全局环境变量而set中显示的是本地定义的shell变量和环境变量。因为dong是在当前目录下定义的局部环境变量所以在env中不会显示。 5、main函数的第一个和第二个参数 main函数的第一个和第二个参数为命令行参数即为运行该程序时输入的一些参数。 例如使用如下的代码来打印指针数组argv的每一个元素的值 可以看到./test03就是指针数组的第一个元素指向的字符串而argc就是在执行程序test03时输入的命令的字符串的个数。当我们只输入./test03时此时argc就是1argv指针数组中就只有一个元素存的是指向./test03字符串的指针。 命令行参数的意义是可以根据不同的选项来执行程序的不同子功能。例如ls 等命令使用不同的选项就有不同的功能其实底层就是使用了命令行参数。 我们可以使用下面的程序来模拟像 ls 这样的命令后面使用不同的选项就实现不同的功能是怎样实现的。 四、程序地址空间 1、程序地址空间 一个程序在运行时的地址空间如下所示。[0,3GB]为用户空间[3GB,4GB]为内核空间。 我们可以使用下面的代码来验证这个图中的各空间所在的位置是否正确。 我们可以使用下面的程序来验证栈区是先使用高地址的空间再使用低地址的空间。而堆区是先使用低地址的空间然后使用高地址空间。 我们使用下面的代码查看static修饰的局部变量的地址可以看到static修饰的局部变量的空间在全局区域。所以static修饰局部变量的本质就是将该变量开辟在全局区域。 我们再使用下面的代码查看字符串常量所在的区域可以看到字符串常量和代码都在代码区并且这个区域的内容只允许读。 2、虚拟内存与物理内存 我们在c语言中使用malloc申请空间使用free释放空间。可是我们有没有这样的疑问我们使用malloc申请了10字节的空间为什么free时就只释放10个字节的空间呢我们在使用free时也没有告诉free释放空间的大小那么free是怎么知道具体要释放多少的空间的呢 这是因为在malloc申请空间时操作系统时间给的空间比10字节要多因为还需要记录这次申请空间的一些属性信息(cookie数据)例如空间大小等这样free才能知道具体要释放多少空间。那么我们就明白了操作系统对每一次申请空间的管理也是使用先描述再组织的方式即每一次申请的空间的相关信息都存了下来。那么我们就猜测内核中的地址空间本质也一定是一种数据结构并且每一个地址空间的结构要和一个特定的进程关联起来。 我们知道计算机中是有虚拟内存的概念的那么为什么要引入虚拟内存的概念呢 这是因为如果直接访问物理内存的话是非常不安全的。因为内存本身是随时可以被读写的如果我们直接使用的是物理内存那么一个进程可以通过物理内存的地址读写另一个进程的数据这是特别不安全的。 所以才有了虚拟内存的概念即进程使用的都是虚拟地址空间然后虚拟地址空间映射到物理内存这样就避免了进程直接访问物理内存。要访问物理内存需要先进行映射如果虚拟地址是一个非法地址就会禁止映射这样就不能修改物理内存的内容了。即例如进程1的虚拟内存空间为0x 00 - 0x 100然后在进程1中访问并且修改了虚拟地址为0x 200的内容想要修改0x 200映射的物理地址的内容需要先进行映射而在映射时发现进程1的虚拟内存空间的地址为0x 00 - 0x 100但是想要修改虚拟地址为 0x 200的内容就会判断这是一个非法地址从而禁止0x 200映射到它的物理地址这样就防止了进程1修改其它地址的数据。而这些进程的虚拟内存空间的划分就相当于使用一个start标识它的起始地址使用一个end标识它的结束位置。这样就定义了一个进程的虚拟内存地址如果想要改变进程的地址空间只需要改变start和end的值即可。 每个进程都需要一个这样的标识来规定它的虚拟内存地址并且每一个进程中都有代码段堆、栈等区域这些区域也需要一个类似start和end的标识来记录它们的地址空间。所以地址空间其实是一种内核数据结构它里面至少要有各个区域的划分。 我们可以看到在linux内核源码中声明了mm_struct结构体。并且还定义了各个区域的start标识和结束标识。 并且每一个进程的task_struct结构体都和一个地址空间结构体mm_struct相关联。地址空间和页表(用户级)是每一个进程都私有一份只要保证每一个进程的页表映射的是物理内存的不同区域就能做到进程之间不会互相干扰这样就保证进程的独立性。 然后我们就可以解释下面的一个问题了。 我们在父进程中创建了一个全局变量g_val和一个局部变量id但是我们发现id变量在父进程中为子进程的pid在子进程中为0一个变量为什么会有两个值呢。并且在下面的代码中我们在子进程中更改了g_val变量的值然后我们打印g_val的地址发现子进程和父进程中g_val的地址相同但是父进程和子进程中g_val的值确是不相同的。 这其实就是因为每一个进程都会对应一个tast_struct结构体并且每一个tast_struct结构体又和一个mm_struct结构体相关联而子进程的mm_struct是以父进程为模板而建的所以在父进程和子进程打印出来的g_val的地址都是虚拟地址所以会出现父进程和子进程中的g_val的地址相同当子进程要修改其内容时就会发生写时拷贝即在物理内存中开辟一片空间将父进程的内容拷贝到这片空间上然后将子进程的页表的对应的物理地址指向这片空间。所以真正的g_val的值是存储在各自的进程的虚拟内存映射的物理内存中的所以父进程和子进程的g_val的值不同。 当我们的程序在编译的时候形成可执行程序的时候其实内部已经有地址了这个地址就是编译器编译好的虚拟地址因为地址空间不仅仅是操作系统内部需要遵守其实编译器也要遵守。所以编译器在编译代码的时候就已经给各个区域代码区、堆区、栈区等分配好了虚拟地址并且采用了和Linux内核中一样的编址方式这样每一个变量每一行代码都进行了编址故程序在编译的时候每一个字段早已经具有了一个虚拟地址。 3、为什么要有地址空间 3.1 有效的保护物理内存 当我们写出如下的代码时会发现并不能更改str[1]‘a’但是能更改str2[1]‘a’这是为什么呢 这是因为str指针指向了常量字符串hello world“的空间而我们在上面测试了常量字符串在代码区这个区域的内容只允许读并不允许写。而str2指向的空间是存在栈区的这片空间中将代码区的hello world字符串拷贝一份到该空间而栈区是允许读写的所以str2[1]a’可以修改。 那么编译器是怎样判断这片空间能不能进行修改呢这其实是在页表做的判断当编译器将代码编译为可执行文件后此时代码中的每一个片段都有了自己的虚拟地址然后在页表中每一块虚拟空间都会映射到一片物理内容中在这时页表就会进行判断如果这片虚拟空间为代码区则就会在映射的物理内存中标明这片映射的物理内存只允许读不允许写。这样页表就为每一块物理内存设置了相应的权限当使用虚拟内存想要修改或者读映射的物理内存中的内容时页表会先判断有没有相应的权限如果有了才可以完成相应操作。所以有了虚拟内存和页表的存在可以一定程度上的保护物理内存中的数据而因为地址空间和页表是操作系统创建并维护的所以有想要使用地址空间和页表进行映射时需要经过操作系统的检查如果操作系统识别到这个进程有非法访问或者映射就会终止这个进程这就是为什么非法访问的进程不能被执行的原因。所以地址空间的存在有效的保护了物理内存中的所有合法数据包括各个进程以及内核的相关有效数据。 3.2 使内存管理模块与进程管理模块分开 因为有地址空间的存在因为有页表的映射的存在在物理内存中就可以对未来的数据进行任意位置的加载这样物理内存的分配就可以和进程的管理分开处理了即内存管理模块和进程管理模块就完成了解耦合。所以我们在c语言、c中使用malloc和new申请空间时本质是在虚拟内存中申请的空间。 如果有一个进程申请了虚拟空间然后操作系统分配了对应的物理空间与这片虚拟空间相映射但是这个进程并没有马上使用这片物理空间那么这片空间就造成了浪费所以操作系统采用了延迟分配的策略来提高整机的效率。本质上因为有地址空间的存在所以进程申请空间时其实是在地址空间上申请的操作系统可能并没有给这个进程分配物理内存。而当这个进程真正的要对申请的空间进行访问时此时操作系统才会为这个进程真正的分配物理内存然后该进程就可以对物理内存进行访问了。这样操作的话可以使内存能更有效的使用。即使内存使用效率为100%。 3.3 使内存分布在进程视角都是有序的 我们在上面使用代码验证了程序中代码或者变量在内存中的顺序。但是在物理内存中理论上是可以任意位置进行存储的所以如果我们的程序直接使用物理内存的话那么几乎所有的数据和代码在物理内存中是乱序的。但是因为有了页表的存在它可以将地址空间上的虚拟地址和物理地址进行映射而进程在地址空间上的虚拟地址是有序的所以在进程视角所有的内存分布都是有序的即代码区地址最小堆区在栈区下面堆区栈区相对而升等规则就可以依靠虚拟地址来验证了。所以地址空间页表的存在可以将内存分布有序化。 其实地址空间就是操作系统给进程画的大饼例如如果有4GB的空间而因为有了地址空间的存在所以每一个进程都认为自己拥有4GB空间并且各个区域是有序的但是实际可能进程要访问的物理内存中的数据和代码现在并没有在物理内存中只有真的使用到时操作系统才会为进程分配真正的物理空间。并且将不同的进程映射到不同的物理内存这样每一个进程就都是独立存在的了就实现了进程的独立性。 4、重新理解挂起 4.1 新建状态 一个可执行程序a.exe的代码和数据都是存储在磁盘上的。当执行这个程序时就会将这个程序的代码和数据拷贝到物理内存中然后操作系统创建这个进程的task_struct和mm_struct等然后再建立页表将a.exe的虚拟内存地址和物理内存地址建立对应的映射。但是有时候并不是必须要把程序的所有代码和数据加载到内存中并且创建好内核数据结构建立映射关系。在最极端的情况下甚至只有内核结构被创建出来了。此时这个进程就是处于新建状态。 4.2 挂机状态 有的大型程序几十G甚至几百G那么我们的电脑才几G运行内存这些程序是如何在电脑上执行的呢。 理论上可以实现对程序的分批加载每次只加载该程序的部分代码和数据到内存中进行执行那么既然可以分批加载那也一定可以分批换出即将那些已经执行完的程序的代码和数据进行换出这个程序中每次加载到内存中进行执行的代码和数据就是一个进程当这个进程执行完并且下面不会再执行时比如阻塞了那么这个进程的代码和数据就会被换出了此时这个进程就叫做挂起了。然后会有这个程序的其他部分的代码和数据加载到内存中形成一个新的进程。这样就分批执行了这个大型程序。页表是将进程的虚拟内存映射到物理内存中但是页表可不仅仅只能将虚拟内存映射到物理内存中页表还可以将虚拟内存映射到磁盘中的位置。
http://www.zqtcl.cn/news/334322/

相关文章:

  • 网站在百度上搜不到了wordpress导航菜单加图片
  • wordpress网站访问慢网站建设35类
  • 绍兴做网站价格字体
  • asp.net网站开发实训可以不花钱做网站吗
  • 北京网站的制作设计服务器和电脑主机的区别
  • 北京网站建设的服务公司凡科网站 怎么开支付
  • 包头公司做网站知名做网站费用
  • 安徽网站建设服务平台重庆网站建公司大全
  • 有什么网站可以做中间人的相城区建设局网站
  • 房屋装修在线设计网站百度联盟广告怎么屏蔽
  • 网站,商城,app+建设域名网址注册
  • 肥西做网站设计网页页面
  • 怎样做百度推广网站iis服务器的默认网站
  • 东莞建设工程交易中心门户网站湖南设计网站机构
  • 做网站在网站建设客户
  • 河北建设厅安监站官方网站一个新手怎么做电商
  • 做结婚请柬网站有那些做网店哪个网站好
  • 做网站尽在美橙互联欧美简约风格网站设计
  • idea建设完整的网站微官网下载
  • 阿城区建设小学网站上海建设行政主管部门政务网站
  • 西丽网站建设网站怎样做才能有点击率
  • 网站建设图片大小建设部网站1667号公告
  • 做wps的网站赚钱网站建设中网站图片如何修改
  • 公司招商型网站建设怎么自己做网站挣钱
  • 红酒手机网站建设中视频自媒体注册
  • 免费网站2022年能用的网址青阳网站建设
  • 网站建设的开发方式知乎科技部网站建设合同范本
  • 兰州市建设厅官方网站做酒店的网站
  • 宠物店网站开发文档撰写洛阳市河阳建设工程有限公司网站
  • 毕业设计做网站应该学什么wordpress调用子分类