0%

出发点

最近,在构思一套内部环境服务的监控系统,起码的功能是能识别到业务进程异常(进程级别的),退出后自动拉起。
找到了supervisor,了解基本功能后,觉得它在分布式方面还存在不足之处,所有配置分散在单机上。鉴于它的技术符合目前需要练习python技术栈。所以,准备对其源代码进行解构、分析和学习。

概览

源码研究和学习,起码要对该工具如何使用有个基本的掌握。

Supervisor是用Python开发的一套通用的进程管理程序,能将一个普通的命令行进程变为后台daemon,并监控进程状态,异常退出时能自动重启。它是通过fork/exec的方式把这些被管理的进程当作supervisor的子进程来启动,这样只要在supervisor的配置文件中,把要管理的进程的可执行文件的路径写进去即可。也实现当子进程挂掉的时候,父进程可以准确获取子进程挂掉的信息的,可以选择是否自己启动和报警。supervisor还提供了一个功能,可以为supervisord或者每个子进程,设置一个非root的user,这个user就可以管理它对应的进程。

medusa

它依赖第三方的medusa来处理网络请求。medusa的源码也值得学习和研究。

supervisord

类似于docker所在服务器运行的守护进程,负责响应客户端的命令行、受监控进程的监控、重启、受控子进程的标准输入输出的日志、生成和处理与子流程生命周期中相对应的“事件”。配置文件一般为/etc/supervisord.conf。

supervisorctl

控制台,和supervisord交互。通过控制台,用户可以连接到不同的supervisord进程( 通过UNIX domain socket or an internet (TCP) socket),查看supervisord的控制进程的状态。读取/etc/supervisord.conf下的[supervisorctl]内容作为配置。

Web Server

supervisord的浏览器控制台。仅支持单机模式,就是说,N台服务器安装了supervisor的话,就有N个webServer,它们彼此之间并没有关联,所以也没有一个集中化的界面统一管理。

有个php的解决方案:https://blog.csdn.net/geerniya/article/details/80107761

XML-RPC Interface

The same HTTP server which serves the web UI serves up an XML-RPC interface that can be used to interrogate and control supervisor and the programs it runs.

不足

优点:
- 可以将非后台运行程序后台运行
- 自动监控,重启进程

缺点:
- 不能管理后台运行程序
- 对多进程服务,不能使用kill关闭

源码分析

源码分析步骤:

1、clone代码到本地;

2、本地做supervisord的配置

3、参考别写的源码文章,找到程序入口,阅读代码

4、debug若干次,追中代码运行逻辑

执行逻辑和核心

supervisord作为进程入口,启动后先将自己的进程daemon化。接着开始利用opitons.py中的代码,对配置进行初始化,读取关键配置,我目前只研究了启动监控进程部分。读取完配置后,去检查运行环境、进程状态等,没有启动则启动受控进程,受控进程和supervisor的进程通过pipes通信,子进程状态上报。其中的核心方法只有一句:

1
2
3
# options.py中的方法
def execve(self, filename, argv, env):
return os.execve(filename, argv, env)

其他代码都服务于它,supervisor的进程控制模块的代码基本就围绕这如何构建filename,argv,env这三大参数展开。如何展开的我就不细说了,debug运行跟,基本都暴露给出来了。要提醒以下的是,执行os.execve这个方法,是supervisor的进程先fork了子进程后再跑的os.execve。

supervisor自身的数据模型

当然这个主要内容都在supervisor/supervisor/supervisord.py里了。

我试着做个简单解读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Supervisor:
# 下面4个是进程自身的状态控制信息
# 处理发送了停止请求的参数
stopping = False # set after we detect that we are handling a stop request
# 暂时不清楚这个参数具体干什么
lastshutdownreport = 0 # throttle for delayed process error reports at stop
# {进程组名称:进程组}的map信息
process_groups = None # map of process group name to process group object
# 按优先级排序了的被shutdown了的进程组信息的list
stop_groups = None # list used for priority ordered shutdown

def __init__(self, options):
self.options = options #配置信息
self.process_groups = {} #hold住的进程组
self.ticks = {} #消息机制

这里需要注意,supervisor对受控进程进行管理,采用的进程组的概念,而不是单进程。

supervisord.py里有个逻辑很漂亮,那就是把自己daemon化了。具体代码怎么流转的自己看代码了,只写我觉得对我有价值的内容。

实现daemon进程的套路

supervisor启动时,也是被自己的父进程fork出来的。因此,它遵循了daemon化的一般原则,详见下面方法的说明。

1
2
3
# supervisor的run方法中有一句,进程状态还不是daemon并且是第一加载配置就daemon化
if (not self.options.nodaemon) and self.options.first:
self.options.daemonize()

调用os.setsid,让进程摆脱父进程的session(会话)而自立门户,成为新的进程组的leader。而有一种情况是自身本来就是会话的leader的化就没有办法再自立门户了,所以标准动作是先fork自己,退出自己,再把fork出来的进程。options中的daemon方法,满满的套路。代码作者还给出了为什么要把标准0.1.2设置为null的原因。点击跳转自己读,如果用我大白话说的话,意思很简单,linux下一切。老实说,我觉得damonize这个方法应该放到进程的相关模块中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
def daemonize(self):
# To disassociate ourselves from our parent's session group we use
# os.setsid. It means "set session id", which has the effect of
# disassociating a process from is current session and process group
# and setting itself up as a new session leader.
#
# Unfortunately we cannot call setsid if we're already a session group
# leader, so we use "fork" to make a copy of ourselves that is
# guaranteed to not be a session group leader.
#
# We also change directories, set stderr and stdout to null, and
# change our umask.
#
# This explanation was (gratefully) garnered from
# http://www.hawklord.uklinux.net/system/daemons/d3.htm

pid = os.fork()
if pid != 0:
# Parent
self.logger.blather("supervisord forked; parent exiting")
os._exit(0)
# Child
self.logger.info("daemonizing the supervisord process")
if self.directory:
try:
os.chdir(self.directory)
except OSError, err:
self.logger.critical("can't chdir into %r: %s"
% (self.directory, err))
else:
self.logger.info("set current directory: %r"
% self.directory)
os.close(0)
self.stdin = sys.stdin = sys.__stdin__ = open("/dev/null")
os.close(1)
self.stdout = sys.stdout = sys.__stdout__ = open("/dev/null", "w")
os.close(2)
self.stderr = sys.stderr = sys.__stderr__ = open("/dev/null", "w")
os.setsid()
os.umask(self.umask)
# XXX Stevens, in his Advanced Unix book, section 13.3 (page
# 417) recommends calling umask(0) and closing unused
# file descriptors. In his Network Programming book, he
# additionally recommends ignoring SIGHUP and forking again
# after the setsid() call, for obscure SVR4 reasons.

所以,python的daemon进程的一般套路可以总结为:

  • fork子进程,fork有两个返回值,通过返回值执行逻辑
  • 返回值为不为0,就是父进程,退出父进程
  • 子进程执行os.setid(),让自己成为进程组和会话的leader。
  • 安全的关闭父进程的三标(输入、输出、错误)
  • 重新指定子进程的标三标,使用os.dup或os.dup2函数,supervisor直接赋值。
  • 重新设置子进程的掩码umask,就是chmod给权限的时候那组组合数字,出于安全性的考虑,往往不希望这些文件被别的用户查看。这时,可以使用umask函数修改文件权限,创建掩码的取值,以满足守护进程的要求。
  • 重新指定子进程的工作目录。当进程没有结束时,其工作目录是不能被卸载的。为了防止这种问题发生,守护进程一般会将其工作目录更改到根目录下(/目录)。更改工作目录使用的函数是chdir。

需要注意:

在 Unix 上通过 spawn 和 forkserver 方式启动多进程会同时启动一个 资源追踪 进程,负责追踪当前程序的进程产生的、并且不再被使用的命名系统资源(如命名信号量以及 SharedMemory 对象)。当所有进程退出后,资源追踪会负责释放这些仍被追踪的的对象。通常情况下是不会有这种对象的,但是假如一个子进程被某个信号杀死,就可能存在这一类资源的“泄露”情况。(泄露的信号量以及共享内存不会被释放,直到下一次系统重启,对于这两类资源来说,这是一个比较大的问题,因为操作系统允许的命名信号量的数量是有限的,而共享内存也会占据主内存的一片空间)。要选择一个启动方法,你应该在主模块的 if name == ‘main‘ 子句中调用 set_start_method()

supervisord模块就解读完毕。

supervisor的配置模型

都在options.py中了,基类Options。

从配置中,我们可以窥探出supervisor整个系统由哪些元素构成。这里做一个列举,它们的基类是Options:

  • ServerOptions
  • ClientOptions
  • ProcessConfig
  • ProcessGroupConfig
  • EventListenerConfig
  • EventListenerPoolConfig
  • FastCGIGroupConfig
  • FastCGIProcessConfig

这里要吐槽下,我现在对python的模块原则还不了解,所以我对supervisor作者在options.py写了些异常类表示不理解,按linux一个命令只做一件事这种原则,不是应该有个异常类的模块才对?

配置从哪里来,我大致看了:命令行、默认配置路径找。

如果我要写和配置有关的代码,我会再来细究,这里就跳过了。

supervisor受控进程模型

process.py中,Subprocess是最重要的一个类。基本几大类:状态、日志、事件、管道、异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Subprocess:

"""A class to manage a subprocess."""

# Initial state; overridden by instance variables

pid = 0 # Subprocess pid; 0 when not running
config = None # ProcessConfig instance
state = None # process state code
listener_state = None # listener state code (if we're an event listener)
event = None # event currently being processed (if we're an event listener)
laststart = 0 # Last time the subprocess was started; 0 if never
laststop = 0 # Last time the subprocess was stopped; 0 if never
delay = 0 # If nonzero, delay starting or killing until this time
administrative_stop = 0 # true if the process has been stopped by an admin
system_stop = 0 # true if the process has been stopped by the system
killing = 0 # flag determining whether we are trying to kill this proc
backoff = 0 # backoff counter (to startretries)
dispatchers = None # asnycore output dispatchers (keyed by fd)
pipes = None # map of channel name to file descriptor #
exitstatus = None # status attached to dead process by finsh()
spawnerr = None # error message attached by spawn() if any
group = None # ProcessGroup instance if process is in the group

def __init__(self, config):
"""Constructor.

Argument is a ProcessConfig instance.
"""
self.config = config
self.dispatchers = {}
self.pipes = {}
self.state = ProcessStates.STOPPED

进程状态模型

ProcessStates类中,该类在state.py中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ProcessStates:
STOPPED = 0
STARTING = 10
RUNNING = 20
BACKOFF = 30
STOPPING = 40
EXITED = 100
FATAL = 200
UNKNOWN = 1000

STOPPED_STATES = (ProcessStates.STOPPED,
ProcessStates.EXITED,
ProcessStates.FATAL,
ProcessStates.UNKNOWN)

RUNNING_STATES = (ProcessStates.RUNNING,
ProcessStates.BACKOFF,
ProcessStates.STARTING)

那接下来,在开源代码中学习代码及所属领域的知识是研究的出发点。

我的应用场景

supervisor并不适合我的应用场景。

首先,它是作为父进程来fork受监控的进程的,也就是子进程的生命周期它能无微不至的照顾到,虽然子进程的所有信息它都能掌握,但在我的场景下,有发版和更新的刚性需求。如果用supervisor来监控和拉起,就导致发版更新后也不得不用supervisor的自动重启来完成。这种强耦合的方式并不是很优美,会导致大量改造工作。

其次,部署信息都是散落在服务器上的,对于超过10个应用,甚至更多,分散化的部署维护起来也不方便。所以有个潜在的需求,统一配置受控进程。supervisor的http服务是单机版的,对于分布式部署来说,缺少一个统一界面维护。

所以,我可能会supervisor做如下的改造:

  • 获取监控进程的信息源来自db或者redis之类持久化的存储服务
  • fork子进程,但是并不监控子进程的状态,只判定它是否存在。不存在超过一个阈值自动拉起。
  • 有一个受控进程的展现页面,所有被supervisor分散监控进程状态都列在上面,可以触发重启、启动操作

源码解读第一波就到这了。

参考

supervisor官方文档

supervisoer源码分析
进程组、session、前台任务、后台任务、守护进程

操作系统核心原理-3.进程原理(上):进程概要

进程实现原理

fork()子进程与父进程的关系(继承了什么)

操作系统中信号工作原理

supervisor的daenon进程原理

linux的文件描述符

这篇文章是研习supervisor源代码的副产品,主要理解supervisor的驱动原理和操作系统对进程管理的一些基本知识。

理解什么是进程

百度百科定义如下:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

进程是操作系统对资源的一个抽象。
操作系统是计算机的管家,掌控着计算机内部的一切资源的分配、调度、回收。
而进程可以理解为计算机资源调度的一个基本单元。用播放电影来举个形象的例子,进程就是电影一针一针的画面,而程序就是播放这部电影的一个统称,电影没开始播放前,得准备放映机(cpu),电影胶片(内存)。放音机齿轮转动就好比cpu的时钟脉冲,每秒扫过N个胶片(进程被调度),图像被光打到幕布上(计算机的输出设备)。

换一个角度看操作系统的进程,linux系统开机启动到系统启动完毕,从进程的角度看就是一生二、二生三、三生万物的感觉。我们面对的就是进程的一课大树,它的源头就是进程号为1(名叫systemd)的进程,systemd是Linux下的一种负责init的进程,那字母d就是daemon的标记,又比如apache的http服务,它的进程也有个字母d,httpd。可以运行pstree命令来体验一下这种子子孙孙无穷匮也。ps和systemctl命令也有类似效果。

1
2
3
pstree
ps axjf
systemctl status

进程运行环境

在Linux中,用户程序装入系统形成一个进程的实质是操作系统为用户程序提供一个完整的运行环境。进程的运行环境是由它的程序代码和程序运行所需要的数据结构以及硬件环境组成的。进程的运行环境主要包括:

  • 1.进程空间(内存)中的代码和数据、各种数据结构、进程堆栈和共享内存区等。

  • 2.环境变量:提供进程运行所需的环境信息。

  • 3.系统数据:进程空间中的对进程进行管理和控制所需的信息,包括进程任务结构体以及内核堆栈等。

  • 4.进程访问设备或者文件时的权限。

  • 5.各种硬件寄存器。

  • 6.地址转换信息。

    Linux下的进程模型

    从PCB中了解进程模型

    一个进程在操作系统中执行,有两种状态:内核态和用户态。如果当前运行的是用户程序(用户代码),那么对应进程就处于用户),如果出现系统调用或者发生中断,那么对应进程就处于内核模式(核心态)。

为了描述控制进程的运行,系统中存放进程的管理和控制信息的数据结构称为进程控制块 PCB(Process Control Block),它是进程实体的一部分,是操作系统中最重要的记录性数据结构。它是进程管理和控制的最重要的数据结构,每一个进程均有一个 PCB,在创建进程时,建立 PCB,伴随进程运行的全过程,直到进程撤消而撤消。PCB 记录了操作系统所需的,用于描述进程的当前情况以及控制进程运行的全部信息(如打开的文件、挂起的信号、进程状态、地址空间等等)。在linux中这个PCB是一个叫做task_struct的结构体。

task_struct 在linux中,每一个进程都有一个进程描述符,这个”进程描述符”是一个结构体名字叫做task_struct,在task_struct里面保存了许多关于进程控制的信息。

task_struct是Linux内核的一种数据结构,它会被装载到RAM里并包含进程的信息。

每个进程都把它的信息放在task_struct这个数据结构里面,而task_struct包含以下内容:

进程状态(State)

表示进程的状态, 在进程执行的时候,它会有一个状态,这个状态对于进程来说是很重要的一个属性。包括:可运行、可中断的等待状态、不可中断的等待状态、将死、暂停、换入/换出。

进程标识符

每个进程都有进程标识符、用户标识符、组标识符,进程标识符对于每一个进程来说都是唯一的。内核通过进程标识符来对不同的进程进行识别,一般来说,行创建的进程都是在前一个进程的基础上PID加上1作为本进程的PID。为了linux平台兼容性,PID一般最大为32767。

进程内核栈。

stack用来维护分配给进程的内核栈,内核栈的意义在于,进程task_struct所占的内存是由内核动态分配的,确切的说就是内核根本不给task_struct分配内存,只给内核栈分配8KB内存,并且一部分会提供给task_struct使用。

标记。

flag,用来反映一个进程的状态信息,但不是运行状态,用于内核识别进程当前的状态,flags的取值可以为

可使用的标记 功能
PF_FORKNOEXEC 进程刚创建,但还没执行。
PF_SUPERPRIV 超级用户特权。
PF_DUMPCORE 关于核心。
PF_SIGNALED 进程被信号(signal)杀出。
PF_EXITING 进程开始关闭。

表示进程亲属关系的成员

考虑到进程的派生,所以进程之间会存在父进程和子进程这样的关系,当然,对于同一个父进程派生出来的进程,他们的关系当然是兄弟进程了。

成员 功能
real_parent 指向父进程的指针,如果父进程不存在了,则指向PID为1的进程
parent 指向父进程的,值与real——parent相同,需要向它的父进程发送信号
children 表示链表的头部,链表中的所有元素都是它的子进程
sibling 用于当前进程插入兄弟链表当中
group_leader 指向进程组的领头进程

ptrace系统调用

ptrace是一种提供父进程控制子进程运行,并且可以检查和改变它的核心image。

进程调度。

调度进程是一个复杂过程,所以它有比较复杂的结构。

  • 调度优先级结构
    利用这部分信息决定系统当中的那个进程最应该运行,并且结合进程的状态信息保证系统运作高效。
成员 功能
static_prio 保存静态优先级,可以通过nice系统进行修改
rt_priority 保存实时优先级
normal_prio 保存静态优先级和调度策略
prio 保存动态优先级
  • 调度控制结构
成员 功能
policy 调度策略
sched_class 调度类
se 普通进程的一个调用的实体,每一个进程都有其中之一的实体
rt 实时进程的调用实体,每个进程都有其中之一的实体
cpus_allowed 用于控制进程可以在处理器的哪里运行
  • 具体的调度策略结构

    种类 功能
    SCHED_NORMAL 用于普通进程
    SCHED_BATCH 普通进程策略的分化版本,采用分时策略
    SCHED_IDLE 优先级最低,系统空闲时才跑这类进程
    SCHED_FIFO 先入先出的调度算法
    SCHED_RR 实时调度算法,采用时间片,相同优先级的任务当用完时间片就会放到队列的尾部,保证公平性,同时,高优先级的任务抢占低优先级的任务。
    SCHED_DEADLINE 新支持的实时调度策略,正对突发性计算
  • 调度类结构

    调度类 功能
    idle_sched_class 每一个cpu的第一个pid=0的线程,是一个静态的线程
    stop_sched_class 优先级最高的线程,会中断所有其他的线程,而且不会被其他任务打断
    rt_sched_slass 作用在实时线程
    fair_sched_class 作用的一般线程

它们的优先级顺序为Stop>rt>fair>idle

进程的地址空间

包括进程所拥有的用户空间的内存描述符,执行运行使用的内存描述符。

成员 功能
mm 进程所拥有的用户空间的内存描述符。内核线程切记是没有地址空间的。
active_mm 指向进程运行时使用的内存描述符,对于普通的进程来说,mm和active_mm是一样的,但是内核线程是没有进程地址空间的,所以内核线程的mm是空的,所以需要初始化内核线程的active_mm

判定标志

用于判断进程的标志信息。

成员 功能
exit_state 进程终止的状态
exit_code 设置进程的终止代号
exit_signal 设置为-1的时候表示是某个线程组当中的一员,只有当线程组的最后一个成员终止时,才会产生型号给父进程
pdeath_signal 用来判断父进程终止时的信号

时间和定时器信息(Times and Timers)

关于时间,一个进程从创建到终止叫做该进程的生存期,进程在其生存期内使用CPU时间,内核都需要进行记录,进程耗费的时间分为两部分,一部分是用户模式下耗费的时间,一部分是在系统模式下耗费的时间。

成员 属性
utime/stime 用于记录进程在用户状态/内核态下所经过的定时器
prev_utime/prev_stime 记录当前的运行时间
utimescaled/stimescaled 分别记录进程在用户态和内核态的运行的时间
gtime 记录虚拟机的运行时间
nvcsw/nicsw 是自愿/非自愿上下文切换计数
start_time/real_start_time 进程创建时间,real还包括了进程睡眠时间

进程的定时器,一共是三种定时器。

定时器类型 解释 更新时刻
ITIMER_REAL 实时定时器 实时更新,不在乎进程是否运行
ITIMER_VIRTUAL 虚拟定时器 只在进程运行用户态时更新
ITIMER_PROF 概况定时器 进程运行于用户态和系统态进行更新
cputime_expires 用来统计进程或进程组被跟踪的处理器时间 三个成员对应的是下面的cpu_times[3]的三个链表

信号处理。

成员 功能
signal 指向进程信号描述符
sighand 指向进程信号处理程序描述符
blocked 表示被阻塞信号的掩码
pending 存放私有挂起信号的数据结构
sas_ss_sp 信号处理程序备用堆栈的地址

文件系统信息

进程可以用来打开和关闭文件,文件属于系统资源,task_struct有两个来描述文件资源,他们会描述两个VFS索引节点,两个节点分别是root和pwd,分别指向根目录和当前的工作目录。

成员 功能
struct fs_struct *fs 进程可执行镜像所在的文件系统
struct files_struct *files 进程当前打开的文件

进程状态模型

这个网上说的很多,
直接盗图了,基于task_struct
iamge
基于开发者视角的盗图
iamge

进程的内存模型

image
进程地址空间分为内核空间和用户空间  

因为每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。

  • A.正文段。这是由cpu执行的机器指令部分。通常,正文段是可共享的,所以即使是经常执行的程序(如文本编辑程序、C编译程序、shell等)在存储器中也只需要有一个副本,另外,正文段常常是只读的,以防止程序由于意外事故而修改器自身的指令。

  • B.初始化数据段。通常将此段称为数据段,它包含了程序中需赋初值的变量。例如,C程序中任何函数之外的说明:

  • C.非初始化数据段。通常将此段称为bss段,这一名称来源于早期汇编程序的一个操作,意思是”block started by symbol”,在程序开始执行之前,内核将此段初始化为0。

  • D.栈。自动变量以及每次函数调用时所需保存的信息都存放在此段中。每次函数调用时,其返回地址、以及调用者的环境信息(例如某些机器寄存器)都存放在栈中。然后,新被调用的函数在栈上为其自动和临时变量分配存储空间。通过以这种方式使用栈,C函数可以递归调用。

  • E.堆。通常在堆中进行动态存储分配。由于历史上形成的惯例,堆位于非初始化数据段顶和栈底之间。

进程之间的关系模型

session(也称为会话)

为什么会有会话这个概念?答案是显而易见的,因为人机需要交互,而交互的识别方式就是用session。
一个或多个进程组可以构成一个会话 (session)。

一个会话中有一个领导进程(session leader)。会话领导进程的PID是会话的SID(session ID)。会话中的每个进程组称为一个工作(job)。会话可以有一个进程组成为会话的前台工作(foreground),而其他的进程组是后台工作(background)。每个会话可以连接一个控制终端(也可以不连接)。

会话的意义在于将多个工作囊括在一个终端,并取其中的一个工作作为前台,来直接接收该终端的输入输出以及终端信号。 其他工作在后台运行。当我们打开多个终端窗口时,实际上就创建了多个终端会话。每个会话都会有自己的前台工作和后台工作。工作组和会话机制在Linux的许多地方应用。
举例:

当用xshell连接到主机时,即创建了一个session。shell即是session的leader进程,随后shell里面运行的进程都将属于这个session,当shell退出后,该会话中的进程将退出。

shell里面启动一个进程后,一般都会将该进程放到一个单独的进程组,然后该进程fork的所有进程都会属于该进程组,比如多进程的程序,它的所有进程都会属于同一个进程组,当在shell里面按下CTRL+C时,该程序的所有进程都会收到SIGINT而退出。

父进程、子进程

每个进程都有一个创建自己的进程,每个进程的父进程号属性反应了系统上所有进程间的树状关系。每个进程的父进程又有自己的父进程,以此类推,回溯到1号进程–init进程,即所有进程的始祖。

如果子进程的父进程终止,则子进程也会被操作系统回收。但是被结束的父进程不是会话组的组长,则子进程就会变成“孤儿”,init进程随即将收养该进程。

进程生进程有fork操作,子进程完全复制父进程的栈空间,也复制了页表,但没有复制物理页面,所以这时虚拟地址相同,物理地址也相同,但是会把父子共享的页面标记为“只读”(类似mmap的private的方式),如果父子进程一直对这个页面是同一个页面,知道其中任何一个进程要对共享的页面“写操作”,这时内核会复制一个物理页面给这个进程使用,同时修改页表。而把原来的只读页面标记为“可写”,留给另外一个进程使用,即进程的写时复制的机制。所以,粗略的可以认为子进程继承了父进程的栈空间、页表。

进程组

所有进程都是属于一个进程组的,而进程组又属于一个会话。一个会话中有一个领导进程(session leader)。会话领导进程的PID是会话的SID(session ID)。

进程组是一组进程的集合,每个进程都属于一个进程组,每个进程组有一个进程组leader进程,进程组的ID(PGID)等于leader进程的ID。对大部分进程来说,它自己就是进程组的leader,并且进程组里面就只有它自己一个进程。
一个或多个进程可以构成一个进程集合,叫做进程组。通常,它们与同一个作业相关联,可以接受来自同一终端的各种信号。每个进程除了有一个进程ID之外,还属于一个进程组。每个进程组都有一个唯一的进程组ID。且每个进程组都有一个组长进程,组长进程的ID就是进程组的ID。通常,一个进程组的组长进程就是创建的第一个进程。或者可以这么说,创建一个进程,就创建了一个进程组,只是现在这一个进程组里边现在只有一个进程(组长进程)。组长进程还可以创建改组中的其他进程。
一个进程组,只要有任意一个进程存在,该组就是存在的,与组长是否存在无关。

进程组的组长是不可以创建会话的,否者返回一个错误;为了保证该规则,通常是通过进程组中的一个进程fork()一个子进程,exit()父进程,此时子进程继承了父进程所在组的pgid,确保了不是进程组长。

可以将一个进程从一个进程组中分离出来,创建一个会话;自然而然就是会话的首领,该进程是会话的唯一一个进程,同时也是进程组组长,身兼多职;

可以通过将信号发送给一个进程组,使进程组中的所有进程都收到该信号。

进程间的通信

信号,操作系统与进程通信的一种机制。
管道,也是一种常见的通信机制。

进程与文件的关系

一句话,读写执行。

但是,要知道的不止这些。 Linux一切皆文件的思想,所有不同种类的类型都被抽象成文件,如:普通文件、目录、字符设备、块设备、套接字等。这就决定了进程绝对跟文件的关系不同一般。

当一个文件被进程打开,就会创建一个文件描述符。这时候,文件的路径就成为了寻址系统,文件描述符成为了字节流的接口。

相对于普通文件这类真实存在于文件系统中的文件,tcp socket、unix domain socket等这些存在于内存中的特殊文件在被进程打开的时候,也会创建文件描述符。所以”一切皆文件”更准确的描述应该是”一切皆文件描述符”。文件描述符(FD:file descriptors)也可以说是文件句柄,当某个进程打开文件时,内核返回相应的文件描述符,程序为了处理该文件必须引用此描述符。文件描述符是一个正整数,用以标明每一个被进程所打开的文件和socket。最前面的三个文件描述符(0,1,2)分别与标准输入(stdin),标准输出(stdout)和标准错误(stderr)对应,后面打开的文件依此类推对应3、4…… 。

linux系统对每个用户、进程、或整个系统的可打开文件描述符数量都有一个限制,一般默认为1024。当我们在系统或应用的日志中碰到“too many open files”错误记录时,这个其实不是说打开的文件过多,而是打开的文件描述符数量已达到了限制,这时就需要增加文件描述符的数量限制了。

内核中task_struct的代码注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
struct task_struct 
{
/*
1. state: 进程执行时,它会根据具体情况改变状态。进程状态是进程调度和对换的依据。Linux中的进程主要有如下状态:
1) TASK_RUNNING: 可运行
处于这种状态的进程,只有两种状态:
1.1) 正在运行
正在运行的进程就是当前进程(由current所指向的进程)
1.2) 正准备运行
准备运行的进程只要得到CPU就可以立即投入运行,CPU是这些进程唯一等待的系统资源,系统中有一个运行队列(run_queue),用来容纳所有处于可运行状态的进程,调度程序执行时,从中选择一个进程投入运行

2) TASK_INTERRUPTIBLE: 可中断的等待状态,是针对等待某事件或其他资源的睡眠进程设置的,在内核发送信号给该进程表明事件已经发生时,进程状态变为TASK_RUNNING,它只要调度器选中该进程即可恢复执行

3) TASK_UNINTERRUPTIBLE: 不可中断的等待状态
处于该状态的进程正在等待某个事件(event)或某个资源,它肯定位于系统中的某个等待队列(wait_queue)中,处于不可中断等待态的进程是因为硬件环境不能满足而等待,例如等待特定的系统资源,它任何情况下都不能被打断,只能用特定的方式来唤醒它,例如唤醒函数wake_up()等
     它们不能由外部信号唤醒,只能由内核亲自唤醒

4) TASK_ZOMBIE: 僵死
进程虽然已经终止,但由于某种原因,父进程还没有执行wait()系统调用,终止进程的信息也还没有回收。顾名思义,处于该状态的进程就是死进程,这种进程实际上是系统中的垃圾,必须进行相应处理以释放其占用的资源。

5) TASK_STOPPED: 暂停
此时的进程暂时停止运行来接受某种特殊处理。通常当进程接收到SIGSTOP、SIGTSTP、SIGTTIN或 SIGTTOU信号后就处于这种状态。例如,正接受调试的进程就处于这种状态
    
     6) TASK_TRACED
     从本质上来说,这属于TASK_STOPPED状态,用于从停止的进程中,将当前被调试的进程与常规的进程区分开来
      
     7) TASK_DEAD
     父进程wait系统调用发出后,当子进程退出时,父进程负责回收子进程的全部资源,子进程进入TASK_DEAD状态

8) TASK_SWAPPING: 换入/换出
*/
volatile long state;

/*
2. stack
进程内核栈,进程通过alloc_thread_info函数分配它的内核栈,通过free_thread_info函数释放所分配的内核栈
*/
void *stack;

/*
3. usage
进程描述符使用计数,被置为2时,表示进程描述符正在被使用而且其相应的进程处于活动状态
*/
atomic_t usage;

/*
4. flags
flags是进程当前的状态标志(注意和运行状态区分)
1) #define PF_ALIGNWARN 0x00000001: 显示内存地址未对齐警告
2) #define PF_PTRACED 0x00000010: 标识是否是否调用了ptrace
3) #define PF_TRACESYS 0x00000020: 跟踪系统调用
4) #define PF_FORKNOEXEC 0x00000040: 已经完成fork,但还没有调用exec
5) #define PF_SUPERPRIV 0x00000100: 使用超级用户(root)权限
6) #define PF_DUMPCORE 0x00000200: dumped core
7) #define PF_SIGNALED 0x00000400: 此进程由于其他进程发送相关信号而被杀死
8) #define PF_STARTING 0x00000002: 当前进程正在被创建
9) #define PF_EXITING 0x00000004: 当前进程正在关闭
10) #define PF_USEDFPU 0x00100000: Process used the FPU this quantum(SMP only)
#define PF_DTRACE 0x00200000: delayed trace (used on m68k)
*/
unsigned int flags;

/*
5. ptrace
ptrace系统调用,成员ptrace被设置为0时表示不需要被跟踪,它的可能取值如下:
linux-2.6.38.8/include/linux/ptrace.h
1) #define PT_PTRACED 0x00000001
2) #define PT_DTRACE 0x00000002: delayed trace (used on m68k, i386)
3) #define PT_TRACESYSGOOD 0x00000004
4) #define PT_PTRACE_CAP 0x00000008: ptracer can follow suid-exec
5) #define PT_TRACE_FORK 0x00000010
6) #define PT_TRACE_VFORK 0x00000020
7) #define PT_TRACE_CLONE 0x00000040
8) #define PT_TRACE_EXEC 0x00000080
9) #define PT_TRACE_VFORK_DONE 0x00000100
10) #define PT_TRACE_EXIT 0x00000200
*/
unsigned int ptrace;
unsigned long ptrace_message;
siginfo_t *last_siginfo;

/*
6. lock_depth
用于表示获取大内核锁的次数,如果进程未获得过锁,则置为-1
*/
int lock_depth;

/*
7. oncpu
在SMP上帮助实现无加锁的进程切换(unlocked context switches)
*/
#ifdef CONFIG_SMP
#ifdef __ARCH_WANT_UNLOCKED_CTXSW
int oncpu;
#endif
#endif

/*
8. 进程调度
1) prio: 调度器考虑的优先级保存在prio,由于在某些情况下内核需要暂时提高进程的优先级,因此需要第三个成员来表示(除了static_prio、normal_prio之外),由于这些改变不是持久的,因此静态(static_prio)和普通(normal_prio)优先级不受影响
2) static_prio: 用于保存进程的"静态优先级",静态优先级是进程"启动"时分配的优先级,它可以用nice、sched_setscheduler系统调用修改,否则在进程运行期间会一直保持恒定
3) normal_prio: 表示基于进程的"静态优先级"和"调度策略"计算出的优先级,因此,即使普通进程和实时进程具有相同的静态优先级(static_prio),其普通优先级(normal_prio)也是不同的。进程分支时(fork),新创建的子进程会集成普通优先级
*/
int prio, static_prio, normal_prio;
/*
4) rt_priority: 表示实时进程的优先级,需要明白的是,"实时进程优先级"和"普通进程优先级"有两个独立的范畴,实时进程即使是最低优先级也高于普通进程,最低的实时优先级为0,最高的优先级为99,值越大,表明优先级越高
*/
unsigned int rt_priority;
/*
5) sched_class: 该进程所属的调度类,目前内核中有实现以下四种:
5.1) static const struct sched_class fair_sched_class;
5.2) static const struct sched_class rt_sched_class;
5.3) static const struct sched_class idle_sched_class;
5.4) static const struct sched_class stop_sched_class;
*/
const struct sched_class *sched_class;
/*
6) se: 用于普通进程的调用实体
  调度器不限于调度进程,还可以处理更大的实体,这可以实现"组调度",可用的CPU时间可以首先在一般的进程组(例如所有进程可以按所有者分组)之间分配,接下来分配的时间在组内再次分配
  这种一般性要求调度器不直接操作进程,而是处理"可调度实体",一个实体有sched_entity的一个实例标识
  在最简单的情况下,调度在各个进程上执行,由于调度器设计为处理可调度的实体,在调度器看来各个进程也必须也像这样的实体,因此se在task_struct中内嵌了一个sched_entity实例,调度器可据此操作各个task_struct
*/
struct sched_entity se;
/*
7) rt: 用于实时进程的调用实体
*/
struct sched_rt_entity rt;

#ifdef CONFIG_PREEMPT_NOTIFIERS
/*
9. preempt_notifier
preempt_notifiers结构体链表
*/
struct hlist_head preempt_notifiers;
#endif

/*
10. fpu_counter
FPU使用计数
*/
unsigned char fpu_counter;

#ifdef CONFIG_BLK_DEV_IO_TRACE
/*
11. btrace_seq
blktrace是一个针对Linux内核中块设备I/O层的跟踪工具
*/
unsigned int btrace_seq;
#endif

/*
12. policy
policy表示进程的调度策略,目前主要有以下五种:
1) #define SCHED_NORMAL 0: 用于普通进程,它们通过完全公平调度器来处理
2) #define SCHED_FIFO 1: 先来先服务调度,由实时调度类处理
3) #define SCHED_RR 2: 时间片轮转调度,由实时调度类处理
4) #define SCHED_BATCH 3: 用于非交互、CPU使用密集的批处理进程,通过完全公平调度器来处理,调度决策对此类进程给与"冷处理",它们绝不会抢占CFS调度器处理的另一个进程,因此不会干扰交互式进程,如果不打算用nice降低进程的静态优先级,同时又不希望该进程影响系统的交互性,最适合用该调度策略
5) #define SCHED_IDLE 5: 可用于次要的进程,其相对权重总是最小的,也通过完全公平调度器来处理。要注意的是,SCHED_IDLE不负责调度空闲进程,空闲进程由内核提供单独的机制来处理
只有root用户能通过sched_setscheduler()系统调用来改变调度策略
*/
unsigned int policy;

/*
13. cpus_allowed
cpus_allowed是一个位域,在多处理器系统上使用,用于控制进程可以在哪里处理器上运行
*/
cpumask_t cpus_allowed;

/*
14. RCU同步原语
*/
#ifdef CONFIG_TREE_PREEMPT_RCU
int rcu_read_lock_nesting;
char rcu_read_unlock_special;
struct rcu_node *rcu_blocked_node;
struct list_head rcu_node_entry;
#endif /* #ifdef CONFIG_TREE_PREEMPT_RCU */

#if defined(CONFIG_SCHEDSTATS) || defined(CONFIG_TASK_DELAY_ACCT)
/*
15. sched_info
用于调度器统计进程的运行信息
*/
struct sched_info sched_info;
#endif

/*
16. tasks
通过list_head将当前进程的task_struct串联进内核的进程列表中,构建;linux进程链表
*/
struct list_head tasks;

/*
17. pushable_tasks
limit pushing to one attempt
*/
struct plist_node pushable_tasks;

/*
18. 进程地址空间
1) mm: 指向进程所拥有的内存描述符
2) active_mm: active_mm指向进程运行时所使用的内存描述符
对于普通进程而言,这两个指针变量的值相同。但是,内核线程不拥有任何内存描述符,所以它们的mm成员总是为NULL。当内核线程得以运行时,它的active_mm成员被初始化为前一个运行进程的active_mm值
*/
struct mm_struct *mm, *active_mm;

/*
19. exit_state
进程退出状态码
*/
int exit_state;

/*
20. 判断标志
1) exit_code
exit_code用于设置进程的终止代号,这个值要么是_exit()或exit_group()系统调用参数(正常终止),要么是由内核提供的一个错误代号(异常终止)
2) exit_signal
exit_signal被置为-1时表示是某个线程组中的一员。只有当线程组的最后一个成员终止时,才会产生一个信号,以通知线程组的领头进程的父进程
*/
int exit_code, exit_signal;
/*
3) pdeath_signal
pdeath_signal用于判断父进程终止时发送信号
*/
int pdeath_signal;
/*
4) personality用于处理不同的ABI,它的可能取值如下:
enum
{
PER_LINUX = 0x0000,
PER_LINUX_32BIT = 0x0000 | ADDR_LIMIT_32BIT,
PER_LINUX_FDPIC = 0x0000 | FDPIC_FUNCPTRS,
PER_SVR4 = 0x0001 | STICKY_TIMEOUTS | MMAP_PAGE_ZERO,
PER_SVR3 = 0x0002 | STICKY_TIMEOUTS | SHORT_INODE,
PER_SCOSVR3 = 0x0003 | STICKY_TIMEOUTS |
WHOLE_SECONDS | SHORT_INODE,
PER_OSR5 = 0x0003 | STICKY_TIMEOUTS | WHOLE_SECONDS,
PER_WYSEV386 = 0x0004 | STICKY_TIMEOUTS | SHORT_INODE,
PER_ISCR4 = 0x0005 | STICKY_TIMEOUTS,
PER_BSD = 0x0006,
PER_SUNOS = 0x0006 | STICKY_TIMEOUTS,
PER_XENIX = 0x0007 | STICKY_TIMEOUTS | SHORT_INODE,
PER_LINUX32 = 0x0008,
PER_LINUX32_3GB = 0x0008 | ADDR_LIMIT_3GB,
PER_IRIX32 = 0x0009 | STICKY_TIMEOUTS,
PER_IRIXN32 = 0x000a | STICKY_TIMEOUTS,
PER_IRIX64 = 0x000b | STICKY_TIMEOUTS,
PER_RISCOS = 0x000c,
PER_SOLARIS = 0x000d | STICKY_TIMEOUTS,
PER_UW7 = 0x000e | STICKY_TIMEOUTS | MMAP_PAGE_ZERO,
PER_OSF4 = 0x000f,
PER_HPUX = 0x0010,
PER_MASK = 0x00ff,
};
*/
unsigned int personality;
/*
5) did_exec
did_exec用于记录进程代码是否被execve()函数所执行
*/
unsigned did_exec:1;
/*
6) in_execve
in_execve用于通知LSM是否被do_execve()函数所调用
*/
unsigned in_execve:1;
/*
7) in_iowait
in_iowait用于判断是否进行iowait计数
*/
unsigned in_iowait:1;

/*
8) sched_reset_on_fork
sched_reset_on_fork用于判断是否恢复默认的优先级或调度策略
*/
unsigned sched_reset_on_fork:1;

/*
21. 进程标识符(PID)
在CONFIG_BASE_SMALL配置为0的情况下,PID的取值范围是0到32767,即系统中的进程数最大为32768个
#define PID_MAX_DEFAULT (CONFIG_BASE_SMALL ? 0x1000 : 0x8000)
在Linux系统中,一个线程组中的所有线程使用和该线程组的领头线程(该组中的第一个轻量级进程)相同的PID,并被存放在tgid成员中。只有线程组的领头线程的pid成员才会被设置为与tgid相同的值。注意,getpid()系统调用
返回的是当前进程的tgid值而不是pid值。
*/
pid_t pid;
pid_t tgid;

#ifdef CONFIG_CC_STACKPROTECTOR
/*
22. stack_canary
防止内核堆栈溢出,在GCC编译内核时,需要加上-fstack-protector选项
*/
unsigned long stack_canary;
#endif

/*
23. 表示进程亲属关系的成员
1) real_parent: 指向其父进程,如果创建它的父进程不再存在,则指向PID为1的init进程
2) parent: 指向其父进程,当它终止时,必须向它的父进程发送信号。它的值通常与real_parent相同
*/
struct task_struct *real_parent;
struct task_struct *parent;
/*
3) children: 表示链表的头部,链表中的所有元素都是它的子进程(子进程链表)
4) sibling: 用于把当前进程插入到兄弟链表中(连接到父进程的子进程链表(兄弟链表))
5) group_leader: 指向其所在进程组的领头进程
*/
struct list_head children;
struct list_head sibling;
struct task_struct *group_leader;

struct list_head ptraced;
struct list_head ptrace_entry;
struct bts_context *bts;

/*
24. pids
PID散列表和链表
*/
struct pid_link pids[PIDTYPE_MAX];
/*
25. thread_group
线程组中所有进程的链表
*/
struct list_head thread_group;

/*
26. do_fork函数
1) vfork_done
在执行do_fork()时,如果给定特别标志,则vfork_done会指向一个特殊地址
2) set_child_tid、clear_child_tid
如果copy_process函数的clone_flags参数的值被置为CLONE_CHILD_SETTID或CLONE_CHILD_CLEARTID,则会把child_tidptr参数的值分别复制到set_child_tid和clear_child_tid成员。这些标志说明必须改变子
进程用户态地址空间的child_tidptr所指向的变量的值。
*/
struct completion *vfork_done;
int __user *set_child_tid;
int __user *clear_child_tid;

/*
27. 记录进程的I/O计数(时间)
1) utime
用于记录进程在"用户态"下所经过的节拍数(定时器)
2) stime
用于记录进程在"内核态"下所经过的节拍数(定时器)
3) utimescaled
用于记录进程在"用户态"的运行时间,但它们以处理器的频率为刻度
4) stimescaled
用于记录进程在"内核态"的运行时间,但它们以处理器的频率为刻度
*/
cputime_t utime, stime, utimescaled, stimescaled;
/*
5) gtime
以节拍计数的虚拟机运行时间(guest time)
*/
cputime_t gtime;
/*
6) prev_utime、prev_stime是先前的运行时间
*/
cputime_t prev_utime, prev_stime;
/*
7) nvcsw
自愿(voluntary)上下文切换计数
8) nivcsw
非自愿(involuntary)上下文切换计数
*/
unsigned long nvcsw, nivcsw;
/*
9) start_time
进程创建时间
10) real_start_time
进程睡眠时间,还包含了进程睡眠时间,常用于/proc/pid/stat,
*/
struct timespec start_time;
struct timespec real_start_time;
/*
11) cputime_expires
用来统计进程或进程组被跟踪的处理器时间,其中的三个成员对应着cpu_timers[3]的三个链表
*/
struct task_cputime cputime_expires;
struct list_head cpu_timers[3];
#ifdef CONFIG_DETECT_HUNG_TASK
/*
12) last_switch_count
nvcsw和nivcsw的总和
*/
unsigned long last_switch_count;
#endif
struct task_io_accounting ioac;
#if defined(CONFIG_TASK_XACCT)
u64 acct_rss_mem1;
u64 acct_vm_mem1;
cputime_t acct_timexpd;
#endif

/*
28. 缺页统计
*/
unsigned long min_flt, maj_flt;

/*
29. 进程权能
*/
const struct cred *real_cred;
const struct cred *cred;
struct mutex cred_guard_mutex;
struct cred *replacement_session_keyring;

/*
30. comm[TASK_COMM_LEN]
相应的程序名
*/
char comm[TASK_COMM_LEN];

/*
31. 文件
1) fs
用来表示进程与文件系统的联系,包括当前目录和根目录
2) files
表示进程当前打开的文件
*/
int link_count, total_link_count;
struct fs_struct *fs;
struct files_struct *files;

#ifdef CONFIG_SYSVIPC
/*
32. sysvsem
进程通信(SYSVIPC)
*/
struct sysv_sem sysvsem;
#endif

/*
33. 处理器特有数据
*/
struct thread_struct thread;

/*
34. nsproxy
命名空间
*/
struct nsproxy *nsproxy;

/*
35. 信号处理
1) signal: 指向进程的信号描述符
2) sighand: 指向进程的信号处理程序描述符
*/
struct signal_struct *signal;
struct sighand_struct *sighand;
/*
3) blocked: 表示被阻塞信号的掩码
4) real_blocked: 表示临时掩码
*/
sigset_t blocked, real_blocked;
sigset_t saved_sigmask;
/*
5) pending: 存放私有挂起信号的数据结构
*/
struct sigpending pending;
/*
6) sas_ss_sp: 信号处理程序备用堆栈的地址
7) sas_ss_size: 表示堆栈的大小
*/
unsigned long sas_ss_sp;
size_t sas_ss_size;
/*
8) notifier
设备驱动程序常用notifier指向的函数来阻塞进程的某些信号
9) otifier_data
指的是notifier所指向的函数可能使用的数据。
10) otifier_mask
标识这些信号的位掩码
*/
int (*notifier)(void *priv);
void *notifier_data;
sigset_t *notifier_mask;

/*
36. 进程审计
*/
struct audit_context *audit_context;
#ifdef CONFIG_AUDITSYSCALL
uid_t loginuid;
unsigned int sessionid;
#endif

/*
37. secure computing
*/
seccomp_t seccomp;

/*
38. 用于copy_process函数使用CLONE_PARENT标记时
*/
u32 parent_exec_id;
u32 self_exec_id;

/*
39. alloc_lock
用于保护资源分配或释放的自旋锁
*/
spinlock_t alloc_lock;

/*
40. 中断
*/
#ifdef CONFIG_GENERIC_HARDIRQS
struct irqaction *irqaction;
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
unsigned int irq_events;
int hardirqs_enabled;
unsigned long hardirq_enable_ip;
unsigned int hardirq_enable_event;
unsigned long hardirq_disable_ip;
unsigned int hardirq_disable_event;
int softirqs_enabled;
unsigned long softirq_disable_ip;
unsigned int softirq_disable_event;
unsigned long softirq_enable_ip;
unsigned int softirq_enable_event;
int hardirq_context;
int softirq_context;
#endif

/*
41. pi_lock
task_rq_lock函数所使用的锁
*/
spinlock_t pi_lock;

#ifdef CONFIG_RT_MUTEXES
/*
42. 基于PI协议的等待互斥锁,其中PI指的是priority inheritance/9优先级继承)
*/
struct plist_head pi_waiters;
struct rt_mutex_waiter *pi_blocked_on;
#endif

#ifdef CONFIG_DEBUG_MUTEXES
/*
43. blocked_on
死锁检测
*/
struct mutex_waiter *blocked_on;
#endif

/*
44. lockdep,
*/
#ifdef CONFIG_LOCKDEP
# define MAX_LOCK_DEPTH 48UL
u64 curr_chain_key;
int lockdep_depth;
unsigned int lockdep_recursion;
struct held_lock held_locks[MAX_LOCK_DEPTH];
gfp_t lockdep_reclaim_gfp;
#endif

/*
45. journal_info
JFS文件系统
*/
void *journal_info;

/*
46. 块设备链表
*/
struct bio *bio_list, **bio_tail;

/*
47. reclaim_state
内存回收
*/
struct reclaim_state *reclaim_state;

/*
48. backing_dev_info
存放块设备I/O数据流量信息
*/
struct backing_dev_info *backing_dev_info;

/*
49. io_context
I/O调度器所使用的信息
*/
struct io_context *io_context;

/*
50. CPUSET功能
*/
#ifdef CONFIG_CPUSETS
nodemask_t mems_allowed;
int cpuset_mem_spread_rotor;
#endif

/*
51. Control Groups
*/
#ifdef CONFIG_CGROUPS
struct css_set *cgroups;
struct list_head cg_list;
#endif

/*
52. robust_list
futex同步机制
*/
#ifdef CONFIG_FUTEX
struct robust_list_head __user *robust_list;
#ifdef CONFIG_COMPAT
struct compat_robust_list_head __user *compat_robust_list;
#endif
struct list_head pi_state_list;
struct futex_pi_state *pi_state_cache;
#endif
#ifdef CONFIG_PERF_EVENTS
struct perf_event_context *perf_event_ctxp;
struct mutex perf_event_mutex;
struct list_head perf_event_list;
#endif

/*
53. 非一致内存访问(NUMA Non-Uniform Memory Access)
*/
#ifdef CONFIG_NUMA
struct mempolicy *mempolicy; /* Protected by alloc_lock */
short il_next;
#endif

/*
54. fs_excl
文件系统互斥资源
*/
atomic_t fs_excl;

/*
55. rcu
RCU链表
*/
struct rcu_head rcu;

/*
56. splice_pipe
管道
*/
struct pipe_inode_info *splice_pipe;

/*
57. delays
延迟计数
*/
#ifdef CONFIG_TASK_DELAY_ACCT
struct task_delay_info *delays;
#endif

/*
58. make_it_fail
fault injection
*/
#ifdef CONFIG_FAULT_INJECTION
int make_it_fail;
#endif

/*
59. dirties
FLoating proportions
*/
struct prop_local_single dirties;

/*
60. Infrastructure for displayinglatency
*/
#ifdef CONFIG_LATENCYTOP
int latency_record_count;
struct latency_record latency_record[LT_SAVECOUNT];
#endif

/*
61. time slack values,常用于poll和select函数
*/
unsigned long timer_slack_ns;
unsigned long default_timer_slack_ns;

/*
62. scm_work_list
socket控制消息(control message)
*/
struct list_head *scm_work_list;

/*
63. ftrace跟踪器
*/
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
int curr_ret_stack;
struct ftrace_ret_stack *ret_stack;
unsigned long long ftrace_timestamp;
atomic_t trace_overrun;
atomic_t tracing_graph_pause;
#endif
#ifdef CONFIG_TRACING
unsigned long trace;
unsigned long trace_recursion;
#endif
};

最近在刷leetcode算法的入门题,刷到二叉树,一开始浑浑噩噩,后来掌握了套路,自己搞定一个,而且很优雅,比留言中的大部分都优雅,嗯,写个日志自嗨一把。
所有刷题都提交到我的github上了,具体位置:windanchaos的github

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
题目要求:
将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。

本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1

示例:

给定有序数组: [-10,-3,0,5,9],

一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:

0
/ \
-3 9
/ /
-10 5

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

我的解法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
/*
执行用时 :1 ms, 在所有 Java 提交中击败了99.76%的用户
内存消耗 :37.2 MB, 在所有 Java 提交中击败了97.44%的用户
*/
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
if(nums.length==0 || null == nums) return null;
//只有一个元素的情况
if(nums.length==1) return new TreeNode(nums[0]);
//数量大于1,构建节点
TreeNode root=new TreeNode(nums[nums.length/2]);
//构建左右子树
root.left=sortedArrayToBST(Arrays.copyOfRange(nums,0,nums.length/2));
root.right=sortedArrayToBST(Arrays.copyOfRange(nums,nums.length/2+1,nums.length));
return root;
}
}

最近在看一本讲数据库架构的英文书,书中很多次提及到一个叫缓存的词语,在我们商城的业务系统中也经常听到缓存这个词语。于是百度找到这篇文章。内心觉得总结很到位。转自:缓存技术原理

一、前言

应用中使用缓存技术,往往可以大大减少计算量,有效提升响应速度,让有限的资源服务更多的用户。但是,似乎还没有一种缓存方案可以满足所有的业务场景,我们需要根据自身的特殊场景和背景,选择最适合的缓存方案,尽量以最小的成本最快的效率达到最优的目的。本文将从多个方面对缓存进行分析,以便作为选择缓存方案的考量。
二、文章要点

三、缓存的理解 3.1 狭义的理解

缓存指的是 CPU 缓存,当 CPU 要读取一个数据时,首先从 CPU 缓存中查找,找到就立即读取并送给 CPU 处理;没有找到,就从速率相对较慢的内存中读取并送给 CPU 处理,同时把这个数据所在的数据块调入缓存中,可以使得以后对整块数据的读取都从缓存中进行,不必再调用内存。

3.2 广义的理解

凡是位于速度相差较大的两种硬件/软件之间的,用于协调两者数据传输速度差异的结构,均可称之为缓存。

3.3 缓存的优点

如下,一个 Web 应用架构一般有如下几层:

在此架构的不同层级之间,都可以存在缓存。比如:

总结来说,缓存在如下三个方面做了提升:

四、CPU 缓存简介

CPU 缓存(Cache Memory)是位于 CPU与 内存之间的临时存储器,它的容量比内存小的多,但是交换速率却比内存要快得多。缓存的出现主要是为了解决 CPU 运算速率与内存读写速率不匹配的矛盾,因为 CPU 运算速率要比内存读写速率快很多,这样会使 CPU 花费很长时间等待数据到来或把数据写入内存。在缓存中的数据是内存中的一小部分,但这一小部分是短时间内 CPU 即将访问的,当 CPU 调用大量数据时,就可避开内存直接从缓存中调用,从而加快读取速率。由此可见,在 CPU 中加入缓存是一种高效的解决方案,这样整个内存储器(缓存+内存)就变成了既有缓存的高速率,又有内存的大容量的存储系统了。 缓存基本上都是采用 SRAM 存储器,存储器在计算机内部的组织方式如下图所示:

越往上,存储器的容量越小、成本越高、速度越快。由于 CPU 和主存之间巨大的速度差异,系统设计者被迫在 CPU 寄存器和主存之间插入了一个小的 SRAM 高速缓存存储器称为 L1 缓存,大约可以在 2-4 个时钟周期(计算机中最小的时间单位)内访问。再后来发现 L1 高速缓存和主存之间还是有较大差距,又在 L1 高速缓存和主存之间插入了 L2 缓存,大约可以在 10 个时钟周期内访问。后面还新增了 L3 等,于是,在这样的模式下,在不断的演变中形成了现在的存储体系。
五、分布式缓存原理 5.1 本地缓存

本地缓存可能是大家用的最多的一种缓存方式了,如 Ehcache、Guava Cache 等,它是在应用中的缓存组件,其最大的优点是应用和 cache 是在同一个进程内部,请求缓存非常快速,没有过多的网络开销等,在单应用不需要集群支持或者集群情况下各节点无需互相通知的场景下使用本地缓存较合适; 同时,它的缺点也是因为缓存跟应用程序耦合,多个应用程序无法直接的共享缓存,各应用或集群的各节点都需要维护自己的单独缓存,对内存是一种浪费。

5.2 分布式缓存特性

分布式缓存能够高性能地读取数据、能够动态地扩展缓存节点、能够自动发现和切换故障节点、能够自动均衡数据分区,而且能够为使用者提供图形化的管理界面,部署和维护都十分方便。优秀的分布式缓存系统有 Memcached、Redis,还有阿里自主开发的 Tair 等;

那么,分布式缓存又是如何做的呢?
5.3 分布式缓存实现原理

数据读取

分布式缓存由一个服务端实现管理和控制,由多个客户端节点存储数据,以达到提高数据的读取速率。那读取某个数据的时候,可以根据一致性哈希算法确定数据的存储和读取节点。以数据 D,节点总个数 N 为基础,通过一致性哈希算法计算出数据 D 对应的哈希值(相当于门牌号),根据这个哈希值就可以找到对应的节点了。一致哈希算法的好处在于节点个数发生变化(减少或增加)时无需重新计算哈希值,保证数据储存或读取时可以正确、快速地找到对应的节点。

数据均匀分布

由多个客户端节点存储数据时,需要保证数据均匀分布。比如,服务器数量较少,很可能造成有些服务器存储的数据较多,承担的压力较大,有些服务器就比较空闲。 解决的办法就是,把一台服务器虚拟成多台服务器,可以在计算服务器对应的哈希值时,在IP地址字符串加多个“尾缀”,比如:10.0.0.1/#1 10.0.0.1/#2 10.0.0.1/#3… 这样,一台物理服务器就被虚拟化成多台服务器。

数据的热备份

实现数据的热备份之前,需要了解一致性哈希算法,计算多台服务器的 IP 地址哈希值时,是将这些哈希值从小到大按顺时针排序组成一个“服务器节点环”。以顺时针方向看“服务器环”,当有客户端把数据存储在第1台服务器上后,第1台服务器负责把该数据拷贝一份给第 2 台服务器,以此类推,也就是说“服务器环”上的每一个节点,都是上一个节点的热备份节点。同时,一个服务器上存了两类数据,一类是自身的业务数据,一类是上一节点的热备数据。
六、影响缓存性能因素 6.1 序列化

访问本地缓存,对于 JVM 语言,有堆内和堆外缓存可以进行选择。由于对内直接以对象的形式进行存储,不需要考虑序列化,而堆外是以字节类型进行存储,就需要进行序列化和反序列化。 序列化一般需要解析对象的结构,而解析对象结构,会带来较大的 CPU 消耗,所以一般的序列化(比如 fastJson)均会缓存对象解析的对象结构,来减少 CPU 的消耗。 具体序列化性能对比这里就不做罗列,可点击 link 这里查看。

6.2 命中率

通常来讲,缓存的命中率越高则表示使用缓存的收益越高,应用的性能越好(响应时间越短、吞吐量越高),抗并发的能力越强。那么影响缓存命中率因素有哪些呢?

业务场景和业务需求

缓存的设计粒度和策略

(1)固定过期时间,被动失效;

(2)感知数据变更,主动更新;

(3)感知数据变更,主动更新。并设置过期时间被动失效兜底;

(4)按照数据冷热性制定策略,如热数据主动失效并 reload,冷数据只失效不 reload 等。

然而,当数据发生变化时,直接更新缓存的值会比移除缓存(或者让缓存过期)的命中率更高,当然,系统复杂度也会更高。

缓存容量和基础设施

缓存的容量有限,则容易引起缓存失效和被淘汰(目前多数的缓存框架或中间件都采用了 LRU 算法)。同时,缓存的技术选型也是至关重要的,比如采用应用内置的本地缓存就比较容易出现单机瓶颈,而采用分布式缓存则比较容易扩展。所以需要做好系统容量规划,并考虑是否可扩展。此外,不同的缓存框架或中间件,其效率和稳定性也是存在差异的。

其他因素

缓存故障处理:当缓存节点发生故障时,需要避免缓存失效并最大程度降低影响,业内比较典型的做法就是通过一致性 Hash 算法,或者通过节点冗余的方式。

以上可见,想要提高缓存收益,需要应用尽可能的通过缓存直接获取数据,并避免缓存失效。需要在业务需求,缓存粒度,缓存策略,技术选型等各个方面去通盘考虑并做权衡。尽可能的聚焦在高频访问且时效性要求不高的热点业务上,通过缓存预加载(预热)、增加存储容量、调整缓存粒度、更新缓存等手段来提高命中率。
6.3 缓存清空策略

通过前面介绍,我们知道缓存策略对于缓存的性能具有很大的影响。那么,缓存策略是为了解决什么问题,又有哪些方案可选呢?

面临的问题

主存容量远大于 CPU 缓存,磁盘容量远大于主存,因此无论是哪一层次的缓存都面临一个同样的问题:当容量有限的缓存的空闲空间全部用完后,又有新的内容需要添加进缓存时,如何挑选并舍弃原有的部分内容,从而腾出空间放入这些新的内容。

解决方案

解决这个问题的算法有几种,如最久未使用算法(LRU)、先进先出算法(FIFO)、最近最少使用算法(LFU)、非最近使用算法(NMRU)等,这些算法在不同层次的缓存上执行时拥有不同的效率和代价,需根据具体场合选择最合适的一种。下面针对每一种算法做一个简单介绍:

七、高并发场景常见缓存问题

通常来讲,在相同缓存时间和 key 的情况下,并发越高,缓存的收益会越高,即便缓存时间很短。而高并发应用场景下一般会引发以下常见的三个问题。

7.1 缓存穿透问题

问题描述

出现场景:指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能 DB 就挂掉了。要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。

解决方案

7.2 缓存并发问题

问题描述

有时候如果网站并发访问高,一个缓存如果失效,可能出现多个进程同时查询 DB,同时设置缓存的情况,如果并发确实很大,这也可能造成 DB 压力过大,还有缓存频繁更新的问题。

解决方案

可以对缓存查询加锁,如果 KEY 不存在,就加锁,然后查 DB 入缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入 DB 查询。
7.3 缓存失效问题

问题描述

引起这个问题的主要原因还是高并发的时候,平时我们设定一个缓存的过期时间时,可能有一些会设置 1 分钟啊,5 分钟这些,并发很高时可能会出在某一个时间同时生成了很多的缓存,并且过期时间都一样,这个时候就可能引发一当过期时间到后,这些缓存同时失效,请求全部转发到 DB,DB 可能会压力过重。

解决方案

其中的一个简单方案就是将缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如 1-5 分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
总结

到这里,关于缓存的内容就介绍完毕了,相信通过本文可以帮助我们理解缓存的基本工作原理,了解常见缓存问题的解决思路。

问题描述:
我们的tomcat启动了apr启动https端口,我设置了systemd的tomcat.service的文件,使用systemctl start tomcat,始终无法识别apr的环境变量。研究了一会后解决。
先贴tomcat.service最终设置,只需加一行配置即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Unit]
Description=java tomcat project
After=syslog.target network.target

[Service]
Type=forking
User=user
Group=user
EnvironmentFile=/opt/apache-tomcat/bin/config
ExecStart=/opt/apache-tomcat/bin/startup.sh
ExecReload=
ExecStop=/arthas/servers/apache-tomcat-8.5.4-80/bin/shutdown.sh
PrivateTmp=true

[Install]
WantedBy=multi-user.target

关键点:

EnvironmentFile=/opt/apache-tomcat/bin/config 的设置。
文件内容如下,是启动tomcat的环境变量:

1
LD_LIBRARY_PATH=/usr/local/apr/lib:$LD_LIBRARY_PATH

原理:
1、/etc/profile或者/etc/security/limit.d这些文件中配置的环境变量仅对通过pam登录的用户生效,而systemd是不读这些配置的,所以这就造成登录到终端时查看环境变量和手动启动应用都一切正常,但是systemd无法正常启动应用。
2、EnvironmentFile:该字段指定软件自己的环境参数。

参考:
https://blog.csdn.net/lizao2/article/details/81030380
https://www.cnblogs.com/jhxxb/p/10654554.html

git在维护版本库的时候统一使用的是LF,这样就可以保证文件跨平台的时候保持一致。
在Linux下默认的换行符也是LF,那也就不存在什么问题。
在Windows下默认的换行符是CRLF,那么我们需要保证在文件提交到版本库的时候文件的换行符是LF,通常来说有两种方法:

1
2
3
4
5
6
# 在工作区使用CRLF,使用git commit提交的时候git帮你把所有的CRLF转换为LF。
git config --global core.autocrlf true
# 在工作区使用LF
git config --global core.autocrlf input
# 避免文件中有混用换行符
git config --global core.safecrlf true

以上措施如果都不管用,尤其是莫名其妙的,git刚刚clone的代码库就存在图片换行符的问题,比如我的。
刚clone的代码库就存在换行符导致的问题
那么你可以试试——————————————————————————升级你的git,是的,所有设置方案都试过后,这一招彻底解决。

这里下载:https://github.com/git/git/releases?after=v2.21.0-rc1

1
2
3
4
5
6
7
8
9
10
11
12
# centos自带Git,7.x版本自带git 1.8.3.1,安装新版本之前需要使用卸载。
yum remove -y git
# 依赖
yum install -y curl-devel expat-devel gettext-devel openssl-devel zlib-devel gcc perl-ExtUtils-MakeMaker
wget https://github.com/git/git/archive/v2.20.0.tar.gz
tar xzf v2.20.0.tar.gz
cd git-2.22.0
make prefix=/usr/local/git all
make prefix=/usr/local/git install
# 创建软连接
ln -s /usr/local/git/bin/git /usr/bin/git
git --version

最近在践行代码,先把尚学堂的习题做完,遇到有点代表的就发个日志。
以下算法时间复杂度为N,还可以。这个好像是用了某种算法,具体叫动态规划法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
103. 【上机】编程求:∑1+∑2+……+∑100
这里输入最大数100作为参数
*/
public void sigma(int num){
//利用数组记录∑n的值,数组的index=n,特征:∑n=∑(n-1)+n
//数组下标等于数字,数组index为0的丢弃
int[] array_tmp=new int[num+1];
int sum=0;
for(int i=1;i<num+1;i++){
//数组index为0时,默认值0,可直接迭代
array_tmp[i]=array_tmp[i-1]+i;
sum=sum+array_tmp[i];
}
System.out.println("∑1+∑2+……+∑"+num+"的和为:"+sum);
}

∑1+∑2+……+∑100的和为:171700

修复问题描述

阿里云上的安全监测提示:
近日,Apache软件基金会(ASF)向Apache Struts项目管理员发布了关于CVE-2016-1000031漏洞的安全公告,其中披露一个Commons FileUpload库的历史高危漏洞CVE-2016-1000031,而2.3.x系列版本的Apache Struts2仍在使用低版本的Commons FileUpload库,该库作为Struts2的一部分,被用作文件上传的默认机制,远程攻击者利用该漏洞可直接获得服务器权限。2.5.12以上版本的Struts2暂不受影响。
注意:java类web应用的修复一般都需要重启应用
方案一:
升级至2.5.18及以上版本的Struts2,官方下载链接:https://struts.apache.org/download.cgi
方案二:
升级Struts2依赖的Commons FileUpload库版本至最新1.3.3,官方下载地址:https://commons.apache.org/proper/commons-fileupload/download_fileupload.cgi

我这里是思路就是采用方案二。

怎么修复

整体思路很简单,下载源代码,修改需要升级的引用包版本,重新编译构建发布。
1、clone jenkins的源代码到本地,修改引用包的版本。这里直接用我的结果即可,文件为jenkins/core/pom.xml

1
2
3
4
5
6
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
<!--<version>1.3.3-jenkins-2</version> -->
</dependency>

2、编译。

1
2
cd "代码路径"
mvn clean package -pl war -am -DskipTests -Dfindbugs.skip -X

编译过程如果不顺利,或下载不下来文件,自己百度处理。
当然,也可以参考jenkins源码编译和打包(20180408更新) 中的部分步骤,像nodeys的没有就不操作。自己搭建私有库,我是现成的。不自己搭建的话,估计问题也不会太大。

3、踩过的坑。版本问题,编译的版本一定要和当前的版本保持一致,至少不能跳大版本,否则启动不起来,甚至会有各种意想不到的问题。版本一致就顺风顺水。

当然还有一种修复思路,直接替换.jenkins/war/WEB-INF/lib 下面的包,用新包ommons-fileupload-1.3.3.jar换旧包(commons-fileupload-1.3.1-jenkins-2.jar),新包用旧包命名,重启。(阿里云扫描不出来你修复了,大概是扫描它记录的是文件路径吧,我是换过的,重起服务,扫不出来修复了,比较尴尬)。

当然,如果阁下运气好和我用的是一个版本,2.176,那直接下载就可以了。

最后

由于够不着jenkins的维护的资格,所以去issue提交了个bug建议修复pom.xml(要墙)。
https://issues.jenkins-ci.org/browse/SECURITY-1463?filter=-2

由低向上的学习计算机是一条比较漫长的道路。但是,得来的知识却是体系化的。而且越到上边,学习的理解的效率是越高的(暂且自我欺骗,毕竟还没达到那高度)
下面就最近研究和学习做一个复盘。以问题出发为脉络。
1、计算机是怎么做运算的?
关键字:二进制、逻辑门
2、在问题1的基础上又问:为什么是二进制?
3、CPU是如何工作?

1、计算机是怎么做运算的?

回答这个问题,要先弄懂人类在使用10进制做计算时的方法,不赘述。
10进制方法迁移到二进制。基本的加法、乘法(可转成进位的加法),减法转成加法实现,除法转成乘法实现(这个我还没深究)。
进制之间可以平滑的换算。

数和进制的抽象

(这是我独立思考得出的,未见有人说过,或者有但我不了解)。
位和进制大小、进位。是三个基本元素。
位是进位中的位,是进制数的承载单元。位只能承载(0到进制-1)的数。比如:10进制中,一个位可以放0-9的数字,到10的时候就需要进位。二进制,最大放1,到2就要进位。
进制大小:就是所谓的2、10、16。
进位:位中承载的数等于进制大小时,发生的高位+1行为。

所以,可以有5进制。甚至可以有100进制,当然如果能够找到这么多数字的象征,恐怕人类的大脑处理起来也是费尽的。
问题又来了,为什么人类有进制这种抽象,我觉得主要还是来源于认知的范围是有限的,而认知的对象是无限的。之所以说认知范围是有限的,主要是人的身体机能决定的,不能同时(并行)思考和处理多个问题。以有限的认知认识无限,只能将无限的进行分而治之。数字是对现实世界和数量有关的一种抽象。位,就是数字抽象的基本单元。对于人类来说,现实世界的物质是无限的,小到分子原子、大到宇宙万物。所以用进制数可以表示一个特定的数量的概念。

如果存在一个主体(比如上帝),ta使用的是无限进制,宇宙万物都囊括在其中,它不用进位。每个事物都有是唯一的,那么人类的进制就已经没有了意义,ta能使用无限进制的前提是掌握了无限事物的信息,超人类思维的存在。这就是无限这个抽象的力量。

什么是运算

传送门
人类为什么要做运算呢?因为人类的认知是有限的啊,为了得到暂时无法获得的信息,基于已有的信息进行信息的变化和组合,就生成了运算。所以,如果人类可以直接获得任何信息,比如看到一个平面物体,便知道它的面积和体积,那基于几何学的长宽高、积分之类的运算就不再具有它的价值,但这目前还只是一种思维游戏,人类还是渺小的。
这里举个例子:
国王统治了100个小城邦,他欲了解各城邦的粮食产量这个信息,以备决策。最简单的方式就是,统计各城邦的产量,然后全部相加。
如果没有数字和运算,他要得到信息,需要把100个城邦的粮食搬到一起,进行称重了。
插个题外话,统计出来的数字只是一种近似值,为什么呢?因为在实操的过程中,城邦是可以谎报数量的,可以报大或报小,加起来的数已经不是国王心中想要的那个数了,但是这也没有办法,先解决有和无的问题,只要偏差不大的离谱,是可以容忍和妥协的。妥协这词在计算机的实现中也是常见的。
物理学中的很多问题都是靠运算得出的,比如光速是怎么得来的(人类一开始测量得到的光速也只是近似值)?天体物理学又靠什么说宇宙在膨胀?
所以,再不要把运算限定在10进制了,它只是众多进制中人类习以为常的一种而已。
结合进制和运算:
比如5进制的23+12=40,它的10进制是多少呢?

23的10进制为:2x51+3x50=13
12的10进制为:1x51+2x50=7
40的10进制为:4x51+0/*50=20

数字的实际意义则有进行运算的人赋予,抽象的作用便在于此。

逻辑门和它们的电路实现

计算机使用二进制,为什么是二进制呢?
早期的计算机有3进制、还有5进制的,都是电驱动,用简单的电子元件来构建。
这里写图片描述
电信号的电压强弱来表征数字,如上图,如果是5进制,那么不稳定的电压,必然导致不稳定的数。不稳定代表了不可信。
二进制就不同了,电压不稳定,只要在范围内,取值就确定的。所以二进制被优胜劣汰的剩下来了。

传送门
二极管有个特性,就是在某个电压值Uon之前,不导电,一旦突破,则通电,如下图。死区电压不导电。
这里写图片描述
晶体二极管为一个由p型半导体和n型半导体形成的pn结,在其界面处两侧形成空间电荷层,并建有自建电场。当不存在外加电压时,由于pn结两边载流子浓度差引起的扩散电流和自建电场引起的漂移电流相等而处于电平衡状态。当外界有正向电压偏置时,外界电场和自建电场的互相抑消作用使载流子的扩散电流增加引起了正向电流。当外界有反向电压偏置时,外界电场和自建电场进一步加强,形成在一定反向电压范围内与反向偏置电压值无关的反向饱和电流I0。当外加的反向电压高到一定程度时,pn结空间电荷层中的电场强度达到临界值产生载流子的倍增过程,产生大量电子空穴对,产生了数值很大的反向击穿电流,称为二极管的击穿现象。pn结的反向击穿有齐纳击穿和雪崩击穿之分。

PN结的工作原理传送门
P是多电子的一个,N是缺电子的一个,他们都导电,P是多电子要对外输出,N是缺电子要对内吸纳。电子多和少是工程师们利用化学原理制造出来的。它们中间隔了一道空气墙,适当电压可以打通,就导电了。

于是:二极管的导电性就可以抽象出1和0,1为通,0为不通。而通和不通,可以被人类(用电压大小的方式)掌控,下图是3个1。有没有觉得像位的抽象?

通电的二极管111

传送门
我不知道是谁发明或发现了这种转换,五体投地。但其中的核心逻辑:由简单到复杂。
将2个二极管,按照不同的线路布线,就可以得到逻辑运算中的“或、与、非”运算。
下面是盗图,盗图源链接
注意下图表示的电流方向。
“与”门
“与”门
“或”门
“或”门
“非”门
“非”门

最后那些常用的电路都有了固定的接线模式,逻辑门的电路被抽象成了符号,它们共有一个特点:实现了抽象逻辑符号,在物理世界的正确表达:
盗图
“非”门

还可以看看这个网站的逻辑门
Basic Gates and Functions

下面这个电路设计图,实现了单个位的二进制计算逻辑,sum是输出,carry是进位。——专业术语,或者叫抽象名“半加器”。
这里写图片描述
这里贴张这个逻辑电路的真值表:
A输入 B输入 sum输出 carry输出 0 0 0 0 0 1 1 0 1 0 1 0 1 1 0 1

上边电路解决了一位的加法,那么多位的呢?
把电路单元连接起来!
所以引出另一个电路图——全加器,由半加器(上边提到的半加器单元用HALF ADDER来表示了)进化而来。
这里写图片描述

上图实现了,进位场景下(三个输入:低位进位的位、高位的两位),两个输出(sum和carry)的计算单元。

所以继续复杂深化,得到2位的计算电路。下图A0 A1分别代表第1位,第二位的二进制数。
这里写图片描述
继续下去得到一个8位的加法电路:
这里写图片描述

所以,这个图很清晰告诉了一个编程中经常出现的名字:“溢出”。装不下了就溢出了。

乘法运算,是带进位的加法运算。首先要搞定的是位乘法运算。使用And运算即可。比如A/*B,前置条件是假设不会溢出。所以限定A和B都小于1进制的4,结果用8位二进制来保存。如下图:原谅我画图不专业。。核心就是B乘Ai的时候,根据B当前位,为结果进位(左移),我仔细想了想,比较low的是接线的时候,按位来接(比如B1就进1位接在结果线路)。加法器这里,抽象了一把,把所有结果加起来。(里面是应该有3个加法器串联在一起)

这里写图片描述

以上,运算有关的就到此为止(逻辑的没有写就略过了)。

关于计算机的可计算性等价于Lambda算子,请百度。本人还未深刻理解Lambda。

参考:
【10分钟速成课:计算机科学】
Lambda算子简介
denallo学习笔记:λ演算(lambda calculate)

正则引擎

正则引擎主要可以分为两大类:一种是DFA,一种是NFA。这两种引擎都有了很久的历史(至今二十多年),当中也由这两种引擎产生了很多变体!于是POSIX的出台规避了不必要变体的继续产生。这样一来,主流的正则引擎又分为3类:一、DFA,二、传统型NFA,三、POSIX NFA。
DFA 引擎在线性时状态下执行,因为它们不要求回溯(并因此它们永远不测试相同的字符两次)。DFA 引擎还可以确保匹配最长的可能的字符串。但是,因为 DFA 引擎只包含有限的状态,所以它不能匹配具有反向引用的模式;并且因为它不构造显示扩展,所以它不可以捕获子表达式。
传统的 NFA 引擎运行所谓的“贪婪的”匹配回溯算法,以指定顺序测试正则表达式的所有可能的扩展并接受第一个匹配项。因为传统的 NFA 构造正则表达式的特定扩展以获得成功的匹配,所以它可以捕获子表达式匹配和匹配的反向引用。但是,因为传统的 NFA 回溯,所以它可以访问完全相同的状态多次(如果通过不同的路径到达该状态)。因此,在最坏情况下,它的执行速度可能非常慢。因为传统的 NFA 接受它找到的第一个匹配,所以它还可能会导致其他(可能更长)匹配未被发现。
POSIX NFA 引擎与传统的 NFA 引擎类似,不同的一点在于:在它们可以确保已找到了可能的最长的匹配之前,它们将继续回溯。因此,POSIX NFA 引擎的速度慢于传统的 NFA 引擎;并且在使用 POSIX NFA 时,您恐怕不会愿意在更改回溯搜索的顺序的情况下来支持较短的匹配搜索,而非较长的匹配搜索。
使用DFA引擎的程序主要有:awk,egrep,flex,lex,MySQL,Procmail等;
使用传统型NFA引擎的程序主要有:GNU Emacs,Java,ergp,less,more,.NET语言,PCRE library,Perl,PHP,Python,Ruby,sed,vi;
使用POSIX NFA引擎的程序主要有:mawk,Mortice Kern Systems’ utilities,GNU Emacs(使用时可以明确指定);
也有使用DFA/NFA混合的引擎:GNU awk,GNU grep/egrep,Tcl。
举例简单说明NFA与DFA工作的区别:
比如有字符串this is yansen’s blog,正则表达式为 /ya(msen|nsen|nsem)/ (不要在乎表达式怎么样,这里只是为了说明引擎间的工作区别)。 NFA工作方式如下,先在字符串中查找 y 然后匹配其后是否为 a ,如果是 a 则继续,查找其后是否为 m 如果不是则匹配其后是否为 n (此时淘汰msen选择支)。然后继续看其后是否依次为 s,e,接着测试是否为 n ,是 n 则匹配成功,不是则测试是否为 m 。为什么是 m ?因为 NFA 工作方式是以正则表达式为标准,反复测试字符串,这样同样一个字符串有可能被反复测试了很多次!
而DFA则不是如此,DFA会从 this 中 t 开始依次查找 y,定位到 y ,已知其后为a,则查看表达式是否有 a ,此处正好有a 。然后字符串a 后为n ,DFA依次测试表达式,此时 msen 不符合要求淘汰。nsen 和 nsem 符合要求,然后DFA依次检查字符串,检测到sen 中的 n 时只有nsen 分支符合,则匹配成功!
由此可以看出来,两种引擎的工作方式完全不同,一个(NFA)以表达式为主导,一个(DFA)以文本为主导!一般而论,DFA引擎则搜索更快一些!但是NFA以表达式为主导,反而更容易操纵,因此一般程序员更偏爱NFA引擎! 两种引擎各有所长,而真正的引用则取决与你的需要以及所使用的语言!

特殊字符

1
2
(转义字符)若要匹配这些特殊字符之一,在字符前面加反斜杠字符 (\)。 
例如,若要搜索“+”文本字符,可使用表达式“\+”。

元字符 行为 示例 /* 零次或多次匹配前面的字符或子表达式。
等效于 {0,}。 zo/* 与“z”和“zoo”匹配。 + 一次或多次匹配前面的字符或子表达式。
等效于 {1,}。 zo+ 与“zo”和“zoo”匹配,但与“z”不匹配。 ? 零次或一次匹配前面的字符或子表达式。
等效于 {0,1}。
当 ? 紧随任何其他限定符(/*、+、?、{n}、{n,} 或 {n,m})之后时,匹配模式是非贪婪的。
非贪婪模式匹配搜索到的、尽可能少的字符串, 而默认的贪婪模式匹配搜索到的、
尽可能多的字符串。 zo+ 与“zo”和“zoo”匹配,但与“z”不匹配。 ^ 匹配搜索字符串开始的位置。 如果标志中包括 m(多行搜索)字符,^ 还将匹配 \n 或 \r 后面的位置。
如果将 ^ 用作括号表达式中的第一个字符,则会对字符集求反。 ^\d{3} 与搜索字符串开始处的 3 个数字匹配。
[^abc] 与除 a、b 和 c 以外的任何字符匹配。 $ 匹配搜索字符串结尾的位置。 如果标志中包括 m(多行搜索)字符,^ 还将匹配 \n 或 \r 前面的位置。 \d{3}$ 与搜索字符串结尾处的 3 个数字匹配。 . 匹配除换行符 \n 之外的任何单个字符。 若要匹配包括 \n 在内的任意字符,请使用诸如 [\s\S] 之类的模式。 a.c 与“abc”、“a1c”和“a-c”匹配。 [] 标记括号表达式的开始和结尾。 [1-4] 与“1”、“2”、“3”或“4”匹配。 [^aAeEiIoOuU] 与任何非元音字符匹配。 {} 标记限定符表达式的开始和结尾。 a{2,3} 与“aa”和“aaa”匹配。 () 标记子表达式的开始和结尾。 可以保存子表达式以备将来之用。 A(\d) 与“A0”至“A9”匹配。 保存该数字以备将来之用。 | 指示在两个或多个项之间进行选择。 z|food 与“z”或“food”匹配。 (z|f)ood 与“zood”或“food”匹配。 / 表示 JScript 中的文本正则表达式模式的开始或结尾。
在第二个“/”后添加单字符标志可以指定搜索行为。 /abc/gi 是与“abc”匹配的 JScript 文本正则表达式。
g(全局)标志指定查找模式的所有匹配项,i(忽略大小写)标志使搜索不区分大小写。 | 将下一字符标记为特殊字符、文本、反向引用或八进制转义符。 \n 与换行符匹配。 ( 与“(”匹配。 \ 与“”匹配。

元字符

1
下表包含了多字符元字符的列表以及它们在正则表达式中的行为。

元字符 行为 示例 \b 与一个字边界匹配;即字与空格间的位置。 er\b 与“never”中的“er”匹配,但与“verb”中的“er”不匹配。 \B 非边界字匹配。 er\B 与“verb”中的“er”匹配,但与“never”中的“er”不匹配。 \d 数字字符匹配。等效于 [0-9]。 在搜索字符串“12 345”中,\d{2} 与“12”和“34”匹配。 \d 与“1”、“2”、“3”、“4”和“5”匹配。 \D 非数字字符匹配。等效于 [^0-9]。 \D+ 与“abc123 def”中的“abc”和“def”匹配。 \w 与以下任意字符匹配:A-Z、a-z、0-9 和下划线。等效于 [A-Za-z0-9_]。 在搜索字符串“The quick brown fox…”中,\w+ 与“The”、“quick”、“brown”和“fox”匹配。 \W 与除 A-Z、a-z、0-9 和下划线以外的任意字符匹配。等效于 [^A-Za-z0-9_]。 在搜索字符串“The quick brown fox…”中,\W+ 与“…”和所有空格匹配。 [xyz] 字符集。 与任何一个指定字符匹配。 [abc] 与“plain”中的“a”匹配。 [^xyz] 反向字符集。 与未指定的任何字符匹配。 [^abc] 与“plain”中的“p”、“l”、“i”和“n”匹配。 [a-z] 字符范围。 匹配指定范围内的任何字符。 [a-z] 与“a”到“z”范围内的任何小写字母字符匹配。 [^a-z] 反向字符范围。 与不在指定范围内的任何字符匹配。 [^a-z] 与不在范围“a”到“z”内的任何字符匹配。 {n} 正好匹配 n 次。 n 是非负整数。 o{2} 与“Bob”中的“o”不匹配,但与“food”中的两个“o”匹配。 {n,} 至少匹配 n 次。 n 是非负整数。
/* 与 {0,} 相等。

  • 与 {1,} 相等。 o{2,} 与“Bob”中的“o”不匹配,但与“foooood”中的所有“o”匹配。 {n,m} 匹配至少 n 次,至多 m 次。 n 和 m 是非负整数,其中 n <= m。 逗号和数字之间不能有空格。
    ? 与 {0,1} 相等。 在搜索字符串“1234567”中,\d{1,3} 与“123”、“456”和“7”匹配。

非打印字符

字符 匹配 等效于 \f 换页符。 \x0c 和 \cL \n 换行符。 \x0a 和 \cJ \r 回车符。 \x0d 和 \cM \s 任何空白字符。 其中包括空格、制表符和换页符。 [ \f\n\r\t\v] \S 任何非空白字符。 [^ \f\n\r\t\v] \t Tab 字符。 \x09 和 \cI \v 垂直制表符。 \x0b 和 \cK

优先级顺序

1
2
正则表达式的计算方式与算术表达式非常类似;即从左到右进行计算,并遵循优先级顺序。
下表按从高到低的顺序包含了正则表达式运算符的优先级顺序。

运算符 说明 \ 转义符 (), (?:), (?=), [] 括号和中括号 /*、+、?、{n}、{n,}、{n,m} 限定符 ^、$、\任何元字符 定位点和序列 | 替换

在线校验

https://regex101.com/