Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说
linux进程指令_linux基础知识点,希望能够帮助你!!!。
接Linux进程详解(一)
4. 进程运行
程序运行时大部分进程状态为运行或睡眠。调度算法解决可以跑的运行状态(就绪和运行),剩下的不可以跑的进程就是睡眠和等待。睡眠实现对应的代码就是调用了schdule函数,唤醒则是对应的是schdule返回。一个进程等资源就会去睡,linux所有的睡眠,对应的task_struct就会挂在队列wait_queue上,当资源来了后,就会唤醒等待队列上的进程。
5. 进程死亡
fork执行后,就会变成2个进程返回,而不是一个进程返回两次。两个进程用的是同一段代码,不同的是在判断fork的返回值后会走向不同的分支。子进程返回的是0,则if(pid == 0)后执行的是子进程,父进程接收到的返回值是子进程的pid值。如下所示:
fork之后:
返回值为-1....................................................fork 失败
返回值为 0....................................................子进程返回
返回值为 pid 号............................................父进程返回
5.1 子死父收尸
linux中子进程死亡时首先变成僵尸,父进程通过wait来获取子进程的死亡原因。调用的API如图5-1所示,父进程通过分析子进程的退出码就可以知道具体的退出原因了。下面是源代码:kernel-4.1.10
/*
* Handle sys_wait4 work for one task in state EXIT_ZOMBIE. We hold
* read_lock(&tasklist_lock) on entry. If we return zero, we still hold
* the lock and this task is uninteresting. If we return nonzero, we have
* released the lock and the system call should return.
*/
static int wait_task_zombie(struct wait_opts *wo, struct task_struct *p)
{
unsigned long state;
int retval, status, traced;
pid_t pid = task_pid_vnr(p);
uid_t uid = from_kuid_munged(current_user_ns(), task_uid(p));
struct siginfo __user *infop;
if (!likely(wo->wo_flags & WEXITED))
return 0;
if (unlikely(wo->wo_flags & WNOWAIT)) {
// 父进程通过exit_code获取子进程退出信息
int exit_code = p->exit_code;
int why;
get_task_struct(p);
read_unlock(&tasklist_lock);
if ((exit_code & 0x7f) == 0) {
why = CLD_EXITED;
status = exit_code >> 8;
} else {
why = (exit_code & 0x80) ? CLD_DUMPED : CLD_KILLED;
status = exit_code & 0x7f;
}
return wait_noreap_copyout(wo, p, pid, uid, why, status);
}
/*
* Try to move the task's state to DEAD
* only one thread is allowed to do this:
*/
state = xchg(&p->exit_state, EXIT_DEAD);
if (state != EXIT_ZOMBIE) {
BUG_ON(state != EXIT_DEAD);
return 0;
}
traced = ptrace_reparented(p);
/*
* It can be ptraced but not reparented, check
* thread_group_leader() to filter out sub-threads.
*/
if (likely(!traced) && thread_group_leader(p)) {
struct signal_struct *psig;
struct signal_struct *sig;
unsigned long maxrss;
cputime_t tgutime, tgstime;
/*
* The resource counters for the group leader are in its
* own task_struct. Those for dead threads in the group
* are in its signal_struct, as are those for the child
* processes it has previously reaped. All these
* accumulate in the parent's signal_struct c* fields.
*
* We don't bother to take a lock here to protect these
* p->signal fields, because they are only touched by
* __exit_signal, which runs with tasklist_lock
* write-locked anyway, and so is excluded here. We do
* need to protect the access to parent->signal fields,
* as other threads in the parent group can be right
* here reaping other children at the same time.
*
* We use thread_group_cputime_adjusted() to get times for the thread
* group, which consolidates times for all threads in the
* group including the group leader.
*/
thread_group_cputime_adjusted(p, &tgutime, &tgstime);
spin_lock_irq(&p->real_parent->sighand->siglock);
psig = p->real_parent->signal;
sig = p->signal;
psig->cutime += tgutime + sig->cutime;
psig->cstime += tgstime + sig->cstime;
psig->cgtime += task_gtime(p) + sig->gtime + sig->cgtime;
psig->cmin_flt +=
p->min_flt + sig->min_flt + sig->cmin_flt;
psig->cmaj_flt +=
p->maj_flt + sig->maj_flt + sig->cmaj_flt;
psig->cnvcsw +=
p->nvcsw + sig->nvcsw + sig->cnvcsw;
psig->cnivcsw +=
p->nivcsw + sig->nivcsw + sig->cnivcsw;
psig->cinblock +=
task_io_get_inblock(p) +
sig->inblock + sig->cinblock;
psig->coublock +=
task_io_get_oublock(p) +
sig->oublock + sig->coublock;
maxrss = max(sig->maxrss, sig->cmaxrss);
if (psig->cmaxrss < maxrss)
psig->cmaxrss = maxrss;
task_io_accounting_add(&psig->ioac, &p->ioac);
task_io_accounting_add(&psig->ioac, &sig->ioac);
spin_unlock_irq(&p->real_parent->sighand->siglock);
}
5.2 父死子托孤与subreaper
任何一个进程死亡后有个原则,首先托付给subreaper,如果没有subreaper则托付给init。如图下图所示。进程可以通过API来设置自己为subreaper。sub-reaper要注意调用wait来处理可能托付过来的僵尸进程。
6. 进程分类
6.1 CPU消耗与IO消耗型
CPU消耗型是指CPU占用率高的应用,比如编译代码。IO消耗型指像读硬盘之类的应用,大部分时间消耗在DMA上,CPU占用率比较低。典型的操作系统内,IO消耗型的优先级比较高。因为IO消耗型应用往往与用户体验密切相关,比如读写硬盘和外设之类的操作,用户操作键盘和鼠标时如果长时间没有反应,就会导致体验很差。而CPU消耗型,比如编译程序,我们可以把它的优先级降低,编译时间从10分钟变成11分钟,对用户的体验影响不是很大。
IO 型对CPU的强弱不敏感,对何时抢到CPU敏感。因为处理时间多数花费在非CPU的计算上,CPU处理占用的比例很小,因此CPU的强弱对IO消耗影响不大。
6.2 应用:ARM大小核设计
从用户体验上,CPU运算能力越快越好,但CPU能力越强,功耗也越大。 为了实现处理相同任务花费的时间和功耗更低的目标,arm采用了大核加小核的设计模式。大核CPU运算力强,功耗高,小核CPU运算力弱,功耗低。CPU调度算法根据CPU消耗型和IO消耗型的特点来分配任务到不同的核上来实现低功耗和高性能的目标。
7. 进程调度策略
linux调度算法的设计目标是满足2个指标:吞吐量与响应。这两个指标是矛盾的,一个指标的上升必然影响到另一个指标的性能。从用户的角度来看,吞吐量是完成所有工作负载花费时间最少,响应是指处理任务时响应时间最短。简单地说就是linux在单位时间内切换任务的次数越多,响应任务的时间越快,但由于切换任务所需的上下文开销,会导致吞吐量的下降。根据不同的应用场景,我们会选择不同的算法来达到这两个指标的平衡。
linux的进程分RT进程和NORMAL进程两种,RT进程没有nice值,执行自己的调度算法。NORMAL进程根据自己的nice值采用相应的算法进行调度。 Linux 2.6之前执行优先级与nice值相关,nice值随进程睡的时间动态相关,普通NORMAL进程的nice值执行动态变化的策略,睡得越久,优先级越高。2.6 之后增加了2 个补丁:熔断机制补丁和CFS调度算法。
1. 熔断补丁: 限制了RT进程和NORMAL进程的比例,当RT进程一直占用CPU到了熔断阈值的时刻,强制调度让NORMAL进程运行。
2. CFS调度算法: 改进了NORMAL进程的调度算法,采用红黑树实现完全公平的调度策略。
7.1 RT进程调度
Linux调度进程时主要考虑两个要素:策略和优先级。RT进程没有nice值。 其调度策略分成SCHED_FIFO和SCHED_RR两种。S Linux 所有的优先级分成0-139,其中0-99为RT进程,100-139对应普通进程(nice值-20到19)。数字越小优先级越高。RT进程和普通进程的调度区别如下:
1. RT进程优先级高的进程未睡眠,优先级低的进程无法抢占。
2. 普通进程优先级低的进程也可以抢占高优先级的普通进程,区别在于抢到的 CPU 时间会比较少。
SCHED_FIFO:如果进程优先级最高,进程就会一直运行到睡眠才让出 CPU。
SCHED_RR:RR 的含义是 round-robin,CHED_FIFO 和 SCHED_RR 在不同优先级上表 现完全一样。区别在于同等优先级的调度上,SCHED_FIFO 策略对应的进程会 一直跑到睡眠才轮转到其它同等优先级的 RT 进程运行。SCHED_RR 会根据时 间片进行轮转。
7.2 NORMAL 进程调度
动态惩罚与奖励机制:
Linux 2.6 早期版本优先级不完全取决于代码中设置的进程nice值,nice值设置之后还会对普通进程进行一个评估,其优先级是动态调整的。评估标准是睡眠越久,优先级越高。优先级是随着睡眠时间的增加而增加,占用CPU的时间越长,优先级会随着降低。此算法的缺点是进程干活越久优先级越低,会导致经常处理任务的进程优先级越来越低,干的活越来越少。之后加入的补丁引入了CFS 算法修正了这个缺陷。
CFS 调度:
CFS(Completely Fair Scheduler) 完合公平调度指的是保证所有NORMAL
进程(task_struct)虚拟运行时间完全相同。其虚拟运行时间的计算公式为
= ∗ 1024/h
ptime:指进程运行物理时间 1024:系数
weight:权重,由 nice 值决定。
所有虚拟时间挂在红黑树上,每次 linux 调度虚拟时间最小的进程运行。
随着时间运行,分子pruntime变大,vruntime也就变大,优先级变低。喜欢睡眠、IO消耗型的进程,分子小。nice值低的,分母大。但是RT的进程,优先级高于所有普通的进程。
红黑树实现的CFS,用分子pruntime来照顾 睡眠情况,用分母来照顾nice值。
当进程里fork了多个线程,每个线程的 调度策略都可以不同,优先级可以不同。原因显然。
8. 进程调整优先级
调度优先级是内核分配给进程的代表执行先后可能的整数(-20-19)整数值越小,优先级越高。bash shell默认以优先级0来启动所有进程,可通过nice命令和renice调整
8.1 用NICE改变进程优先级
对于普通用户来说,只可以以更低优先级运行命令,更高优先级运行命令需要高级用户权限。nice命令是为未运行命令指定运行时调度优先级的,如果是已运行的命令则需要renice命令。
# 以 10 优先级值后台运行 httpd 命令
nice -n 10 httpd &
-n 后面整数指定 httpd 命令运行的优先级
httpd 即要改变优先级的命令
& 表示此命令为后台运行
8.2 用RENICE改变进程优先级
renice命令与nice命令用法一样,限制也一样(普通用户只能以更低的调 度优先级运行命令),惟一不同就是可以更新正在运行命令的调度优先级。
Linux renice 命令 参数含义:
-g 使用程序群组名称,修改所有隶属于该程序群组的程序的优先权。
-p 改变该程序的优先权等级,此参数为预设值。
-u 指定用户名称,修改所有隶属于该用户的程序的优先权。
nice 命令改变进程优先级 #PID 为 5200 的进程 nice 设为-5
renice -5 -p 5200
8.3 用CHRT改变进程优先级
使用实时调度策略,必须具有root权限。chrt命令的策略选项
Linux chrt 命令参 数含义
Linux chrt 命令参 数含义
短选项 长选项 詳細
-f –fifo 调度器设成 SCHED_FIFO
-o –other 调度器设成 SCHED_OTHER
-r –rr 调度器设成 SCHED_RR
-a –all-tasks 设置 Pid 号对应的所有线程
9. 负载均衡
9.1 LINUX下的负载均衡处理对象
负载均衡是最大化利用CPU资源的方法,要求在有任务(task_struct,中断,软中断) 执行时,所有的CPU都能利用上,不产生有任务处理却有CPU闲置的情况。首先从任务的优先级的角度来看,CPU处理的任务只有下面4种优先级,按高到低依次是:
优先级Linux中CPU所处状态
1 中断
2 软中断
3 处于 spinlock 等关闭了调度区间的进程
4 普通进程
附注: 优先级数字越低优先级越高:中断 > 软中断 > spinlock > 普通进程
9.2 中断负载均衡
在TOP命令中,cpu时间占用中有一列是hi和si,分别对应中断和软中断。说明cpu时间除了在task_struct上,还有可能花在中断和软中断,当网络流量比较大时,cpu花在中断和软中断的时间比较大,可以考虑中断负载均衡。
分配IRQ到某个CPU,掩码01代表CPU0,02代表CPU1,04代表CPU2, 08代表CPU3
以上优先级的任务在 linux 处理规则如下:
1. 中断不可以嵌套中断,在2.6版本后,处于中断区间再次发生中断时,会等到前一个中断执行结束后再进行处理下一个中断。
2. 中断可以唤起软中断
3. 软中断可以唤起中断
4. CFS 等调度算法只处理普通进程和普通进程之间的调度,不涉及中断, 软中断,及关闭了调度的进程。具体表现如下:
• 如果CPU在处理1,2,3优先级的任务时,不受调度算法的调度,只有 等处理完 1,2,3 优先级的任务后才会再由调度算法调度。
• 如果CPU在处理4普通进程的任务时,高优先级的中断和软中断可以直接抢占普通进程,不用等调度算法调度。
9.3 软中断负载均衡–RPS
有时候有的网卡只有一个队列,一个队列的中断只能分配到一个核,Linux 设计是一个核上抛出的软中断只能在同一个核上执行,cpu 0 上的中断抛出一 个软中断,tcp/ip 协议栈也只能在 cpu 0 的软中断上处理。google 在 linux 内核 里面加入了 rps 补丁,其作用是尽管中断是在一个 cpu 核上,但 tcp/ip 协议处 理的软中断可以在多个核上进行处理。rps 的原理是收到软中断后通过核间中 断的方法给另外的核发中断,让其他核处理软中断,从而支持单队列情况下的 网络负载均衡。
9.4 进程间(TASK_STRUCT)负载均衡
linux负载均衡算法原则
linux下所有CPU核会进行分布式的PUSH和PULL操作,当CPU核空闲时会向周围的核PULL任务来执行,CPU核本身在执行任务时也会 PUSH任务到其他核。每个核执行同样的负载均衡算法,负载均衡包括RT任务的负载 均衡和普通任务的负载均衡。
RT任务的负载均衡算法是将N个优先级最高的RT分布到N个核。 pull_rt_task(); push_rt_task()
普通任务负载均衡有三种:IDLE式负载均衡,周期性负载均衡,FORK和EXEC式负载均衡
1 周期性负载均衡: 时钟tick的时间点上CPU核查询自己是否很闲,周围核是否很忙,是则用PULL将周围核的任务拉过来处理。
2 IDLE式负载均衡: 当CPU核在IDLE时会查询周围核是否在忙,如果旁边核比较忙时,自动PULL旁边核的task_struct任务来执行。
3 FORK和EXEC式负载均衡: FORK和EXEC创建一个新的进程时,Linux会自动找一个最闲的核将FORK和EXEC新创建出的进程放在上面处理。
以上处理由核与核之间分布式负载均衡处理是自动进行的。
注:
在一个程序中起2个进程,每个进程都在做CPU消耗型操作(代码中调用while(1)死循环), 在进程执行的过程中会自动将进程分配到2个CPU核上进行处理。可以通过查看CPU占用率和时间占用情况来验证。分配到两个CPU核后,CPU占用率会上升到200%,用time计算程序的占用时间,真实时间是系统时间的一半,因为系统时间是单独统计每个CPU核上占用的时间,2个CPU核上会统计2次,显示的结果就是系统时间是真实时间的2倍。
10. 实时系统
10.1 REAL TIME实时系统的含义
实时不是越快越好,是指可预期性。比如发射导弹时,必须保证在截止期限内发射出去,否则后果可能是灾难性的。硬实时强调在恶劣的情况下,从唤醒到任务真正执行之间的时间是可预期的,可以保证在预期的时间内执行到。 Linux设计不保证是可预期的,因为 Linux在中断,软中断,及spinlock的区间时是不可抢占的,这些区间内的执行时间是不可预期的。linux不是硬实时的系统,是软实时。
10.2 抢占:LINUX无法硬实时的原因
要实现实时,最重要的就是要实现任何时刻都可以在预定期限内实现进程抢占,来保证进程可以在预期内执行。Linux无法硬实时的原因就是Linux的CPU在的4类区间有3类是无法进行抢占的,如果CPU进入了这3类区间, Linux是无法保证在这3类区间内的运行时间的。所以无法达到硬实时的要求。 Linux抢占的时机很多,我们主要记住不可抢占的时机:
1、中断,
2、软中断,
3、spinlock 区间的进程。
如果在这三类任务时间内唤起了高优先级的任务,任务是不能抢占的,只有当CPU脱离了这三类区间后,高优先级的任务才可以进行抢占。如表 9-1 所示,如果在一二三类区间唤醒了高优先级的进程,当前无法进行抢占,只有当CPU退回到第4类普通进程区间时才开始抢占。
10.3 LINUX实时补丁的用法
linux 的RT版本,https://wiki.linuxfoundation.org/realtime/start 这个项目是实现Linux的实时版本。主要原理是将 Linux的中断线程化,即把一二三类区间都转换成四类区间。相应的linux源码RT版本只针对特定的几个版本,使用方法是将代码中的RT补丁merge到对应的源码中,linux的RT可以做到100us量级的实时性能,(vxworks是几个us),相应的吞吐性能也会下降。
安装linux的调度器的抢占模型选项
1. server 版本(不抢占)
2. desktop 版本(kernel 内不抢占)
3. no-latency desktop (kernel 内可抢占,手机桌面一般用此模型)
4. completely preemption (kernel 内一二三类中断软中断都可抢占)
安装 RT 补丁后还是会有很多的代码坑需要处理,比如内存管理方面,Linux的内存分配是LAZY式,当用到时才实际分配,安装实时补丁后有可能出现写内存时内存实际还未分配的情形。
今天的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
上一篇
已是最后文章
下一篇
已是最新文章