linux内核设计与实现——进程(第3章和第4章)

1. 进程

进程就是处于执行期的程序。但进程不仅仅包括可执行程序代码,还包括打开的文件、内存、信号量、处理器状态等资源。

线程是进程中活动的对象。每个线程都拥有一个独立的程序计数器、进程栈和一组进程寄存器。内核调度的对象是线程,而不是进程。

linux对线程和进程并不特别区分,线程是一种特殊的进程,它会与其他进程共享某些资源。

1.1 进程描述符

内核把进程的列表存放在叫做任务队列的双向循环链表中,链表中的每一项都是一个结构体task_struct,它被称为进程描述符。该结构体定义在 linux/sched.h 中,它几乎包含了一个进程的所有信息,所以非常复杂,其大致结构如下图所示:
这里写图片描述

进程描述符与任务队列的关系如下图所示:

进程描述符及任务队列

linux通过slab分配器分配task_struct结构。slab分配器在《linux内核设计与实现》的第12章“内存管理”有详细讲解。

1.2 进程状态

进程描述符中的state字段描述了进程的当前状态(参见图3-3)。系统中的每个进程都必然处于五种进程状态中的一种。state的值也必为下列五种状态标志之一:

  • TASK_RUNNING(运行)—进程是可执行的;它或者正在执行,或者在运行队列中等待执行(运行队列将会在第4章中讨论)。这是进程在用户空间中执行惟一可能的状态;也可以应用到内核空间中正在执行的进程。

  • TASK_INTERRUPTIBLE(可中断)—进程正在睡眠(也就是说它被阻塞),等待某些条件的达成。一旦这些条件达成,内核就会把进程状态设置为运行。处于此状态的进程也会因为接收到信号而提前被唤醒并投入运行。

  • TASK_UNINTERRUPTIBLE(不可中断)—除了不会因为接收到信号而被唤醒从而投入运行外,这个状态与可打断状态相同。这个状态通常在进程必须在等待时不受干扰或等待事件很快就会发生时出现。由于处于此状态的任务对信号不作响应,所以较之可中断状态,使用得较少。

  • TASK_ZOMBIE(僵死)—该进程已经结束了,但是其父进程还没有调用wait4()系统调用。为了父进程能够获知它的消息,子进程的进程描述符仍然被保留着。一旦父进程调用了wait4(),进程描述符就会被释放。

  • TASK_STOPPED(停止)—进程停止执行;进程没有投入运行也不能投入运行。通常这种状态发生在接收到SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU等信号的时候。此外,在调试期间接收到任何信号,都会使进程进入这种状态。

进程状态转化

1.3 进程家族树

在linux系统中,所有的进程都是PID为1的init进程的后代。

系统中的每个进程必有一个父进程,相应的,每个进程也可以拥有0个或多个子进程。

进程间的关系存放在进程描述符task_struct结构体中,每个task_struct都包含一个指向其父进程的指针parent,和一个指向其子进程链表的指针children。

2. 进程管理

linux进程通过调用fork()函数复制当前进程来创建一个子进程,通过exec()函数读取可执行文件并将其载入地址空间开始运行,最后通过exit()函数终结自己并将占用的资源释放掉。

进程退出执行后被设置为僵死状态,直到它的父进程调用wait()或waitpid为止。父进程可以通过wait4()查询子进程是否终结。

2.1 进程创建

linux的fork()使用写时拷贝页实现。在创建子进程后,内核并不会复制整个地址空间,而是让父子进程共享地址空间,只有在进程地址空间的内容要发生变化时,才会将父进程变化的地址空间复制一份给子进程。在一般情况下,进程创建后都会立即调用exec()函数来运行一个可执行文件,写时拷贝技术可以避免复制大量根本就不会被使用的数据。

这样,fork()的实际开销就是复制父进程的页表以及给子进程创建一个唯一的进程描述符。

fork()实际上是由clone()系统调用实现的,它将需要的参数标志传递给clone(),然后由clone()去调用do_fork(),do_fork()调用copy_process()函数,让进程开始运行。

copy_process()函数为新进程创建一个内核栈、thread_info结构和task_struct结构。根据传递给clone()的参数标志,copy_process()拷贝或共享打开的文件、文件系统信息、信号处理函数、进程地址空间和命名空间等。

fork()之后,内核会通过将子进程放在队列的前面,让子进程先执行。因为一般子进程都会马上调用exec()函数,这样可以避免写时拷贝的额外开销。

2.2 进程终结

当一个进程终结时,内核必须释放它所占用的资源,并把这一消息告知其父进程。

进程调用exit()函数执行终结任务,exit()调用do_exit()函数来完成大部分的资源释放工作。

在do_exit()之后,进程已经僵死不能再运行了,但是系统还保留了它的进程描述符,这样做可以让系统有办法在子进程终结后仍能获得它的信息。在父进程获得已终结的子进程的信息后,或者通知内核它并不关注那些信息后,子进程的task_struct结构才被释放。

如果父进程在子进程之前退出,必须有一个机制来保证子进程能找到一个新的父亲,否则这些成为孤儿的进程就会在退出时永远处于僵死状态,白白地耗费掉内存。对于这个问题,解决方法是给子进程在当前进程组内找一个进程作为父亲,如果不行,就让init做它们的父进程。

2.3 线程在linux中的实现

linux实现线程的机制非常独特。从内核的角度来说,它并没有线程这个概念,而是把所有的线程都当作进程来实现。线程仅仅被视为一个与其他进程共享某些资源的进程。

线程的创建和普通进程的创建类似,只不过在调用clone()的时候需要传递一些参数标志来指明需要共享的资源:

/* 指明父子进程共享地址空间、文件系统资源、文件描述符和信号处理程序 */
clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);

内核经常需要在后台执行一些操作,这可以通过内核线程完成。内核线程和普通进程的区别在于内核线程没有独立的地址空间(它的task_struct结构的mm指针被设置为NULL),它只在内核空间运行,从来不切换到用户空间去。内核线程和普通进程一样,可以被调度,也可以被抢占。

3. 进程调度

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在风能领域,准确预测风速对于风电场的运行与管理至关重要。Matlab作为一个强大的数学计算和数据分析平台,被广泛应用于风速预测模型的构建。本文将深入探讨基于四种风速——随机风、基本风、阵风和渐变风的组合风速预测技术。 我们来理解这四种风速类型: 1. **随机风**:随机风是指风速呈现出随机性的变化,通常由大气湍流引起。在建模中,通常通过统计方法如高斯分布或Weibull分布来模拟这种不确定性。 2. **基本风**:基本风速是指在无特定扰动条件下的平均风速,它是长期观测结果的平均值,通常用于结构设计和风能评估。 3. **阵风**:阵风是短时间内风速显著增强的现象,对建筑物和风力发电机造成的主要威胁之一。阵风的预测涉及到风的脉动特性分析。 4. **渐变风**:渐变风是指风速随时间和空间逐渐变化的过程,常见于风向转变或地形影响下的风场变化。 在Matlab中,利用这四种风速类型进行组合预测,可以提高预测的准确性。预测模型可能包括以下几个步骤: 1. **数据收集与预处理**:收集历史风速数据,包括随机风、基本风、阵风和渐变风的数据,进行异常值检测、缺失值填充以及数据标准化。 2. **特征工程**:提取风速变化的相关特征,如平均值、标准差、极值、频率分布等,这些特征可能对预测有重要影响。 3. **模型选择**:可以选择多种预测模型,如时间序列分析(ARIMA、状态空间模型等)、机器学习算法(线性回归、决策树、支持向量机、神经网络等)或深度学习模型(LSTM、GRU等)。 4. **模型训练**:利用历史数据训练选定的模型,调整模型参数以优化性能,例如通过交叉验证来避免过拟合。 5. **模型验证与评估**:使用独立的测试集验证模型预测效果,常见的评估指标有均方误差(MSE)、平均绝对误差(MAE)和决定系数(R²)。 6. **组合预测**:结合四种风速的不同模型预测结果,可以采用加权平均、集成学习(如bagging、boosting)等方式,以提升整体预测精度。 7. **实时更新与动态调整**:实际应用中,模型需要不断接收新的风速数据并进行在线更新,以适应风场环境的变化。 通过以上步骤,可以构建一个综合考虑各种风速特性的预测系统,这对于风电场的功率输出预测、风电设备的维护计划以及电网调度都具有重要价值。然而,需要注意的是,每个风场的地理环境、气候条件和设备状况都有所不同,因此模型的建立应根据实际情况进行定制和优
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值