xen-timer

2023-09-18 22:09:52

目的

主要了解一下arm timer spec 和xen项目中timer是怎么使用,如何实现的。同时也是对学习过程的一些记录。

学习链接

文章链接
时间子系统http://www.wowotech.net/sort/timer_subsystem
arm timer spechttps://developer.arm.com/documentation/102379/0101/The-processor-timers

xen timer代码文件

xen/arch/arm/time.c
xen/arch/arm/vtimer.c
xen/common/timer.c
xen/include/xen/time.h
xen/include/xen/timer.h

Generic Timer

硬件架构

所有使用ARM处理器的系统中都会包含一个标准化的通用定时器(Generic Timer)框架。这个通用定时器提供了一个系统计数器(System Counter)和一组定时器(Timer),其结构如下图:

在这里插入图片描述

系统计数器(System Counter)是一个始终打开的设置,它提供了一个固定频率递增的系统计数值,系统计数值被广播到系统的中的所有核心,使核心对时间的流逝有一个共同的视角。系统计数器值的宽度在56到64位之间,从Armv8.6和Armv9.1-A开始,计数的频率固定在1GHz;在Armv8.6a之前,计数频率由系统设计自身实现选择,通常在1MHz到50MHz的范围内。

每个核都有一组定时器(Timer)。这些定时器(Timer)是比较器,它们与系统计数器(System Counter)提供的广播系统计数进行比较。软件可以配置定时器(Timer),在将来的设定值生成中断或事件。软件还可以使用系统计数来添加时间戳,因为系统计数为所有核心提供了一个公共参考点。

The processor timer

每一个Arm核都配备一组专门为自己服务的定时器。定时器设定的计数值到了之后会通过私有的PPI中断(Private Peripheral Interrupt)向通用中断控制器发中断请求。按照不同的指令集扩展,每组都有最多7个定时器,但无论如何最基本的都会提供4个,它们分别是:

Timer nameWhen is the timer presentProvide
EL1 physical timerAlwaysyes
EL1 virtual timerAlwaysyes
Non-secure EL2 physical timerImplements EL2yes
Non-secure virtual physical timerImplements ARMv8.1-VHEno
EL3 physical timerImplements EL3yes
Secure EL2 physical timerImplements ARMv8.4-SecEL2no
Secure EL2 virtual timerImplements ARMv8.4-SecEL2no

计数和频率

CNTPCT_ELx

对于系统计数器来说,可以通过读取控制寄存器CNTPCT_EL0来获得当前的系统计数值(System Counter的值,无论处于哪个异常级别),也就是通过以下汇编指令:

MRS Xn, CNTPCT_EL0

这条指令是可以乱序执行的,使用的时候要适当保护。当读取计数器的顺序很重要时,可以使用ISB,如下面的代码所示:

loop:  // Polling for some communication to indicate a requirement to read the timer
  LDR X1, [X2] #以X2寄存器的值作为内存地址,加载这个内存地址的值到X1寄存器中
  CBZ x1, loop #比较(Compare),如果结果为零(Zero)就跳转到loop
  ISB         // Without this, the CNTPCT could be read before the memory location in
              // [X2] has had the value 0 written to it
  MRS X1, CNTPCT_EL0
  # 事实上,在xen的代码里,也很可以看到确实会用ISB来保持指令执行的前后次序
CNTFRQ_EL0

CNTFRQ_EL0报告系统计数的频率。但是,这个寄存器不是由硬件填充的。寄存器在实现的最高异常级别上是可写的,在所有异常级别上是可读的。固件(通常在EL3上运行)将这个寄存器填充为早期系统初始化的一部分。更高级别的软件,如操作系统,可以使用寄存器来获取频率。

Timer registers

每个定时器有以下三种系统寄存器,如下:

RegisterPurpose
_CTL_EL每组定时器都还有一个控制寄存器(CTL),其只有最低三位有意义,其它位全是保留的。具体参考下表
_CVAL_EL比较寄存器有64位,如果设置了之后,当系统计数器达到或超过了这个值之后(CVAL < System Counter),就会触发定时器中断。通过这种方式来实现第一类定时任务
_TVAL_EL定时寄存器有32位,如果设置了之后,会将比较寄存器设置成当前系统计数器加上设置的定时寄存器的值(CVAL= System Counter + TVAL)。当系统计数器(System Counter)达到或超过了这个值后,就会触发定时中断。通过这种方式来实现第二种定时任务

控制寄存器低三位表示:

bitfuncpurpose
0ENBALE是否打开定时器,使其工作
1IMASK中断掩码,若设置成1,则即使定时器是工作的,仍然不会发生中断
2ISTATUS定时器中断的状态。 该位是只读的,1表示产生了中断

在寄存器名称中, 标识正在访问的定时器。 下表显示了可能的值:

TimerRegister prefixEL
EL1 physical timerCNTPEL0
EL1 virtual timerCNTPEL0
Non-secure EL2 physical timerCNTHPEL2
Non-secure virtual physical timerCNTHPEL2
EL3 physical timerCNTPSEL1
Secure EL2 physical timerCNTHPSEL2
Secure EL2 virtual timerCNTHVSEL2

For example, CNTP_CVAL_EL0 is the Comparator register of the EL1 physical timer.

访问timer

对于某些定时器,可以配置哪些异常级别可以访问定时器

  1. EL1物理和虚拟定时器:对这些定时器的EL0访问由CNTKCTL_EL1控制。

  2. EL2物理和虚拟定时器:当HCR_EL2.{TGE,E2H}=={1,1}, EL0对这些定时器的访问由CNTKCTL_EL2控制。

配置定时器

有两种方法配置定时器,要么使用比较器(CVAL)寄存器,要么使用定时器(TVAL)寄存器。

  • 比较寄存器CVAL是一个64位寄存器。软件向这个寄存器写入一个值,当计数达到或超过该值时,计时器就会触发,如下所示:
Timer Condition Met: CVAL <= System Count
  • 定时寄存器有32位,如果设置了之后,会将比较寄存器设置成当前系统计数器加上设置的定时寄存器的值(CVAL= 系统计数器 + TVAL),后面的流程就跟方式1一致了:
CVAL = TVAL + System Counter 
Timer Condition Met: CVAL <= System Count

记住,TVAL和CVAL是对同一个定时器进行编程的不同方法。它们不是两个不同的定时器。

中断关联

可以通过配置定时器来产生中断。来自核心定时器的中断只能被传递到该核心。这意味着一个核心的定时器不能用来生成针对另一个核心的中断。

中断的生成是通过CTL寄存器的这些字段来控制的:

  • ENABLE 置1打开定时器,使其工作;
  • IMASK 中断屏蔽。0:启用中断生成 1:禁用中断生成
  • ISTATUE ENABLE==1 同时(Cval <= System Count)置1,报告定时器是否正在触发

为了产生中断,软件必须将ENABLE设置为1,将IMASK设置为0。当定时器触发时CVAL <= System Count,中断信号被assert到中断控制器。

每个定时器的中断号如下所示:

TimerRegister prefixINITID
EL1 physical timerCNTP30
EL1 virtual timerCNTP27
Non-secure EL2 physical timerCNTHP26
Non-secure virtual physical timerCNTHP28
EL3 physical timerCNTPS29
Secure EL2 physical timerCNTHPS20
Secure EL2 virtual timerCNTHVS19

这些INTID在私有外围中断(PPI)范围内。这些INITD对于特定的核心是私有的。这意味着每个核心都将其EL1物理计时器视为INTID 30。

定时器生成的中断触发类型是电平触发。这意味着,一旦达到定时器触发条件,定时器将继续发出中断信号,直到发生以下情况之一:

  • IMASK 置位1,屏蔽timer对应中断;
  • ENBALE 置位0,关闭该定时器;
  • TVAL or CVAL 更新,以致(CVAL <= System Count)不成立。

在为定时器编写中断处理程序时,软件在GIC中取消中断之前清除中断是很重要的。否则GIC将重新发出相同的中断信号。

timer虚拟化

前面,我们介绍了处理器中不同的定时器。这些计时器可以分为两组:虚拟定时器和物理定时器。

物理定时器(如EL3物理定时器、CNTPS)与系统计数器提供的计数值进行比较。这个值被称为物理计数,由CNTPCT_EL0提供。

虚拟定时器(如EL1虚拟定时器、CNTV)与虚拟计数进行比较。虚拟计数的计算方法为:

Virtual Count = Physical Count - <offset>

offset在寄存器CNTVOFF_EL2中指定,它只能在EL2或EL3处访问(后缀为EL2)。该配置如下图所示:

在这里插入图片描述

note:如果EL2没有实现,offset固定为0。这意味着虚拟计数和物理计数值总是相同的。

虚拟计数允许管理程序向虚拟机(VM)显示虚拟时间。例如,hypervisor可以使用offset来隐藏虚拟机未被调度时的时间流逝。这意味着虚拟计数可以表示虚拟机经历的时间,而不是wall clock time。

事件流

Generic Timer 还可以用来生成事件流,作为等待事件机制的一部分。WFE指令将核心置于低功耗状态,由事件唤醒核心。

有几种方法来生成一个事件,包括:

  • 在不同的核心上执行SEV(发送事件)指令;
  • 清除核心的全局独占监视器;
  • 从核心的通用定时器使用事件流。

Generic Timer 可以配置为以定期间隔生成事件流。此配置的一个用途是生成超时。通常在等待资源可用时使用WFE,此时等待时间预计不会很长。来自计时器的事件流意味着核心处于低功耗状态的最大时间是有限的。

事件流可以从物理计数CNTPCT_EL0或虚拟计数CNTVCT_EL0生成。

  • CNTKCTL_EL1 - 控制从CNTVCT_EL0生成事件流
  • CNTKCTL_EL2 - 控制从CNTPCT_EL0生成事件流

对于每个寄存器,位域如下:

  • EVNTEN Enables or disables the generation of events
  • EVNTI Controls the rate of events
  • EVNTDIR Controls when the event is generated

EVNTI:指定了0到15范围内的位位置。当所选位置的位发生变化时,将产生事件。例如,如果EVNTI设置为3,则当计数的[3]位发生变化时,将生成一个事件。

EVNTDIR:控制所选位从1到0或从0到1转换时是否生成事件。

总结

该表总结了本节中讨论的不同计时器的信息。

在这里插入图片描述

对于这些计时器,虚拟偏移量(CNTVOFFSET_EL2)总是表现为0。因此,尽管这些计时器与虚拟计数值进行比较,但实际上它们使用的是物理计数器值。

Subjects to re-direction when HCR_EL2.E2H==1

数据结构

问题:如何抽象一个定时器?

回答:xen中用下面的数据结构来表示timer。

/* xen/include/xen/timer.h */
struct timer {
    s_time_t expires; // System time expiry value (nanoseconds since boot):系统时间到期值

    union {     // Position in active-timer data structure
        unsigned int heap_offset; // Timer-heap offset (TIMER_STATUS_in_heap)
        struct timer *list_next;  // Linked list (TIMER_STATUS_in_list)
        struct list_head inactive;// Linked list of inactive timers TIMER_STATUS_inactive
    };

    /* On expiry, '(*function)(data)' will be executed in softirq context. */
    void (*function)(void *); // timer处理函数(当cnt值达到expires时就会触发运行)
    void *data;               // timer处理函数的参数

    uint16_t cpu;   // 将在其上安装和执行此计时器的CPU

    uint8_t status; // timer 状态
    /* 包含如下几种状态:
     * TIMER_STATUS_invalid     : Should never see this
     * TIMER_STATUS_inactive    : Not in use; can be activated
     * TIMER_STATUS_killed      : Not in use; cannot be activated
     * TIMER_STATUS_in_heap     : In use; on timer heap
     * TIMER_STATUS_in_list     : In use; on overflow linked list
     */
};

timer是管理一个定时器实例,xen中的timers就是管理一个cpu中的所有timer实例,两者在单词就差一个s,切记需要认真区分。

/* xen/common/timer.c */
struct timers {
    spinlock_t     lock;
    struct timer **heap;
    struct timer  *list;
    struct timer  *running;    /* cpu正在运行执行的timer */
    struct list_head inactive;
} __cacheline_aligned;

static DEFINE_PER_CPU(struct timers, timers); // 每个cpu都有一份

流程分析

timer系统初始化

--> start_xen
    -------- (1) 获取设备树的节点信息,如频率,各组定时器的中断号 --------
    --> preinit_xen_time // 获取timer节点,获取cpu_khz/boot_count
    	--> preinit_dt_xen_time
    		--> dt_device_noe timer = dt_find_matching_node // 获取设备树的timer节点
    		--> dt_device_set_used_by(timer, DOMID_XEN) // timer被xen占用
    		--> dt_property("clock_frequency", rate) // 若有 cpu_khz = rate/1000
    	--> cpu_khz=READ_SYSREG(CNTFRQ_EL0&CNTFRQ_MASK)/1000 //设备树没有设置运行频率,去读取CNTFRQ_EL0寄存器(在EL3阶段设置该寄存器)
    	--> platform_init_time //没有注册对应平台的init_time,空函数
    	--> boot_count = get_cycles // 系统启动时间 本质:READ_SYSREG64(CNTPCT_EL0)
    --> init_xen_time  // 获取timer中断号
    	--> init_dt_xen_time // 获取设备树的timer INIID,保存在timer_irq[]数组中

    -------- (2) 初始化timer寄存器,同时根据定时器中断号注册对应的服务函数 --------
    --> init_timer_interrupt // timer与gic的关联,注册对应的timer中断服务函数
    	--> WRITE_SYSREG64(0, CNTVOFF_EL2) // No VM-specific offset
    	--> WRITE_SYSREG(CNTHCTL_EL2_EL1PCTEN, CNTHCTL_EL2) //Kernel/user can access to physical counter
    	--> WRITE_SYSREG(0, CNTP_CTL_EL0) // Physical timer disabled
    	--> WRITE_SYSREG(0, CNTHP_CTL_EL2) // Hypervisor's timer disabled
    	--> request_irq(timer_irq[TIMER_HYP_PPI],timer_interrupt) // 注册HYP timer handler
    	--> request_irq(timer_irq[TIMER_VIRT_PPI], vtimer_interrupt)
    	--> request_irq(timer_irq[TIMER_PHYS_NONSECURE_PPI],timer_interrupt)

    -------- (3) 初始化软件定时器和调试软定时器接口 --------
    --> timer_init // timer定时的软中断注册
    	--> open_softirq(TIMER_SOFTIRQ,timer_softirq_action) // 注册timer软中断
    	--> cpu_callback(&cpu_nfb, CPU_UP_PREPARE, cpu) // Only initialise ts once
    		--> struct timers *ts = &per_cpu(timers, cpu)
            --> INIT_LIST_HEAD(&ts->inactive)
            --> spin_lock_init(&ts->lock)
            --> ts->heap = dummy_heap
    	--> register_cpu_notifier(&cpu_nfb) // 注册给非boot cpu初始化 struct timers
    	--> register_keyhandler('a', dump_timerq) // timer调试接口
    

初始化timer

init_timer(struct timer *timer,       // 一个timer抽象出一个struct timer定时器实体
           void (*function)(void *), // 处理函数
           void *data,               // 处理函数的参数
           uint  cpu)                // 绑定到具体cpu上
{
    timer->function = function;
    timer->data = data;
    timer->cpu = cpu;
    timer->status = TIMER_STATUS_inactive; // 初始化的timer的状态为inactive
    list_add(&timer->inactive, &per_cpu(timers, cpu).inactive); // 加到链表中维护
}

初始化后,只有expires的值以及status还没准备好,其他的资源都就绪了。

设置timer

void set_timer(struct timer *timer, s_time_t expires)
{
    /* 1: 设置前需要判断timer是否在激活状态,如果是,需要更改如下内容:
     * (1)从对应的链表删除该timer,根据情况抛出cpu_raise_softirq(timer->cpu, TIMER_SOFTIRQ)
     * (2)timer->status = TIMER_STATUS_inactive
     * (3)添加到timers.inactive链表中
     */
    if ( active_timer(timer) ) // TIMER_STATUS_in_heap/in_list为活跃状态
        deactivate_timer(timer); 
    /* 2: 设置定时到期点 */
    timer->expires = expires;
	/* 3: 激活timer,更改状态 */
    activate_timer(timer);
}

激活timer

static inline void activate_timer(struct timer *timer)
{
    ASSERT(timer->status == TIMER_STATUS_inactive);
    timer->status = TIMER_STATUS_invalid;
    list_del(&timer->inactive); //从inactive链表中删除需要激活的timer

    if ( add_entry(timer) ) /* 把该timer添加到per cpu变量timers的heap以及list里面 */
        cpu_raise_softirq(timer->cpu, TIMER_SOFTIRQ); /* 唤醒timer软中断,处理timers->list */
}

停止timer

void stop_timer(struct timer *timer)
{
    if ( active_timer(timer) )
        deactivate_timer(timer);
}

/* 判断timer是否激活状态,激活状态机为 TIMER_STATUS_in_heap || TIMER_STATUS_in_list */
bool active_timer(const struct timer *timer)
{
    return timer_is_active(timer);
    /* timer->status >= TIMER_STATUS_in_heap */
}

void deactivate_timer(struct timer *timer)
{
    if ( remove_entry(timer) ) // 之前状态为active的,需要从对应链表剔除timer
        cpu_raise_softirq(timer->cpu, TIMER_SOFTIRQ); // 同时抛出软中断,处理其他状态

    timer->status = TIMER_STATUS_inactive; // timer状态值更改为inactive
    list_add(&timer->inactive, &per_cpu(timers, timer->cpu).inactive); // 插入inactive链表
}

执行timer

前面有提到,xen注册了3个timer中断,分别是物理timer、虚拟timer以及hyp timer(phys=30 virt=27 hyp=26)

其中物理timer和hyp timer共用一个中断服务函数timer_interrupt,虚拟timer使用vtimer_interrupt中断服务函数。

timer_interrupt

static void timer_interrupt(int irq, void *dev_id, struct cpu_user_regs *regs)
    if ( irq == (timer_irq[TIMER_HYP_PPI]) && READ_SYSREG(CNTHP_CTL_EL2) & CNTx_CTL_PENDING ) /* 中断属于hyp并且检查产生了中断 */
    {
        perfc_incr(hyp_timer_irqs); /* hyp_timer_irqs用来统计产生了多少次hyp timer中断 */
        raise_softirq(TIMER_SOFTIRQ); /* 唤醒timer软中断来处理 */
        WRITE_SYSREG(0, CNTHP_CTL_EL2); /* disable EL2 timer interrupt */
    }
	if ( irq == (timer_irq[TIMER_PHYS_NONSECURE_PPI]) && READ_SYSREG(CNTP_CTL_EL0) & CNTx_CTL_PENDING ) /* 中断属于phy */
    {
        perfc_incr(phys_timer_irqs); /* phys_timer_irqs用来统计产生了多少次phy timer中断 */
        raise_softirq(TIMER_SOFTIRQ); /* 唤醒timer软中断来处理 */
        WRITE_SYSREG(0, CNTP_CTL_EL0); /* disable EL1 timer interrupt */
    }

两者都调用raise_softirq(TIMER_SOFTIRQ)来唤醒软中断来做进一步处理,该软中断对应的服务函数为:

static void timer_softirq_action(void)
    /* Execute ready heap timers. */
    while ( (heap_metadata(heap)->size != 0) && ((t = heap[1])->expires < now) )
    {
        remove_from_heap(heap, t);
        execute_timer(ts, t);
    }
    /* Execute ready list timers. */
    while ( ((t = ts->list) != NULL) && (t->expires < now) )
    {
        ts->list = t->list_next;
        execute_timer(ts, t);
    }
	......
    if ( !reprogram_timer(this_cpu(timer_deadline)) ) /* 该函数会重新enable EL2定时器中断 */
        raise_softirq(TIMER_SOFTIRQ);        
static void execute_timer(struct timers *ts, struct timer *t)
    void (*fn)(void *) = t->function;
    void *data = t->data;

    t->status = TIMER_STATUS_inactive;
    list_add(&t->inactive, &ts->inactive); /* 处理完该timer后,重新插入inactive链表里面 */

    ts->running = t;
    spin_unlock_irq(&ts->lock);
    (*fn)(data); /* 运行init_timer设置的timer处理函数 */
    spin_lock_irq(&ts->lock);
    ts->running = NULL;

vtimer_interrupt

static void vtimer_interrupt(int irq, void *dev_id, struct cpu_user_regs *regs)
    if ( unlikely(is_idle_vcpu(current)) )
        return;

    perfc_incr(virt_timer_irqs);

    current->arch.virt_timer.ctl = READ_SYSREG(CNTV_CTL_EL0);
    WRITE_SYSREG(current->arch.virt_timer.ctl | CNTx_CTL_MASK, CNTV_CTL_EL0);
    vgic_inject_irq(current->domain, current, current->arch.virt_timer.irq, true);   

疑问

问题描述回答
1CNTFRQ_EL0由硬件自动设置,用于报告系统计数的频率?FALSE. It is the responsibility of boot software in EL3 to populate the register with the correct value
2在中断处理程序中,软件如何清除定时器中断?It can set IMASK (masking interrupts), it can clear ENABLE (disabling the timer) or update CVAL/TVAL
3当使用通用定时器生成事件流时,如何控制事件的速率?EVNTI通过选择必须更改计数中的哪个位以生成事件来控制事件的速率。EVNTDIR控制触发事件的位的转换是0到1还是1到0
4中断关联章节的中断号INITID和设备树timer节点写的中断号(10、11、13、14)有什么关系?定时器中断属于PPI中断类型,其范围是16~31,所以要加上偏移16,才是真正的物理INITID
5struct timers 结构体里的heap成员是做什么用的?
6什么时候使用virt timer,什么时候使用phy timer?
更多推荐

看板系统如何异地电脑手机访问?主机内网ip端口映射域名外网访问

看板系统是一种可视化管理系统平台,如生产管理看板、项目管理看板、APP运营看板等将企业或工厂本地项目具体数据转换成图表模式,方便实时管理和汇总,有效提升工作效率和助力生产实践。单位内部服务器部署了看板管理系统,由于无公网IP原因,无法直接让手机访问,也无法让异地外网电脑查看。这时,我们可以利用快解析端口映射方法,一步将

【藏经阁一起读】(69)__《阿里云视频云产品手册2023版》

【藏经阁一起读】(69)__《阿里云视频云产品手册2023版》目录一、概述阿里云视频云全产品矩阵二、数字人视频制作以下是人工智能虚拟人物的一些优秀代表作品及其特点:阿里云视频云数字人视频制作产品优势▶高还原度的拟真效果▶场景化智能AI联动处理▶丰富的衍生制作能力▶灵活的应用方案阿里云视频云数字人视频制作产品能力▶数字人

前端录入音频并上传

目录纯js实现(有问题)使用插件`recorder-core`(没问题)纯js实现(有问题)上传音频文件时blob数据中size一直是0,导致上传之后音频不可播放(本地录制后本地是可以播放的)<template><div><buttonv-if="!isRecording"@click="startRecording"

计算机丢失mfc140u.dll怎么办,mfc140u.dll丢失的解决方法分享

随着科技的飞速发展,计算机已经成为了人们日常生活和工作中不可或缺的工具。然而,在使用计算机的过程中,用户可能会遇到各种问题,其中计算机丢失mfc140u.dll无法运行的问题就是一个比较常见的困扰。小编将从以下几个方面对这个问题进行详细的探讨。一、mfc140u.dll文件的作用Mfc140u.dll是Microsof

linux相关知识以及有关指令3

在linux的世界中我们首先要有万物皆文件的概念,那么在系统中有那么多的文件,我们该怎么区分呢?文章目录1.文件分类2.文件的权限1).拥有者和所属组以及other2).文件的权限3).粘滞位4).对于权限修改的拓展知识点a.修改权限b.修改拥有者所属组c.权限掩码1.文件分类在linux中文件大致分为以下几类:-:普

windows安装npm教程及生成DEMO

在使用之前,先类掌握3个东西,明白它们是用来干什么的:npm:nodejs下的包管理器。webpack:它主要用途是通过CommonJS的语法把所有浏览器端需要发布的静态资源作相应的准备,比如资源的合并和打包。vue-cli:用户生成Vue工程模板。(帮你快速开始一个vue的项目,也就是给你一套vue的结构,包含基础的

这个世界,永远靠实力说话

这个世界,永远靠实力说话。与其寄望于运气或机会的降临,还不如脚踏实地,努力提升自己。因为只有强者,才能在这个充满挑战和机遇的世界里谈笑风生,创造出属于自己的成功和辉煌。就比如峰民dyfm888给人取名改名,有时候,峰民愿意给从没见过面的福主改名。有人不禁会问:“那别人远在千里,如何信任你?面都没见到,又如何相信你,给你

冯诺依曼体系结构+操作系统

目录一、冯诺依曼体系结构(一)基本结构(二)举例二、操作系统(一)概念(二)设计OS的目的(三)定位(四)内存管理、驱动管理、进程管理和文件管理1.内存管理2.驱动管理3.进程管理4.文件管理三、整体结构1.操作系统——驱动程序——底层硬件2.systemcall——操作系统3.用户——用户操作接口一、冯诺依曼体系结构

主机安全技术

主机安全1、主机分类类Unix主机Unix:Solaris,AIXLiunx:Redhat,Centos,SUSE等Windows主机Windowsserver2012,server2008等特殊主机IBMiseris,大型机等等2、主机风险操作系统风险0day漏洞,操作系统漏洞,黑客可以利用漏洞控制服务器或者发动拒绝

echarts常用参数详解汇总(饼图,柱形图,折线图)持续更新中

常用配置:X/Y轴线的基础设置《通用》细微的差距只能去官网查看了,基本一致这里只是做了个汇总方便查看xAxis/yAxis:{show:false,//不显示坐标轴线、坐标轴刻度线和坐标轴上的文字axisTick:{----------------------------------------------------

数据结构之拓扑排序

目录拓扑排序思想代码实现拓扑排序相关数据结构拓扑排序思想拓扑排序的思想是通过对有向无环图(DAG)进行排序,将图中的节点按照一定的拓扑顺序进行排列。拓扑排序的基本思想是,首先找到入度为0的节点,将其加入结果序列中,并将其从图中删除。然后,更新剩余节点的入度,继续找到新的入度为0的节点,重复上述过程,直到所有节点都被加入

热文推荐