程序运行原理

news2025/3/12 9:55:56

程序是如何运行起来的

软件被开发出来,是文本格式的代码,这些代码通常不能直接运行,需要使用编译器编译成操作系统或者虚拟机可以运行的代码,即可执行代码,它们都被存储在文件系统中。不管是文本格式的代码还是可执行的代码,都被称为 程序,程序是静态的,安静地呆在磁盘上,什么也干不了。要想让程序处理数据,完成计算任务,必须把程序从外部设备加载到内存中,并在操作系统的管理调度下交给CPU去执行,去运行起来,才能真正发挥软件的作用,程序运行起来以后,被称作 进程

进程除了包含可执行的程序代码,还包括进程在运行期使用的内存堆空间、栈空间、供操作系统管理用的数据结构。如下图所示:

alt

操作系统把可执行代码加载到内存中,生成相应的数据结构和内存空间后,就从可执行代码的起始位置读取指令交给CPU顺序执行。指令执行过程中,可能会遇到一条跳转指令,即CPU要执行的下一条指令不是内存中可执行代码顺序的下一条指令。编程中使用的循环for…,while…和if…else…最后都被编译成跳转指令。

程序运行时如果需要创建数组等数据结构,操作系统就会在进程的 堆空间 申请一块相应的内存空间,并把这块内存的首地址信息记录在进程的栈中。堆是一块无序的内存空间,任何时候进程需要申请内存,都会从堆空间中分配,分配到的内存地址则记录在栈中。

栈是严格的一个后进先出的数据结构,同样由操作系统维护,主要用来记录函数内部的局部变量、堆空间分配的内存空间地址等。

我们以如下代码示例,描述函数调用过程中,栈的操作过程:

void f(){
  int x = g(1);
  x++; //g函数返回,当前堆栈顶部为f函数栈帧,在当前栈帧继续执行f函数的代码。
}
int g(int x){
  return x + 1;
}

每次函数调用,操作系统都会在栈中创建一个栈帧(stack frame)。正在执行的函数参数、局部变量、申请的内存地址等都在当前栈帧中,也就是堆栈的顶部栈帧中。如下图所示:

alt

当f函数执行的时候,f函数就在栈顶,栈帧中存储着f函数的局部变量,输入参数等等。当f函数调用g函数,当前执行函数就变成g函数,操作系统会为g函数创建一个栈帧并放置在栈顶。当函数g()调用结束,程序返回f函数,g函数对应的栈帧出栈,顶部栈帧变又为f函数,继续执行f函数的代码,也就是说,真正执行的函数永远都在栈顶。而且因为栈帧是隔离的,所以不同函数可以定义相同的变量而不会发生混乱。

计算机如何同时处理数以百计的任务

我们自己日常使用的PC计算机通常只是一核或者两核的CPU,我们部署应用程序的服务器虽然有更多的CPU核心,通常也不过几核或者几十核。但是我们的PC计算机可以同时编程、听音乐,而且还能执行下载任务,而服务器则可以同时处理数以百计甚至数以千计的 并发 用户请求。

那么为什么一台计算机服务器可以同时处理数以百计,以千计的计算任务呢?这里主要依靠的是操作系统的CPU分时共享技术。如果同时有很多个进程在执行,操作系统会将CPU的执行时间分成很多份,进程按照某种策略轮流在CPU上运行。由于现代CPU的计算能力非常强大,虽然每个进程都只被执行了很短一个时间,但是在外部看来却好像是所有的进程都在同时执行,每个进程似乎都独占一个CPU执行。

所以虽然从外部看起来,多个进程在同时运行,但是在实际物理上,进程并不总是在CPU上运行的,一方面进程共享CPU,所以需要等待CPU运行,另一方面,进程在执行I/O操作的时候,也不需要CPU运行。进程在生命周期中,主要有三种状态,运行、就绪、阻塞。

  • 运行:当一个进程在CPU上运行时,则称该进程处于运行状态。处于运行状态的进程的数目小于等于CPU的数目。
  • 就绪:当一个进程获得了除CPU以外的一切所需资源,只要得到CPU即可运行,则称此进程处于就绪状态,就绪状态有时候也被称为等待运行状态。
  • 阻塞:也称为等待或睡眠状态,当一个进程正在等待某一事件发生(例如等待I/O完成,等待锁……)而暂时停止运行,这时即使把CPU分配给进程也无法运行,故称该进程处于阻塞状态。

不同进程轮流在CPU上执行,每次都要进行进程间CPU切换,代价是非常大的,实际上,每个用户请求对应的不是一个进程,而是一个线程。线程可以理解为轻量级的进程,在进程内创建,拥有自己的线程栈,在CPU上进行线程切换的代价也更小。线程在运行时,和进程一样,也有三种主要状态,从逻辑上看,进程的主要概念都可以套用到线程上。我们在进行服务器应用开发的时候,通常都是多线程开发,理解线程对我们设计、开发软件更有价值。

系统为什么会变慢,为什么会崩溃

现在的服务器软件系统主要使用多线程技术实现多任务处理,完成对很多用户的并发请求处理。也就是我们开发的应用程序通常以一个进程的方式在操作系统中启动,然后在进程中创建很多线程,每个线程处理一个用户请求。

以Java的web开发为例,似乎我们编程的时候通常并不需要自己创建和启动线程,那么我们的程序是如何被多线程并发执行,同时处理多个用户请求的呢?实际中,启动多线程,为每个用户请求分配一个处理线程的工作是在web容器中完成的,比如常用的Tomcat容器。

如下图所示:

alt

Tomcat启动多个线程,为每个用户请求分配一个线程,调用和请求URL路径相对应的Servlet(或者Controller)代码,完成用户请求处理。而Tomcat则在JVM虚拟机进程中,JVM虚拟机则被操作系统当做一个独立进程管理。真正完成最终计算的,是CPU、内存等服务器硬件,操作系统将这些硬件进行分时(CPU)、分片(内存)管理,虚拟化成一个独享资源让JVM进程在其上运行。

以上就是一个Java web应用运行时的主要 架构,有时也被称作 架构过程视图。需要注意的是,这里有个很多web开发者容易忽略的事情,那就是 不管你是否有意识,你开发的web程序都是被多线程执行的,web开发天然就是多线程开发

CPU以线程为单位进行分时共享执行,可以想象代码被加载到内存空间后,有多个线程在这些代码上执行,这些线程从逻辑上看,是同时在运行的,每个线程有自己的线程栈,所有的线程栈都是完全隔离的,也就是每个方法的参数和方法内的局部变量都是隔离的,一个线程无法访问到其他线程的栈内数据。

但是当某些代码修改内存堆里的数据的时候,如果有多个线程在同时执行,就可能会出现同时修改数据的情况,比如,两个线程同时对一个堆中的数据执行+1操作,最终这个数据只会被加一次,这就是人们常说的 线程安全 问题,实际上线程的结果应该是依次加一,即最终的结果应该是+2。

多个线程访问共享资源的这段代码被称为 临界区,解决线程安全问题的主要方法是使用锁,将临界区的代码加锁,只有获得锁的线程才能执行临界区代码,如下:

lock.lock();  //线程获得锁
i++;  //临界区代码,i位于堆中
lock.unlock();  //线程释放锁

如果当前线程执行到第一行,获得锁的代码的时候,锁已经被其他线程获取并没有释放,那么这个线程就会进入阻塞状态,等待前面释放锁的线程将自己唤醒重新获得锁。

锁会引起线程阻塞,如果有很多线程同时在运行,那么就会出现线程排队等待锁的情况,线程无法并行执行,系统响应速度就会变慢。此外I/O操作也会引起阻塞,对数据库连接的获取也可能会引起阻塞。目前典型的web应用都是基于RDBMS关系数据库的,web应用要想访问数据库,必须获得数据库连接,而受数据库资源限制,每个web应用能建立的数据库的连接是有限的,如果并发线程数超过了连接数,那么就会有部分线程无法获得连接而进入阻塞,等待其他线程释放连接后才能访问数据库,并发的线程数越多,等待连接的时间也越多,从web请求者角度看,响应时间变长, 系统变慢

被阻塞的线程越多,占据的系统资源也越多,这些被阻塞的线程既不能继续执行,也不能释放当前已经占据的资源,在系统中一边等待一边消耗资源,如果阻塞的线程数超过了某个系统资源的极限,就会导致系统宕机, 应用崩溃

解决系统因高并发而导致的响应变慢、应用崩溃的主要手段是使用 分布式系统架构,用更多的服务器构成一个集群,以便共同处理用户的并发请求,保证每台服务器的并发负载不会太高。此外必要时还需要在请求入口处进行 限流,减小系统的并发请求数;在应用内进行业务 降级,减小线程的资源消耗。

总结

事实上,现代CPU和操作系统的设计远比这篇文章讲的要复杂得多,但是基础原理大致就是如此。为了让程序能很好地被执行,软件开发的时候要考虑很多情况,为了让软件能更好地发挥效能,需要在部署上进行规划和架构。软件是如何运行的,应该是软件工程师和架构师的常识,在设计开发软件的时候,应该时刻以常识去审视自己的工作,保证软件开发在正确的方向上前进。

本文由 mdnice 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/640557.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ChatGPT付费创作系统V2.0.2独立版+小程序安装教程

ChatGPT付费创作系统V2.0.2独立版播播资源测试了下相比,本版核心WEB端进行升级优化,前端增加了创作、模拟、使用帮助等选项,小程序端相比上一版无大的变化。体验下来问答速度感觉体验更好。小程序端有更新请对应开发工具更新上传,…

HAL库记录-SDRAM的使用

正点原子--阿波罗开发板STM32F429IGT6 CLK 时钟信号,在该时钟的上升沿采集输入信号 CKE 时钟使能,禁止时钟时,SDRAM 会进入自刷新模式 CS# 片选信号,低电平有效 RAS# 行地址选通信号&#xff…

剑指offer57.和为s的两个数字

双指针i从左往右,j从右往左,如果大于目标值,j往左走否则i往右走,直到等于目标值 class Solution {public int[] twoSum(int[] nums, int target) {int[] res new int[2];int i 0; int j nums.length-1;int sum nums[i] nums…

docker指令

镜像命令 镜像命令 命令描述docker images php查看名字为php的镜像docker images查看所有镜像docker search搜索镜像docker pull 镜像名:tag 拉取镜像docker rmi php删除php镜像 (保存php镜像到目录) docker image save php > D:\phpstudy_pro\WWW\docker\php.tgz 或者 …

【利用AI让知识体系化】简要了解面向对象编程设计

文章目录 I. 面向对象编程简介面向对象编程的定义与发展历程面向对象编程的优点和特点面向对象和面向过程和面向函数式编程之间的对比 II. 面向对象编程的基本概念类和对象抽象和封装继承和多态封装、继承和多态之间的一些对比 III. 面向对象编程设计原则单一职责原则&#xff…

如何搭建高可用redis架构?

题记 Redis 是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API。 如今,互联网业务的数据正以更快的速度在增长,数据类型越来越丰富,这对数据处理的速度和能力提…

chatgpt赋能python:Python怎么建网站的SEO

Python怎么建网站的SEO 介绍 Python是一种流行的编程语言,其灵活性和可靠性使其成为了许多网站开发人员的首选语言之一。Python可以用于构建各种类型的网站,包括电子商务和企业级应用程序等。但是,构建一个网站是不够的,您还需要…

力扣刷题记录--二叉树相关问题

目录 二叉树的前中后序遍历递归方法迭代方法(未统一写法)前序迭代中序迭代后序迭代 迭代方法(统一模板)二叉树遍历LeetCode 144. 二叉树的前序遍历LeetCode 145. 二叉树的后序遍历LeetCode 94. 二叉树的中序遍历 n叉树的遍历LeetC…

树莓派(raspbian2)上开发OpenCv_C++

树莓派[raspbian2]上开发OpenCv_C 背景故事一.首先就是搭建环境了1.该位置是环境下载地址2.远程连接3.安装OpenCV(如果使用的是上面的镜像,则不用安装) 二.代码示例1.打开摄像头2.采集图像 以上是2023-06-12的日志,接下来还有四天的实训!继续更新! 背景故事 最近刚好赶上学校的…

chatgpt赋能python:Python怎样进行快速复制?

Python怎样进行快速复制? 如果你经常使用Python编程,那么你了解到实现复制粘贴操作的重要性。可以通过快速复制代码片段,提高您的生产力和效率。在这篇文章中,我们将讨论一些Python中的技巧和工具,以便您进行快速复制…

《剑指 Offer--LeetCode 学习计划》-- 链表

剑指 Offer 06. 从尾到头打印链表&#xff08;Easy&#xff09; 题目描述 输入一个链表的头节点&#xff0c;从尾到头反过来返回每个节点的值&#xff08;用数组返回&#xff09;。限制&#xff1a;0 < 链表长度 < 10000。 举例说明 示例 1&#xff1a; 输入&#xf…

谈谈几个常见数据结构的原理

数组 数组是最常用的数据结构&#xff0c;创建数组必须要内存中一块 连续 的空间&#xff0c;并且数组中必须存放 相同 的数据类型。比如我们创建一个长度为10&#xff0c;数据类型为整型的数组&#xff0c;在内存中的地址是从1000开始&#xff0c;那么它在内存中的存储格式如…

【送书福利-第十期】清华社 IT BOOK 多得图书活动 ~!

大家好&#xff0c;我是洲洲&#xff0c;欢迎关注&#xff0c;一个爱听周杰伦的程序员。关注公众号【程序员洲洲】即可获得10G学习资料、面试笔记、大厂独家学习体系路线等…还可以加入技术交流群欢迎大家在CSDN后台私信我&#xff01; 本文目录 一、前言二、内容介绍三、抽奖方…

第一章JUC概述

文章目录 什么是JUC为什么学习好多线程很重要硬件来说软件来说存在的问题 Java多线程相关概念一把锁两个并三个程进程和程序的联系线程和进程的联系管程 用户线程和守护线程JAVA线程 什么是JUC 在 Java 5.0 提供了 java.util.concurrent &#xff08; 简称JUC &#xff09; 包…

Spring Cloud Alibaba - Sentinel源码分析

目录 一、Sentinel核心源码分析 1、Sentinel核心概念 1.1、Node之间的关系 2、Sentinel源码入口 2.1、SlotChain解析 2.2、NodeSelectorSlot解析 2.3、ClusterBuilderSlot解析 一、Sentinel核心源码分析 Sentinel是分布式系统的防御系统。以流量为切入点&#xff0c;通过…

001安装Jenkins

安装JenkinsJenkins 是一个开源自动化服务器http://www.jenkins.io/zh/doc/book/installing/#%E7%B3%BB%E7%BB%9F%E8%A6%81%E6%B1%82 docker docker run \-u root \--rm \-d \-p 8080:8080 \-p 50000:50000 \-v jenkins-data:/var/jenkins_home \-v /var/run/docker.sock:/va…

传感器融合概念及对比

1.多传感器融合的定义 传感器数据融合的定义可以概括为把分布在不同位置的多个同类或不同类传感器所提供的局部数据资源加以综合&#xff0c;采用计算机技术对其进行分析&#xff0c;消除多传感器信息之间可能存在的冗余和矛盾&#xff0c;加以互补&#xff0c;降低其不确实性…

记录好项目D2

记录好项目 你好呀&#xff0c;这里是我专门记录一下从某些地方收集起来的项目&#xff0c;对项目修改&#xff0c;进行添砖加瓦&#xff0c;变成自己的闪亮项目。修修补补也可以成为毕设哦 本次的项目是个旅游门户网站 技术栈&#xff1a;JSPjQueryAjaxechartsSpringSpring…

第四章LockSupport与线程中断

文章目录 线程中断机制面试题什么是中断机制?实现三种中断方式通过一个volatile变量实现通过AtomicBoolean&#xff08;原子布尔型&#xff09;通过Thread类自带的中断api方法实现 API源码分析当前线程的中断标识为true&#xff0c;是不是线程就立刻停止&#xff1f;后手案例-…

Vector-常用CAN工具 - Vector Hardware Manager

本文提供了有关 Vector 用于配置 Vector 接口的新工具Vector 硬件管理器(vHardwareManager) 的一些一般信息。 常见问题 1、什么是vHardwareManager&#xff1f; 2、哪些接口支持vHardwareManager&#xff1f; 3、什么时候需要vHardwareManager&#xff1f; 4、哪里可以下…