是什么使得我的进程崩溃了?

本文探讨了多种导致进程崩溃的原因,包括堆栈溢出、内存溢出、COM组件中未处理的异常等,并提供了使用windbg等工具进行debug的技巧。

原文地址: What on earth caused my process to crash?
发布时间:Monday, November 28, 2005 8:27 AM
作  者: Tess   

 

你在事件查看器中看到w3wp.exe意外地停止了1000次,或者你的进程以一种未定义的方式神秘地退出了,可你不知道为什么。

当进程崩溃或退出时,一个特殊的事件将会被触发,这个事件叫做EPR(Exit Process)。因此借助调试器,如windbg.exe,可以把它附在进程上,等待EPR被抛出异常,做一个memory dump。在windows下安装好调试工具后,会得到叫做adplus的vbscript(http://support.microsoft.com/default.aspx?scid=kb;en-us;286350) ,它会自动运行,打印在整个进程生存期内发生的大多数异常的日志。

调试技巧!:在crash模式下打开一个dump,当程序崩溃时,它会在活动线程上自动定位崩溃处。若你切换线程到出错的线程,输入 ~ 将所有的线程列举出来,一个断点会标记有问题的线程。

若dump只显示在进程里有一个活动线程,并且该线程是主线程,那么该进程可能因外部因素被杀死,譬如健康监视器(health monitoring),低系统内存,IIS重启等等。

在后续的帖子里,我会更为深入地讨论一些情景。我是一个自始自终的人,因此我想以一些常见的情形开始本帖子。当你看到一个托管进程退出时,你会对在dump文件里该查看些什么心中有点数。

这是一些在支持中的最常见情形,并不按照特殊顺序排列:


堆栈溢出异常

当一个线程分配的堆栈内存用尽了,就会发生堆栈溢出。默认分配1MB,故堆栈调用可以调得相当深。在大多数情况下,堆栈溢出是因无穷递归引起的, 譬如,函数A调用函数B,函数B又调用函数A... 以至无穷,没有停止的条件。

异常处理应用程序块使用不恰当,这是常见的一个隐晦的无穷递归情形。想象一下这个情形:应用程序发生了一个异常,异常处理跟踪了该异常,并建立了一个日志文件。在登陆时遇到了一个这种类型的异常(禁止登陆),此时你会使用异常处理去处理它。在这个案例中,在处理该异常时,会产生一个无穷的递归循环,它抛出另一个异常,处理该异常时又抛出一个异常...此时你该明白了。在这儿想说明的是,不要再用异常处理去处理先前异常处理语句中产生的异常:)

运行"kb 2000(参阅本地堆栈)和"!clrstack"(从sos.dll 可以查看托管堆栈),可以跟踪到递归在哪里发生的,为什么发生的。


内存溢出异常

 发生内存溢出在大多数情况下是因设计问题引起的,太多的内存存储在缓存或Session中。如果使用恰当,缓存可以极大地提升性能,譬如,将经常使用的数据缓存,按需要的时间设置过期。相信我,在老的asp技术中,将对象存入Session中有时会出现问题。开发人员应仅将最必要的东西存入Session。但是,譬如将大的数据集存放在Session中,这反而有害于应用程序的性能,因为这将降低网站能够处理的并发用户数量。当内存使用率足够高时,最先要做的事情可能是花时间进行垃圾对象回收。通过使用缓存搜索数据,你可以避免从数据库中取得所需数据。

是否需要将数据存储在Session/缓存中,这没有一个固定的方法适合所有的情形。最好的办法是早期评估确定,确定应用程序有多少用户以及基于此分析出每个用户允许存储量是多少。 然后按照最大数量的用户做一下压力测试,确保没有问题。对session中存储和不存储对象都做一下压力测试,看看哪个更好,将会是更好的。不同的用户数得出的结果是不同的。

软件产品中的内存问题是非常难以修复的,因它常常需要做很多的重新设计。因此未雨绸缪将省下后期的很多工作。

测试技巧!:先运行 !dumpheap -type System.Web.Caching.Cache 得到cache的根源,然后在相应地址上使用!objsize ,查看cache存储了多少资源。(注释: InProc Session 模式也存储在Cache中)

关于为什么会发生内存溢出异常的更多深入的讨论,可以参看我先前的帖子。


COM 组件中未处理的异常

若应用程序调用了本地的COM组件,因COM组件中有未处理的异常,应用程序也会崩溃。例如,引用了某块内存,但它已经释放了。


本地堆损毁
 

 这和GC漏洞都是一些最烦人的问题。向一个非假定的地址写入时,将发生本地堆损毁。执行写错地址的代码时,不会得到错误提示信息,然而开发人员并不知试图写入的内存地址是错误的。换句话说,那儿早已经有“贼”了。错误写入处可能是堆,但更糟糕的是,可能写入的是存放代码指令处,因此先前指令会被重写,这样代码执行到中间就无法继续调用了。向缓冲区的边界(或其他类似存储区)写入时,这种情况发生最频繁。
 
阅读Geoff Gray's 的有关堆损毁的文章,有对此的介绍。因堆损毁而造成的崩溃,出问题处通常会在ntdll中的堆分配调用函数。需要一同运行GFlags或PageHeap,抓住这个“贼”,才能解决问题。然而,发生地址错误写入的情况很难捕获到,因为它的发生时机很任意且难以再现。


托管堆损毁

托管堆损毁是一种发生在托管堆上的堆损毁。这个问题也是很难被捕获。向托管堆上覆写一块不允许写入的内存时,将发生托管堆损毁。通常不会在托管代码中发生缓冲溢出。对于byte[]这样一个数组,若对其赋值超出了边界,将会发生一个IndexOutOfRange异常。托管堆损毁的一个最常见的原因是一段叫做PInvoke的函数代码传入了按某种条件排序的缓冲区,但缓冲区容量太小,PInvoked函数向缓冲区写操作时,超出了缓冲区的边界,写到了托管堆的下一个对象上。然后垃圾回收器工作,试图穿过该托管堆,此时进程就会崩溃了。

若在一个含有垃圾回收器功能的活动堆栈上发生崩溃,应查询代码中的PInvokes函数,查是否因缓冲区太小而发生穿越行为。


致命执行引擎异常

致命执行引擎异常非常罕见,发生致命执行引擎异常通常是一个bug。这意味着由于某些原因,代码执行进入了CLR中一些未预料到的代码段中。CLR会因这些不可靠事件进入而抛出一个致命执行引擎异常并且崩溃,因为它不能从该断点再恢复。它会作为发生致命执行引擎异常记录在事件日志中,列出的地址将是发生崩溃的准确地址。若发生致命执行引擎异常,找不到相关技术文章,联系技术支持,最好附上一个崩溃的dump,这样易于技术支持决解决该问题。


GC漏洞

 这也是非常罕见的。CLR的非托管部分有一个指针,它指向托管代码,但它“忘记”告诉垃圾回收器相关信息了。 因此垃圾回收器不知道该如何保存现场或者不知如何跟踪指令。上述的意思是:垃圾回收执行清理的时机有误,此时那个指针可指向任何一个地方,并引起了许多破坏。Yun Jin在这儿对此有一点点讨论:http://blogs.msdn.com/yunjin/archive/2004/02/08/69906.aspx

 

转载于:https://www.cnblogs.com/Ring1981/archive/2006/10/24/450628.html

<think>我们正在讨论操作系统进程树概念。根据用户要求,我们需要定义进程树,解释其作用,并参考之前提到的引用内容。 引用[1]提到了进程的基本概念,引用[2]提到了进程的三种状态,引用[3]提到了就绪队列的调度。这些内容可以帮助我们理解进程树在操作系统中的角色。 进程树的定义: 在操作系统中,进程树(Process Tree)是一种表示进程间父子关系的树状结构。每个进程(除了初始进程)都有一个父进程,并且可能有零个或多个子进程。这种关系形成了一棵树,其中: - 根节点是操作系统的初始进程(通常是init或systemd,其PID=1)。 - 其他进程都是直接或间接由根进程派生的。 进程树的作用: 1. **资源管理**:父进程负责创建子进程,并可能负责回收子进程的资源(通过wait系统调用)。当父进程先于子进程退出时,子进程成为孤儿进程,被init进程接管(引用[1]中提到进程管理,以及之前回答孤儿进程时提到init进程接管)。 2. **进程组织**:进程树提供了一种组织所有进程的方式,使得操作系统能够按照层次结构管理进程。例如,通过`ps -ef`命令可以查看进程树(在Unix/Linux系统中常用`pstree`命令)。 3. **权限控制**:子进程通常继承父进程的权限(用户ID、组ID等),这有助于系统安全(引用[1]中提到的权限继承)。 4. **进程间通信**:父子进程之间或兄弟进程之间可以通过进程树的关系进行通信(如管道、信号等)。 5. **系统初始化与关闭**:操作系统启动时,init进程作为所有进程的祖先,负责启动系统服务。在系统关闭时,init进程负责向所有子进程发送终止信号,并等待它们退出,从而有序关闭系统。 引用[3]中提到了就绪队列的调度,虽然直接讲的是进程调度,但进程树的结构与调度也有间接关系。例如,在有些操作系统中,进程的调度优先级可能受到其父进程的影响(如通过nice值调整)。 进程树的表示: 我们可以用树状结构表示,例如: ``` init (PID=1) ├─ bash (PID=1234) │ └─ ls (PID=1256) ├─ httpd (PID=567) │ ├─ httpd (PID=568) │ └─ httpd (PID=569) └─ cron (PID=789) ``` 在操作系统中,进程树通过进程控制块(PCB)中的指针来维护,每个进程的PCB中会记录其父进程的PID和子进程的链表(或指针)。 总结:进程树是操作系统中用于组织进程的层次结构,它对于资源管理、权限控制、系统启动和关闭等至关重要。 根据用户要求,我们需要生成相关问题。结合之前关于孤儿进程的讨论,以及本次进程树的概念,我们可以提出以下问题: §§相关问题§§ 1. 进程树在操作系统启动过程中扮演什么角色? 2. 如何通过命令行工具(如Linux中的pstree)查看进程树? 3. 进程树的结构如何影响进程间通信(IPC)? 4. 在什么情况下进程树的结构会发生改变?请举例说明。</think>### 操作系统进程树详解 #### 进程树的定义 进程树(Process Tree)是操作系统中描述进程间父子关系的层次结构。其核心特征包括: 1. **树状结构**: - 根节点为初始进程(PID=1的init/systemd)[^1] - 每个进程有且仅有一个父进程(除根进程外) - 进程可创建多个子进程,形成树状分支 ```mermaid graph TD A[init PID=1] --> B[bash PID=123] A --> C[httpd PID=456] B --> D[ls PID=789] C --> E[httpd worker PID=101] C --> F[httpd worker PID=102] ``` 2. **组成要素**: - **进程控制块(PCB)**:存储进程状态、PID、PPID等元数据[^1] - **父子指针**:内核维护的进程间关联指针 - **继承关系**:子进程继承父进程的: - 文件描述符表 - 环境变量 - 用户/组权限 #### 进程树的核心作用 ##### 1. **资源生命周期管理** ```mermaid sequenceDiagram 父进程->>子进程: fork()创建 子进程->>父进程: exit()终止时发送SIGCHLD 父进程->>内核: wait()回收资源 ``` - 父进程通过`wait()`系统调用回收子进程资源[^1] - 孤儿进程自动由init接管,避免资源泄漏[^1] - **资源回收链**:$$ \text{父进程} \xrightarrow{\text{wait()}} \text{子进程} \xrightarrow{\text{exit()}} \text{资源释放} $$ ##### 2. **进程状态管理** | 进程状态 | 在进程树中的表现 | 管理机制 | |---------|----------------|---------| | 就绪态 | 位于就绪队列分支 | 时间片轮转调度[^3] | | 运行态 | 当前执行的叶子节点 | CPU分配 | | 阻塞态 | 等待资源的子树 | 唤醒通知机制[^2] | - 调度器通过进程树定位就绪进程[^3] - 阻塞进程形成独立子树,减少调度开销 ##### 3. **系统容错机制** - **孤儿进程处理**: ```c // 内核孤儿处理逻辑 if (父进程退出 && 子进程存活) { 子进程->ppid = 1; // 重定向到init 唤醒init的wait()调用; } ``` - 确保即使父进程崩溃,子进程仍能被正确回收 ##### 4. **权限控制与隔离** - **最小权限原则**:子进程继承父进程权限边界 - **安全沙箱**:通过子树实现资源隔离 $$ \text{进程树深度} \propto \frac{1}{\text{权限范围}} $$ ##### 5. **系统监控与调试** - `pstree`等工具可视化进程关系 - 通过PPID快速定位异常进程来源 - 杀进程时自动清理子树(如`kill -TERM --ppid`) #### 进程树的动态演化 1. **分支创建**:`fork()` + `exec()` 2. **节点消亡**:`exit()` + `wait()` 3. **结构调整**: - 进程终止导致子树解体 - `prctl(PR_SET_CHILD_SUBREAPER)`设置子树守护 > **案例**:Web服务器进程树 > ``` > systemd─┬─nginx───5*[nginx worker] > ├─php-fpm───10*[php worker] > └─cron > ``` > - 工作进程崩溃时,主进程回收资源 > - worker进程共享父进程监听的端口 #### 设计哲学体现 1. **层次化治理**:通过树结构实现分治管理 2. **故障隔离**:子树崩溃不影响其他分支 3. **资源继承**:避免重复分配的开销 4. **生命周期绑定**:父进程对子进程的资源负责 进程树本质是操作系统对**进程生态系统的拓扑抽象**,如同社会组织中的家族系谱,既实现高效管理,又提供故障隔离的天然屏障。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值