精华内容
下载资源
问答
  • 时间子系统
    万次阅读
    2012-09-19 12:37:58

    本系列文章的前两节讨论了用于计时的时钟源:clocksource,以及内核内部时间的一些表示方法,但是对于真实的用户来说,我们感知的是真实世界的真实时间,也就是所谓的墙上时间,clocksource只能提供一个按给定频率不停递增的周期计数,如何把它和真实的墙上时间相关联?本节的内容正是要讨论这一点。

    1.  时间的种类

    内核管理着多种时间,它们分别是:

    • RTC时间
    • wall time:墙上时间
    • monotonic time
    • raw monotonic time
    • boot time:总启动时间

    RTC时间  在PC中,RTC时间又叫CMOS时间,它通常由一个专门的计时硬件来实现,软件可以读取该硬件来获得年月日、时分秒等时间信息,而在嵌入式系统中,有使用专门的RTC芯片,也有直接把RTC集成到Soc芯片中,读取Soc中的某个寄存器即可获取当前时间信息。一般来说,RTC是一种可持续计时的,也就是说,不管系统是否上电,RTC中的时间信息都不会丢失,计时会一直持续进行,硬件上通常使用一个后备电池对RTC硬件进行单独的供电。因为RTC硬件的多样性,开发者需要为每种RTC时钟硬件提供相应的驱动程序,内核和用户空间通过驱动程序访问RTC硬件来获取或设置时间信息。

    xtime  xtime和RTC时间一样,都是人们日常所使用的墙上时间,只是RTC时间的精度通常比较低,大多数情况下只能达到毫秒级别的精度,如果是使用外部的RTC芯片,访问速度也比较慢,为此,内核维护了另外一个wall time时间:xtime,取决于用于对xtime计时的clocksource,它的精度甚至可以达到纳秒级别,因为xtime实际上是一个内存中的变量,它的访问速度非常快,内核大部分时间都是使用xtime来获得当前时间信息。xtime记录的是自1970年1月1日24时到当前时刻所经历的纳秒数。

    monotonic time  该时间自系统开机后就一直单调地增加,它不像xtime可以因用户的调整时间而产生跳变,不过该时间不计算系统休眠的时间,也就是说,系统休眠时,monotoic时间不会递增。

    raw monotonic time  该时间与monotonic时间类似,也是单调递增的时间,唯一的不同是:raw monotonic time“更纯净”,他不会受到NTP时间调整的影响,它代表着系统独立时钟硬件对时间的统计。

    boot time  与monotonic时间相同,不过会累加上系统休眠的时间,它代表着系统上电后的总时间。

    时间种类精度(统计单位)访问速度累计休眠时间受NTP调整的影响
    RTCYesYes
    xtimeYesYes
    monotonicNoYes
    raw monotonicNoNo
    boot timeYesYes


    2.  struct timekeeper

    内核用timekeeper结构来组织与时间相关的数据,它的定义如下:

    struct timekeeper {
    	struct clocksource *clock;    /* Current clocksource used for timekeeping. */
    	u32	mult;    /* NTP adjusted clock multiplier */
    	int	shift;	/* The shift value of the current clocksource. */
    	cycle_t cycle_interval;	/* Number of clock cycles in one NTP interval. */
    	u64	xtime_interval;	/* Number of clock shifted nano seconds in one NTP interval. */
    	s64	xtime_remainder;	/* shifted nano seconds left over when rounding cycle_interval */
    	u32	raw_interval;	/* Raw nano seconds accumulated per NTP interval. */
    
    	u64	xtime_nsec;	/* Clock shifted nano seconds remainder not stored in xtime.tv_nsec. */
    	/* Difference between accumulated time and NTP time in ntp
    	 * shifted nano seconds. */
    	s64	ntp_error;
    	/* Shift conversion between clock shifted nano seconds and
    	 * ntp shifted nano seconds. */
    	int	ntp_error_shift;
    
    	struct timespec xtime;	/* The current time */
    
    	struct timespec wall_to_monotonic;
    	struct timespec total_sleep_time;	/* time spent in suspend */
    	struct timespec raw_time;	/* The raw monotonic time for the CLOCK_MONOTONIC_RAW posix clock. */
    
    	ktime_t offs_real;	/* Offset clock monotonic -> clock realtime */
    
    	ktime_t offs_boot;	/* Offset clock monotonic -> clock boottime */
    
    	seqlock_t lock;	/* Seqlock for all timekeeper values */
    };
    其中的xtime字段就是上面所说的墙上时间,它是一个timespec结构的变量,它记录了自1970年1月1日以来所经过的时间,因为是timespec结构,所以它的精度可以达到纳秒级,当然那要取决于系统的硬件是否支持这一精度。

    内核除了用xtime表示墙上的真实时间外,还维护了另外一个时间:monotonic time,可以把它理解为自系统启动以来所经过的时间,该时间只能单调递增,可以理解为xtime虽然正常情况下也是递增的,但是毕竟用户可以主动向前或向后调整墙上时间,从而修改xtime值。但是monotonic时间不可以往后退,系统启动后只能不断递增。奇怪的是,内核并没有直接定义一个这样的变量来记录monotonic时间,而是定义了一个变量wall_to_monotonic,记录了墙上时间和monotonic时间之间的偏移量,当需要获得monotonic时间时,把xtime和wall_to_monotonic相加即可,因为默认启动时monotonic时间为0,所以实际上wall_to_monotonic的值是一个负数,它和xtime同一时间被初始化,请参考timekeeping_init函数。

    计算monotonic时间要去除系统休眠期间花费的时间,内核用total_sleep_time记录休眠的时间,每次休眠醒来后重新累加该时间,并调整wall_to_monotonic的值,使其在系统休眠醒来后,monotonic时间不会发生跳变。因为wall_to_monotonic值被调整。所以如果想获取boot time,需要加入该变量的值:

    void get_monotonic_boottime(struct timespec *ts)
    {
            ......
    	do {
    		seq = read_seqbegin(&timekeeper.lock);
    		*ts = timekeeper.xtime;
    		tomono = timekeeper.wall_to_monotonic;
    		sleep = timekeeper.total_sleep_time;
    		nsecs = timekeeping_get_ns();
    
    	} while (read_seqretry(&timekeeper.lock, seq));
    
    	set_normalized_timespec(ts, ts->tv_sec + tomono.tv_sec + sleep.tv_sec,
    			ts->tv_nsec + tomono.tv_nsec + sleep.tv_nsec + nsecs);
    }
    
    raw_time字段用来表示真正的硬件时间,也就是上面所说的raw monotonic time,它不受时间调整的影响,monotonic时间虽然也不受settimeofday的影响,但会受到ntp调整的影响,但是raw_time不受ntp的影响,他真的就是开完机后就单调地递增。xtime、monotonic-time和raw_time可以通过用户空间的clock_gettime函数获得,对应的ID参数分别是 CLOCK_REALTIME、CLOCK_MONOTONIC、CLOCK_MONOTONIC_RAW。

    clock字段则指向了目前timekeeper所使用的时钟源,xtime,monotonic time和raw time都是基于该时钟源进行计时操作,当有新的精度更高的时钟源被注册时,通过timekeeping_notify函数,change_clocksource函数将会被调用,timekeeper.clock字段将会被更新,指向新的clocksource。

    早期的内核版本中,xtime、wall_to_monotonic、raw_time其实是定义为全局静态变量,到我目前的版本(V3.4.10),这几个变量被移入到了timekeeper结构中,现在只需维护一个timekeeper全局静态变量即可:

    static struct timekeeper timekeeper;

    3.  timekeeper的初始化

    timekeeper的初始化由timekeeping_init完成,该函数在start_kernel的初始化序列中被调用,timekeeping_init首先从RTC中获取当前时间:

    void __init timekeeping_init(void)
    {
    	struct clocksource *clock;
    	unsigned long flags;
    	struct timespec now, boot;
    
    	read_persistent_clock(&now);
    	read_boot_clock(&boot);
    
    然后对锁和ntp进行必要的初始化:

    	seqlock_init(&timekeeper.lock);
    
    	ntp_init();
    接着获取默认的clocksource,如果平台没有重新实现clocksource_default_clock函数,默认的clocksource就是基于jiffies的clocksource_jiffies,然后通过timekeeper_setup_inernals内部函数把timekeeper和clocksource进行关联:

    	write_seqlock_irqsave(&timekeeper.lock, flags);
    	clock = clocksource_default_clock();
    	if (clock->enable)
    		clock->enable(clock);
    	timekeeper_setup_internals(clock);
    利用RTC的当前时间,初始化xtime,raw_time,wall_to_monotonic等字段:

    	timekeeper.xtime.tv_sec = now.tv_sec;
    	timekeeper.xtime.tv_nsec = now.tv_nsec;
    	timekeeper.raw_time.tv_sec = 0;
    	timekeeper.raw_time.tv_nsec = 0;
    	if (boot.tv_sec == 0 && boot.tv_nsec == 0) {
    		boot.tv_sec = timekeeper.xtime.tv_sec;
    		boot.tv_nsec = timekeeper.xtime.tv_nsec;
    	}
    	set_normalized_timespec(&timekeeper.wall_to_monotonic,
    				-boot.tv_sec, -boot.tv_nsec);
    最后,初始化代表实时时间和monotonic时间之间偏移量的offs_real字段,total_sleep_time字段初始化为0:

    	update_rt_offset();
    	timekeeper.total_sleep_time.tv_sec = 0;
    	timekeeper.total_sleep_time.tv_nsec = 0;
    	write_sequnlock_irqrestore(&timekeeper.lock, flags);
    }
    xtime字段因为是保存在内存中,系统掉电后无法保存时间信息,所以每次启动时都要通过timekeeping_init从RTC中同步正确的时间信息。其中,read_persistent_clock和read_boot_clock是平台级的函数,分别用于获取RTC硬件时间和启动时的时间,不过值得注意到是,到目前为止(我的代码树基于3.4版本),ARM体系中,只有tegra和omap平台实现了read_persistent_clock函数。如果平台没有实现该函数,内核提供了一个默认的实现:

    void __attribute__((weak)) read_persistent_clock(struct timespec *ts)
    {
    	ts->tv_sec = 0;
    	ts->tv_nsec = 0;
    }
    void __attribute__((weak)) read_boot_clock(struct timespec *ts)
    {
    	ts->tv_sec = 0;
    	ts->tv_nsec = 0;
    }
    那么,其他ARM平台是如何初始化xtime的?答案就是CONFIG_RTC_HCTOSYS这个内核配置项,打开该配置后,driver/rtc/hctosys.c将会编译到系统中,由rtc_hctosys函数通过do_settimeofday在系统初始化时完成xtime变量的初始化:

    static int __init rtc_hctosys(void) 
    { 
            ...... 
            err = rtc_read_time(rtc, &tm); 
            ......
            rtc_tm_to_time(&tm, &tv.tv_sec); 
            do_settimeofday(&tv); 
            ...... 
            return err; 
    } 
    late_initcall(rtc_hctosys);

    4.  时间的更新

    xtime一旦初始化完成后,timekeeper就开始独立于RTC,利用自身关联的clocksource进行时间的更新操作,根据内核的配置项的不同,更新时间的操作发生的频度也不尽相同,如果没有配置NO_HZ选项,通常每个tick的定时中断周期,do_timer会被调用一次,相反,如果配置了NO_HZ选项,可能会在好几个tick后,do_timer才会被调用一次,当然传入的参数是本次更新离上一次更新时相隔了多少个tick周期,系统会保证在clocksource的max_idle_ns时间内调用do_timer,以防止clocksource的溢出:

    void do_timer(unsigned long ticks)
    {
    	jiffies_64 += ticks;
    	update_wall_time();
    	calc_global_load(ticks);
    }
    在do_timer中,jiffies_64变量被相应地累加,然后在update_wall_time中完成xtime等时间的更新操作,更新时间的核心操作就是读取关联clocksource的计数值,累加到xtime等字段中,其中还设计ntp时间的调整等代码,详细的代码就不贴了。

    5.  获取时间

    timekeeper提供了一系列的接口用于获取各种时间信息。

    • void getboottime(struct timespec *ts);    获取系统启动时刻的实时时间
    • void get_monotonic_boottime(struct timespec *ts);     获取系统启动以来所经过的时间,包含休眠时间
    • ktime_t ktime_get_boottime(void);   获取系统启动以来所经过的c时间,包含休眠时间,返回ktime类型
    • ktime_t ktime_get(void);    获取系统启动以来所经过的c时间,不包含休眠时间,返回ktime类型
    • void ktime_get_ts(struct timespec *ts) ;   获取系统启动以来所经过的c时间,不包含休眠时间,返回timespec结构
    • unsigned long get_seconds(void);    返回xtime中的秒计数值
    • struct timespec current_kernel_time(void);    返回内核最后一次更新的xtime时间,不累计最后一次更新至今clocksource的计数值
    • void getnstimeofday(struct timespec *ts);    获取当前时间,返回timespec结构
    • void do_gettimeofday(struct timeval *tv);    获取当前时间,返回timeval结构


    更多相关内容
  • 而随着内核的不断演进,大牛们已经对这种低分辨率定时器的精度不再满足,而且,硬件也在不断地发展,系统中的定时器硬件的精度也越来越高,这也给高分辨率定时器的出现创造了条件。内核从2.6.16开始加入了高精度...

    上一篇文章,我介绍了传统的低分辨率定时器的实现原理。而随着内核的不断演进,大牛们已经对这种低分辨率定时器的精度不再满足,而且,硬件也在不断地发展,系统中的定时器硬件的精度也越来越高,这也给高分辨率定时器的出现创造了条件。内核从2.6.16开始加入了高精度定时器架构。在实现方式上,内核的高分辨率定时器的实现代码几乎没有借用低分辨率定时器的数据结构和代码,内核文档给出的解释主要有以下几点:

    • 低分辨率定时器的代码和jiffies的关系太过紧密,并且默认按32位进行设计,并且它的代码已经经过长时间的优化,目前的使用也是没有任何错误,如果硬要基于它来实现高分辨率定时器,势必会打破原有的时间轮概念,并且会引入一大堆#if--#else判断;
    • 虽然大部分时间里,时间轮可以实现O(1)时间复杂度,但是当有进位发生时,不可预测的O(N)定时器级联迁移时间,这对于低分辨率定时器来说问题不大,可是它大大地影响了定时器的精度;
    • 低分辨率定时器几乎是为“超时”而设计的,并为此对它进行了大量的优化,对于这些以“超时”未目的而使用定时器,它们大多数期望在超时到来之前获得正确的结果,然后删除定时器,精确时间并不是它们主要的目的,例如网络通信、设备IO等等。

    为此,内核为高精度定时器重新设计了一套软件架构,它可以为我们提供纳秒级的定时精度,以满足对精确时间有迫切需求的应用程序或内核驱动,例如多媒体应用,音频设备的驱动程序等等。以下的讨论用hrtimer(high resolution timer)表示高精度定时器。
    /*****************************************************************************************************/
    声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!
    /*****************************************************************************************************/

    1.  如何组织hrtimer?

    我们知道,低分辨率定时器使用5个链表数组来组织timer_list结构,形成了著名的时间轮概念,对于高分辨率定时器,我们期望组织它们的数据结构至少具备以下条件:

    • 稳定而且快速的查找能力;
    • 快速地插入和删除定时器的能力;
    • 排序功能;

    内核的开发者考察了多种数据结构,例如基数树、哈希表等等,最终他们选择了红黑树(rbtree)来组织hrtimer,红黑树已经以库的形式存在于内核中,并被成功地使用在内存管理子系统和文件系统中,随着系统的运行,hrtimer不停地被创建和销毁,新的hrtimer按顺序被插入到红黑树中,树的最左边的节点就是最快到期的定时器,内核用一个hrtimer结构来表示一个高精度定时器:

    struct hrtimer {
    	struct timerqueue_node		node;
    	ktime_t				_softexpires;
    	enum hrtimer_restart		(*function)(struct hrtimer *);
    	struct hrtimer_clock_base	*base;
    	unsigned long			state;
            ......
    };
    定时器的到期时间用ktime_t来表示,_softexpires字段记录了时间,定时器一旦到期,function字段指定的回调函数会被调用,该函数的返回值为一个枚举值,它决定了该hrtimer是否需要被重新激活:

    enum hrtimer_restart {
    	HRTIMER_NORESTART,	/* Timer is not restarted */
    	HRTIMER_RESTART,	/* Timer must be restarted */
    };
    state字段用于表示hrtimer当前的状态,有几下几种位组合:

    #define HRTIMER_STATE_INACTIVE	0x00  // 定时器未激活
    #define HRTIMER_STATE_ENQUEUED	0x01  // 定时器已经被排入红黑树中
    #define HRTIMER_STATE_CALLBACK	0x02  // 定时器的回调函数正在被调用
    #define HRTIMER_STATE_MIGRATE	0x04  // 定时器正在CPU之间做迁移
    hrtimer的到期时间可以基于以下几种时间基准系统:

    enum  hrtimer_base_type {
    	HRTIMER_BASE_MONOTONIC,  // 单调递增的monotonic时间,不包含休眠时间
    	HRTIMER_BASE_REALTIME,   // 平常使用的墙上真实时间
    	HRTIMER_BASE_BOOTTIME,   // 单调递增的boottime,包含休眠时间
    	HRTIMER_MAX_CLOCK_BASES, // 用于后续数组的定义
    };
    和低分辨率定时器一样,处于效率和上锁的考虑,每个cpu单独管理属于自己的hrtimer,为此,专门定义了一个结构hrtimer_cpu_base:

    struct hrtimer_cpu_base {
            ......
    	struct hrtimer_clock_base	clock_base[HRTIMER_MAX_CLOCK_BASES];
    };
    其中,clock_base数组为每种时间基准系统都定义了一个hrtimer_clock_base结构,它的定义如下:

    struct hrtimer_clock_base {
    	struct hrtimer_cpu_base	*cpu_base;  // 指向所属cpu的hrtimer_cpu_base结构
            ......
    	struct timerqueue_head	active;     // 红黑树,包含了所有使用该时间基准系统的hrtimer
    	ktime_t			resolution; // 时间基准系统的分辨率
    	ktime_t			(*get_time)(void); // 获取该基准系统的时间函数
    	ktime_t			softirq_time;// 当用jiffies
    	ktime_t			offset;      // 
    };
    active字段是一个timerqueue_head结构,它实际上是对rbtree的进一步封装:
    struct timerqueue_node {
    	struct rb_node node;  // 红黑树的节点
    	ktime_t expires;      // 该节点代表队hrtimer的到期时间,与hrtimer结构中的_softexpires稍有不同
    };
    
    struct timerqueue_head {
    	struct rb_root head;          // 红黑树的根节点
    	struct timerqueue_node *next; // 该红黑树中最早到期的节点,也就是最左下的节点
    };
    timerqueue_head结构在红黑树的基础上,增加了一个next字段,用于保存树中最先到期的定时器节点,实际上就是树的最左下方的节点,有了next字段,当到期事件到来时,系统不必遍历整个红黑树,只要取出next字段对应的节点进行处理即可。timerqueue_node用于表示一个hrtimer节点,它在标准红黑树节点rb_node的基础上增加了expires字段,该字段和hrtimer中的_softexpires字段一起,设定了hrtimer的到期时间的一个范围,hrtimer可以在hrtimer._softexpires至timerqueue_node.expires之间的任何时刻到期,我们也称timerqueue_node.expires为硬过期时间(hard),意思很明显:到了此时刻,定时器一定会到期,有了这个范围可以选择,定时器系统可以让范围接近的多个定时器在同一时刻同时到期,这种设计可以降低进程频繁地被hrtimer进行唤醒。经过以上的讨论,我们可以得出以下的图示,它表明了每个cpu上的hrtimer是如何被组织在一起的:

                                                           图 1.1  每个cpu的hrtimer组织结构

    总结一下:

    • 每个cpu有一个hrtimer_cpu_base结构;
    • hrtimer_cpu_base结构管理着3种不同的时间基准系统的hrtimer,分别是:实时时间,启动时间和单调时间;
    • 每种时间基准系统通过它的active字段(timerqueue_head结构指针),指向它们各自的红黑树;
    • 红黑树上,按到期时间进行排序,最先到期的hrtimer位于最左下的节点,并被记录在active.next字段中;
    • 3中时间基准的最先到期时间可能不同,所以,它们之中最先到期的时间被记录在hrtimer_cpu_base的expires_next字段中。

    2.  hrtimer如何运转

    hrtimer的实现需要一定的硬件基础,它的实现依赖于我们前几章介绍的timekeeper和clock_event_device,如果你对timekeeper和clock_event_device不了解请参考以下文章:Linux时间子系统之三:时间的维护者:timekeeperLinux时间子系统之四:定时器的引擎:clock_event_device。hrtimer系统需要通过timekeeper获取当前的时间,计算与到期时间的差值,并根据该差值,设定该cpu的tick_device(clock_event_device)的下一次的到期时间,时间一到,在clock_event_device的事件回调函数中处理到期的hrtimer。现在你或许有疑问:前面在介绍clock_event_device时,我们知道,每个cpu有自己的tick_device,通常用于周期性地产生进程调度和时间统计的tick事件,这里又说要用tick_device调度hrtimer系统,通常cpu只有一个tick_device,那他们如何协调工作?这个问题也一度困扰着我,如果再加上NO_HZ配置带来tickless特性,你可能会更晕。这里我们先把这个疑问放下,我将在后面的章节中来讨论这个问题,现在我们只要先知道,一旦开启了hrtimer,tick_device所关联的clock_event_device的事件回调函数会被修改为:hrtimer_interrupt,并且会被设置成工作于CLOCK_EVT_MODE_ONESHOT单触发模式。

    2.1  添加一个hrtimer

    要添加一个hrtimer,系统提供了一些api供我们使用,首先我们需要定义一个hrtimer结构的实例,然后用hrtimer_init函数对它进行初始化,它的原型如下:

    void hrtimer_init(struct hrtimer *timer, clockid_t which_clock,
    			 enum hrtimer_mode mode);
    which_clock可以是CLOCK_REALTIME、CLOCK_MONOTONIC、CLOCK_BOOTTIME中的一种,mode则可以是相对时间HRTIMER_MODE_REL,也可以是绝对时间HRTIMER_MODE_ABS。设定回调函数:
    timer.function = hr_callback;

    如果定时器无需指定一个到期范围,可以在设定回调函数后直接使用hrtimer_start激活该定时器:

    int hrtimer_start(struct hrtimer *timer, ktime_t tim,
    			 const enum hrtimer_mode mode);
    如果需要指定到期范围,则可以使用hrtimer_start_range_ns激活定时器:

    hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim,
    			unsigned long range_ns, const enum hrtimer_mode mode);
    要取消一个hrtimer,使用hrtimer_cancel:

    int hrtimer_cancel(struct hrtimer *timer);
    以下两个函数用于推后定时器的到期时间:

    extern u64
    hrtimer_forward(struct hrtimer *timer, ktime_t now, ktime_t interval);
    
    /* Forward a hrtimer so it expires after the hrtimer's current now */
    static inline u64 hrtimer_forward_now(struct hrtimer *timer,
    				      ktime_t interval)
    {
    	return hrtimer_forward(timer, timer->base->get_time(), interval);
    }
    以下几个函数用于获取定时器的当前状态:

    static inline int hrtimer_active(const struct hrtimer *timer)
    {
    	return timer->state != HRTIMER_STATE_INACTIVE;
    }
    
    static inline int hrtimer_is_queued(struct hrtimer *timer)
    {
    	return timer->state & HRTIMER_STATE_ENQUEUED;
    }
    
    static inline int hrtimer_callback_running(struct hrtimer *timer)
    {
    	return timer->state & HRTIMER_STATE_CALLBACK;
    }
    hrtimer_init最终会进入__hrtimer_init函数,该函数的主要目的是初始化hrtimer的base字段,同时初始化作为红黑树的节点的node字段:

    static void __hrtimer_init(struct hrtimer *timer, clockid_t clock_id,
    			   enum hrtimer_mode mode)
    {
    	struct hrtimer_cpu_base *cpu_base;
    	int base;
    
    	memset(timer, 0, sizeof(struct hrtimer));
    
    	cpu_base = &__raw_get_cpu_var(hrtimer_bases);
    
    	if (clock_id == CLOCK_REALTIME && mode != HRTIMER_MODE_ABS)
    		clock_id = CLOCK_MONOTONIC;
    
    	base = hrtimer_clockid_to_base(clock_id);
    	timer->base = &cpu_base->clock_base[base];
    	timerqueue_init(&timer->node);
            ......
    }

    hrtimer_start和hrtimer_start_range_ns最终会把实际的工作交由__hrtimer_start_range_ns来完成:

    int __hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim,
    		unsigned long delta_ns, const enum hrtimer_mode mode,
    		int wakeup)
    {
            ......        
            /* 取得hrtimer_clock_base指针 */
            base = lock_hrtimer_base(timer, &flags); 
            /* 如果已经在红黑树中,先移除它: */
            ret = remove_hrtimer(timer, base); ......
            /* 如果是相对时间,则需要加上当前时间,因为内部是使用绝对时间 */
            if (mode & HRTIMER_MODE_REL) {
                    tim = ktime_add_safe(tim, new_base->get_time());
                    ......
            } 
            /* 设置到期的时间范围 */
            hrtimer_set_expires_range_ns(timer, tim, delta_ns);
            ...... 
            /* 把hrtime按到期时间排序,加入到对应时间基准系统的红黑树中 */
            /* 如果该定时器的是最早到期的,将会返回true */
            leftmost = enqueue_hrtimer(timer, new_base);
            /* 
            * Only allow reprogramming if the new base is on this CPU. 
            * (it might still be on another CPU if the timer was pending) 
            * 
            * XXX send_remote_softirq() ?
            * 定时器比之前的到期时间要早,所以需要重新对tick_device进行编程,重新设定的的到期时间
            */
            if (leftmost && new_base->cpu_base == &__get_cpu_var(hrtimer_bases))
                    hrtimer_enqueue_reprogram(timer, new_base, wakeup);
            unlock_hrtimer_base(timer, &flags);
            return ret;
    }
    

    2.2  hrtimer的到期处理

    高精度定时器系统有3个入口可以对到期定时器进行处理,它们分别是:

    • 没有切换到高精度模式时,在每个jiffie的tick事件中断中进行查询和处理;
    • 在HRTIMER_SOFTIRQ软中断中进行查询和处理;
    • 切换到高精度模式后,在每个clock_event_device的到期事件中断中进行查询和处理;

    低精度模式  因为系统并不是一开始就会支持高精度模式,而是在系统启动后的某个阶段,等待所有的条件都满足后,才会切换到高精度模式,当系统还没有切换到高精度模式时,所有的高精度定时器运行在低精度模式下,在每个jiffie的tick事件中断中进行到期定时器的查询和处理,显然这时候的精度和低分辨率定时器是一样的(HZ级别)。低精度模式下,每个tick事件中断中,hrtimer_run_queues函数会被调用,由它完成定时器的到期处理。hrtimer_run_queues首先判断目前高精度模式是否已经启用,如果已经切换到了高精度模式,什么也不做,直接返回:

    void hrtimer_run_queues(void)
    {
    
    	if (hrtimer_hres_active())
    		return;
    
    如果hrtimer_hres_active返回false,说明目前处于低精度模式下,则继续处理,它用一个for循环遍历各个时间基准系统,查询每个hrtimer_clock_base对应红黑树的左下节点,判断它的时间是否到期,如果到期,通过__run_hrtimer函数,对到期定时器进行处理,包括:调用定时器的回调函数、从红黑树中移除该定时器、根据回调函数的返回值决定是否重新启动该定时器等等:

    	for (index = 0; index < HRTIMER_MAX_CLOCK_BASES; index++) {
    		base = &cpu_base->clock_base[index];
    		if (!timerqueue_getnext(&base->active))
    			continue;
    
    		if (gettime) {
    			hrtimer_get_softirq_time(cpu_base);
    			gettime = 0;
    		}
    
    		raw_spin_lock(&cpu_base->lock);
    
    		while ((node = timerqueue_getnext(&base->active))) {
    			struct hrtimer *timer;
    
    			timer = container_of(node, struct hrtimer, node);
    			if (base->softirq_time.tv64 <=
    					hrtimer_get_expires_tv64(timer))
    				break;
    
    			__run_hrtimer(timer, &base->softirq_time);
    		}
    		raw_spin_unlock(&cpu_base->lock);
    	}
    上面的timerqueue_getnext函数返回红黑树中的左下节点,之所以可以在while循环中使用该函数,是因为__run_hrtimer会在移除旧的左下节点时,新的左下节点会被更新到base->active->next字段中,使得循环可以继续执行,直到没有新的到期定时器为止。

    高精度模式  切换到高精度模式后,原来给cpu提供tick事件的tick_device(clock_event_device)会被高精度定时器系统接管,它的中断事件回调函数被设置为hrtimer_interrupt,红黑树中最左下的节点的定时器的到期时间被编程到该clock_event_device中,这样每次clock_event_device的中断意味着至少有一个高精度定时器到期。另外,当timekeeper系统中的时间需要修正,或者clock_event_device的到期事件时间被重新编程时,系统会发出HRTIMER_SOFTIRQ软中断,软中断的处理函数run_hrtimer_softirq最终也会调用hrtimer_interrupt函数对到期定时器进行处理,所以在这里我们只要讨论hrtimer_interrupt函数的实现即可。

    hrtimer_interrupt函数的前半部分和低精度模式下的hrtimer_run_queues函数完成相同的事情:它用一个for循环遍历各个时间基准系统,查询每个hrtimer_clock_base对应红黑树的左下节点,判断它的时间是否到期,如果到期,通过__run_hrtimer函数,对到期定时器进行处理,所以我们只讨论后半部分,在处理完所有到期定时器后,下一个到期定时器的到期时间保存在变量expires_next中,接下来的工作就是把这个到期时间编程到tick_device中:

    void hrtimer_interrupt(struct clock_event_device *dev)
    {
            ......
    	for (i = 0; i < HRTIMER_MAX_CLOCK_BASES; i++) {
                    ......
    		while ((node = timerqueue_getnext(&base->active))) {
                            ......
    			if (basenow.tv64 < hrtimer_get_softexpires_tv64(timer)) {
    				ktime_t expires;
    
    				expires = ktime_sub(hrtimer_get_expires(timer),
    						    base->offset);
    				if (expires.tv64 < expires_next.tv64)
    					expires_next = expires;
    				break;
    			}
    
    			__run_hrtimer(timer, &basenow);
    		}
    	}
    
    	/*
    	 * Store the new expiry value so the migration code can verify
    	 * against it.
    	 */
    	cpu_base->expires_next = expires_next;
    	raw_spin_unlock(&cpu_base->lock);
    
    	/* Reprogramming necessary ? */
    	if (expires_next.tv64 == KTIME_MAX ||
    	    !tick_program_event(expires_next, 0)) {
    		cpu_base->hang_detected = 0;
    		return;
    	}
    如果这时的tick_program_event返回了非0值,表示过期时间已经在当前时间的前面,这通常由以下原因造成:

    • 系统正在被调试跟踪,导致时间在走,程序不走;
    • 定时器的回调函数花了太长的时间;
    • 系统运行在虚拟机中,而虚拟机被调度导致停止运行;
    为了避免这些情况的发生,接下来系统提供3次机会,重新执行前面的循环,处理到期的定时器:

    	raw_spin_lock(&cpu_base->lock);
    	now = hrtimer_update_base(cpu_base);
    	cpu_base->nr_retries++;
    	if (++retries < 3)
    		goto retry;
    如果3次循环后还无法完成到期处理,系统不再循环,转为计算本次总循环的时间,然后把tick_device的到期时间强制设置为当前时间加上本次的总循环时间,不过推后的时间被限制在100ms以内:

    	delta = ktime_sub(now, entry_time);
    	if (delta.tv64 > cpu_base->max_hang_time.tv64)
    		cpu_base->max_hang_time = delta;
    	/*
    	 * Limit it to a sensible value as we enforce a longer
    	 * delay. Give the CPU at least 100ms to catch up.
    	 */
    	if (delta.tv64 > 100 * NSEC_PER_MSEC)
    		expires_next = ktime_add_ns(now, 100 * NSEC_PER_MSEC);
    	else
    		expires_next = ktime_add(now, delta);
    	tick_program_event(expires_next, 1);
    	printk_once(KERN_WARNING "hrtimer: interrupt took %llu ns\n",
    		    ktime_to_ns(delta));
    }

    3.  切换到高精度模式

    上面提到,尽管内核配置成支持高精度定时器,但并不是一开始就工作于高精度模式,系统在启动的开始阶段,还是按照传统的模式在运行:tick_device按HZ频率定期地产生tick事件,这时的hrtimer工作在低分辨率模式,到期事件在每个tick事件中断中由hrtimer_run_queues函数处理,同时,在低分辨率定时器(时间轮)的软件中断TIMER_SOFTIRQ中,hrtimer_run_pending会被调用,系统在这个函数中判断系统的条件是否满足切换到高精度模式,如果条件满足,则会切换至高分辨率模式,另外提一下,NO_HZ模式也是在该函数中判断并切换。

    void hrtimer_run_pending(void)
    {
    	if (hrtimer_hres_active())
    		return;
            ......
    	if (tick_check_oneshot_change(!hrtimer_is_hres_enabled()))
    		hrtimer_switch_to_hres();
    }
    因为不管系统是否工作于高精度模式,每个TIMER_SOFTIRQ期间,该函数都会被调用,所以函数一开始先用hrtimer_hres_active判断目前高精度模式是否已经激活,如果已经激活,则说明之前的调用中已经切换了工作模式,不必再次切换,直接返回。hrtimer_hres_active很简单:

    DEFINE_PER_CPU(struct hrtimer_cpu_base, hrtimer_bases) = {
            ......
    }
    
    static inline int hrtimer_hres_active(void)
    {
    	return __this_cpu_read(hrtimer_bases.hres_active);
    }
    hrtimer_run_pending函数接着通过tick_check_oneshot_change判断系统是否可以切换到高精度模式,

    int tick_check_oneshot_change(int allow_nohz)
    {
    	struct tick_sched *ts = &__get_cpu_var(tick_cpu_sched);
    
    	if (!test_and_clear_bit(0, &ts->check_clocks))
    		return 0;
    
    	if (ts->nohz_mode != NOHZ_MODE_INACTIVE)
    		return 0;
    
    	if (!timekeeping_valid_for_hres() || !tick_is_oneshot_available())
    		return 0;
    
    	if (!allow_nohz)
    		return 1;
    
    	tick_nohz_switch_to_nohz();
    	return 0;
    }
    函数的一开始先判断check_clock标志的第0位是否被置位,如果没有置位,说明系统中没有注册符合要求的时钟事件设备,函数直接返回,check_clock标志由clocksource和clock_event_device系统的notify系统置位,当系统中有更高精度的clocksource被注册和选择后,或者有更精确的支持CLOCK_EVT_MODE_ONESHOT模式的clock_event_device被注册时,通过它们的notify函数,check_clock标志的第0为会置位。

    如果tick_sched结构中的nohz_mode字段不是NOHZ_MODE_INACTIVE,表明系统已经切换到其它模式,直接返回。nohz_mode的取值有3种:

    • NOHZ_MODE_INACTIVE    // 未启用NO_HZ模式
    • NOHZ_MODE_LOWRES    // 启用NO_HZ模式,hrtimer工作于低精度模式下
    • NOHZ_MODE_HIGHRES   // 启用NO_HZ模式,hrtimer工作于高精度模式下
    接下来的timerkeeping_valid_for_hres判断timekeeper系统是否支持高精度模式,tick_is_oneshot_available判断tick_device是否支持CLOCK_EVT_MODE_ONESHOT模式。如果都满足要求,则继续往下判断。allow_nohz是函数的参数,为true表明可以切换到NOHZ_MODE_LOWRES 模式,函数将进入tick_nohz_switch_to_nohz,切换至NOHZ_MODE_LOWRES 模式,这里我们传入的allow_nohz是表达式:

    (!hrtimer_is_hres_enabled())

    所以当系统不允许高精度模式时,将会在tick_check_oneshot_change函数内,通过tick_nohz_switch_to_nohz切换至NOHZ_MODE_LOWRES 模式,如果系统允许高精度模式,传入的allow_nohz参数为false,tick_check_oneshot_change函数返回1,回到上面的hrtimer_run_pending函数,hrtimer_switch_to_hres函数将会被调用,已完成切换到NOHZ_MODE_HIGHRES高精度模式。好啦,真正的切换函数找到了,我们看一看它如何切换:

    首先,它通过hrtimer_cpu_base中的hres_active字段判断该cpu是否已经切换至高精度模式,如果是则直接返回:

    static int hrtimer_switch_to_hres(void)
    {
    	int i, cpu = smp_processor_id();
    	struct hrtimer_cpu_base *base = &per_cpu(hrtimer_bases, cpu);
    	unsigned long flags;
    
    	if (base->hres_active)
    		return 1;
    接着,通过tick_init_highres函数接管tick_device关联的clock_event_device:

    	local_irq_save(flags);
    
    	if (tick_init_highres()) {
    		local_irq_restore(flags);
    		printk(KERN_WARNING "Could not switch to high resolution "
    				    "mode on CPU %d\n", cpu);
    		return 0;
    	}
    tick_init_highres函数把tick_device切换到CLOCK_EVT_FEAT_ONESHOT模式,同时把clock_event_device的回调handler设置为hrtimer_interrupt,这样设置以后,tick_device的中断回调将由hrtimer_interrupt接管,hrtimer_interrupt在上面已经讨论过,它将完成高精度定时器的调度和到期处理。

    接着,设置hres_active标志,以表明高精度模式已经切换,然后把3个时间基准系统的resolution字段设为KTIME_HIGH_RES:

    	base->hres_active = 1;
    	for (i = 0; i < HRTIMER_MAX_CLOCK_BASES; i++)
    		base->clock_base[i].resolution = KTIME_HIGH_RES;
    最后,因为tick_device被高精度定时器接管,它将不会再提供原有的tick事件机制,所以需要由高精度定时器系统模拟一个tick事件设备,继续为系统提供tick事件能力,这个工作由tick_setup_sched_timer函数完成。因为刚刚完成切换,tick_device的到期时间并没有被正确地设置为下一个到期定时器的时间,这里使用retrigger_next_event函数,传入参数NULL,使得tick_device立刻产生到期中断,hrtimer_interrupt被调用一次,然后下一个到期的定时器的时间会编程到tick_device中,从而完成了到高精度模式的切换:
    	tick_setup_sched_timer();
    	/* "Retrigger" the interrupt to get things going */
    	retrigger_next_event(NULL);
    	local_irq_restore(flags);
    	return 1;
    }

    整个切换过程可以用下图表示:


                                                                                 图3.1  低精度模式切换至高精度模式

    4.  模拟tick事件

    根据上一节的讨论,当系统切换到高精度模式后,tick_device被高精度定时器系统接管,不再定期地产生tick事件,我们知道,到目前的版本为止(V3.4),内核还没有彻底废除jiffies机制,系统还是依赖定期到来的tick事件,供进程调度系统和时间更新等操作,大量存在的低精度定时器也仍然依赖于jiffies的计数,所以,尽管tick_device被接管,高精度定时器系统还是要想办法继续提供定期的tick事件。为了达到这一目的,内核使用了一个取巧的办法:既然高精度模式已经启用,可以定义一个hrtimer,把它的到期时间设定为一个jiffy的时间,当这个hrtimer到期时,在这个hrtimer的到期回调函数中,进行和原来的tick_device同样的操作,然后把该hrtimer的到期时间顺延一个jiffy周期,如此反复循环,完美地模拟了原有tick_device的功能。下面我们看看具体点代码是如何实现的。

    在kernel/time/tick-sched.c中,内核定义了一个per_cpu全局变量:tick_cpu_sched,从而为每个cpu提供了一个tick_sched结构, 该结构主要用于管理NO_HZ配置下的tickless处理,因为模拟tick事件与tickless有很强的相关性,所以高精度定时器系统也利用了该结构的以下字段,用于完成模拟tick事件的操作:

    struct tick_sched {
    	struct hrtimer			sched_timer;
    	unsigned long			check_clocks;
    	enum tick_nohz_mode		nohz_mode;
            ......
    };
    
    sched_timer就是要用于模拟tick事件的hrtimer,check_clock上面几节已经讨论过,用于notify系统通知hrtimer系统需要检查是否切换到高精度模式,nohz_mode则用于表示当前的工作模式。

    上一节提到,用于切换至高精度模式的函数是hrtimer_switch_to_hres,在它的最后,调用了函数tick_setup_sched_timer,该函数的作用就是设置一个用于模拟tick事件的hrtimer:

    void tick_setup_sched_timer(void)
    {
    	struct tick_sched *ts = &__get_cpu_var(tick_cpu_sched);
    	ktime_t now = ktime_get();
    
    	/*
    	 * Emulate tick processing via per-CPU hrtimers:
    	 */
    	hrtimer_init(&ts->sched_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
    	ts->sched_timer.function = tick_sched_timer;
    
    	/* Get the next period (per cpu) */
    	hrtimer_set_expires(&ts->sched_timer, tick_init_jiffy_update());
    
    	for (;;) {
    		hrtimer_forward(&ts->sched_timer, now, tick_period);
    		hrtimer_start_expires(&ts->sched_timer,
    				      HRTIMER_MODE_ABS_PINNED);
    		/* Check, if the timer was already in the past */
    		if (hrtimer_active(&ts->sched_timer))
    			break;
    		now = ktime_get();
    	}
    
    #ifdef CONFIG_NO_HZ
    	if (tick_nohz_enabled)
    		ts->nohz_mode = NOHZ_MODE_HIGHRES;
    #endif
    }
    
    该函数首先初始化该cpu所属的tick_sched结构中sched_timer字段,把该hrtimer的回调函数设置为tick_sched_timer,然后把它的到期时间设定为下一个jiffy时刻,返回前把工作模式设置为NOHZ_MODE_HIGHRES,表明是利用高精度模式实现NO_HZ。

    接着我们关注一下hrtimer的回调函数tick_sched_timer,我们知道,系统中的jiffies计数,时间更新等是全局操作,在smp系统中,只有一个cpu负责该工作,所以在tick_sched_timer的一开始,先判断当前cpu是否负责更新jiffies和时间,如果是,则执行更新操作:

    static enum hrtimer_restart tick_sched_timer(struct hrtimer *timer)
    {
            ......
    
    #ifdef CONFIG_NO_HZ
    	if (unlikely(tick_do_timer_cpu == TICK_DO_TIMER_NONE))
    		tick_do_timer_cpu = cpu;
    #endif
    
    	/* Check, if the jiffies need an update */
    	if (tick_do_timer_cpu == cpu)
    		tick_do_update_jiffies64(now);
    
    然后,利用regs指针确保当前是在中断上下文中,然后调用update_process_timer:

    	if (regs) {
                    ......
    		update_process_times(user_mode(regs));
    		......
    	}
    最后,把hrtimer的到期时间推进一个tick周期,返回HRTIMER_RESTART表明该hrtimer需要再次启动,以便产生下一个tick事件。

    	hrtimer_forward(timer, now, tick_period);
    
    	return HRTIMER_RESTART;
    }
    关于update_process_times,如果你你感兴趣,回看一下本系列关于clock_event_device的那一章: Linux时间子系统之四:定时器的引擎:clock_event_device中的第5小节,对比一下模拟tick事件的hrtimer的回调函数tick_sched_timer和切换前tick_device的回调函数 tick_handle_periodic,它们是如此地相像,实际上,它们几乎完成了一样的工作。

    展开全文
  • Linux时间子系统(一) -- 原理

    千次阅读 2017-08-19 16:34:18
    转载请标明出处floater的csdn blog,http://blog.csdn.net/flaoter1 内核概述内核2.6之前的时间子系统主要是围绕低分辨定时器和基于它实现的tick时钟周期展开的,随着嵌入式应用的发展,时间子系统引入了两个主要...

    转载请标明出处floater的csdn blog,http://blog.csdn.net/flaoter

    1 内核概述

    内核2.6之前的时间子系统主要是围绕低分辨定时器和基于它实现的tick时钟周期展开的,随着嵌入式应用的发展,时间子系统引入了两个主要功能: 高分辨率定时器, tickless系统。 1. tickless系统。为了满足嵌入式设备的低功耗需求,会停用周期性的时钟,使系统长时间地进入省电模式。 2.高分辨率定时器。为了满足多媒体的应用程序需要的非常精确的计时功能。

    原来的定时器是由固定的栅栏所框定的,而高分辨率事件在本质上可以发生在任意时刻,如下图。高分辨率时钟信号只在某些事件可能发生时才会发生。
    这里写图片描述

    下图是来自wowotech的一幅关于当前时间子系统的框架图。
    ■ 最底层是硬件和驱动层,每个cpu core都有自己的cpu local timer,此外SOC内部肯定会有一个用于全局的global counter。

    ■ 中间层是linux内核层,内核抽象出了时钟源(clocksource), 时钟事件设备(clock_event_device), tick设备(tick_device)用于时间管理。分为左右两部分,
    • 右边实现计时功能。linux内核有各种time line, 包括real time clock, monotonic clock, monotonic raw clock等。clocksource提供了一个单调增加的计时器产生tick,为timeline提供时钟源。timekeeper是内核提供时间服务的基础模块,负责选择并维护最优的clocksource。
    • 左边实现定时功能。clock event管理可产生event或是触发中断的定时器, 一般而言,每个CPU形成自己的一个小系统,也就要管理自己的clock event。tick device是基于clock event设备进行工作的,cpu管理自己的调度、进程统计等是基于tick设备的。低精度timer和高精度timer都是基于tick device生成的定时器设备,关于它们的事件和周期信号的关系在上面的图中有一个大体的介绍。

    ■ 最上层是linux应用层。基于timekeeping设备的是时间管理的库time lib,基于定时器设备的是定时管理的库timer lib。

    这里写图片描述

    2 时钟硬件介绍

    ARM在自己的design中加入了时钟模块的设计,方便SOC开发者进行设计和管理。下图是来自与Arm Architecutre Reference Manual Armv8的arm generic timer的框图。

    这里写图片描述

    ARM Generic Timer的主要特点,
    • System Counter, 就是计数器,计量real-time单调递增,全局共享。
    • Timer,就是定时器,每个Processor Element都有一个,当定时是时间结束时会触发system events。可进行增加计数和减少计数,可工作在real-time和virtual-time
    • Virtual Counter,需要virtualization extension的系统支持,virtualization并不是本节关注的内容,所以关于这方面的设计在此不进行展开。

    每一个clock cycle,System counter会加一。每个timer可以通过Counter interface获得System counter的值,并且所有timer看到的counter值都是一致的。
    此外,System counter需要工作在一个Always-powered的domain,不能进入睡眠。timer会和processor一起会进入休眠模式。

    1. System Counter

    • Width, 至少56 bits, 64-bit 读操作返回的值0扩展到64bit.
    • Frequency, 1-50MHz,低功耗时支持以更低频率计数并每次增长大于1的数,例如10MHz的counter可以在10M频率下每次加一,也可以在低功耗的50K频率下每次加200
    • Roll-over , 40 years.
    • Accuracy, 推荐24小时10s误差
    • Start-up, 0
      此外,system counter还支持event stream功能,ARM处理器会进入WFE状态等待event进行唤醒,这个唤醒操作可能来自与其他处理器或者SOC上的其它硬件block,比如system counter,它能产生event stream生成周期性的wakeup event。

    2. Timers

    ARMv8 每个processor都有如下四个timer,
    • A Non-secure EL1 physical timer.
    • A Secure EL1 physical timer.
    • A Non-secure EL2 physical timer.
    • A virtual timer.
      每个timer都有输出信号输出到GIC,可产生中断。并且由于timer都是cpu私有的,所以应该使用PPI类型的中断。

    此外关于时钟硬件的寄存器访问支持两种方式,
    • CP15协处理访问
    • memory map到IO地址空间,需要SOC设计时进行定义
    armv8中读取syscnt的函数arch_counter_get_cntvct,最终的寄存器读取是通过mrs实现的。

    arch_counter_get_cntvct
     arch_timer_reg_read_stable(cntvct_el0);
      read_sysreg(reg)
       asm volatile("mrs %0, " __stringify(r) : "=r" (__val));
    

    具体使用依赖于具体的SOC设计。

    展开全文
  • Windows下kali子系统安装教程

    千次阅读 2021-09-08 11:11:59
    注意:完整版kali子系统需要占用C盘19G空间。 微软商店无法访问 首先解决的是微软商店无法访问的问题,这里我使用UsbEAm Hosts Editor [多平台hosts修改]工具解决,原作者提供的下载链接...

    本篇教程着重说明我自己碰到的一些坑,比较新手向。

    注意:完整版kali子系统需要占用C盘19G空间。

    微软商店无法访问

    首先解决的是微软商店无法访问的问题,这里我使用UsbEAm Hosts Editor [多平台hosts修改]工具解决,原作者提供的下载链接https://www.dogfight360.com/blog/475/,使用教程作者博客也有,这里就解决了因网络问题导致的微软商店无法访问的问题。

    安装kali子系统前的准备

    首先来到:控制面板\程序\启用或关闭Windows程序,勾选Hyper-V、适用于Linux的Windows子系统、虚拟机平台,确定后重启。(勾选下图中标黄选项)

     重启后按win键直接键盘输入PowerShell以管理员身份运行

     打开后依次输入以下命令,随后输入y重启系统

    wsl --install
    
    Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux

     如果这些准备都做了后面安装完kali子系统打开后仍然报错可能是内核需要升级,这里放上微软官方安装子系统教程及报错信息处理方法链接:https://docs.microsoft.com/zh-cn/windows/wsl/install-win10

     安装kali子系统

    到微软商店下载安装kali子系统

    下载完成打开后如果没有报错就是普通的设置你的用户名密码,随后就可以进入系统了 。如这里报错请参考上面给的微软官方文档或评论区留言。

     更换kali源以及安装kali应用

    PS:这里从微软商店下载的kali的源是国外的,访问速度约等于无,所以需要更换为国内的源提高访问速度。这里的kali下载完后是没有常用的应用的,需要安装,后面提供一个命令安装完整版kali。

    这里vim是用不了的,我看有的大佬可以安装,但我这里安装不了(没换源连不上)。所以这里直接使用自带的vi编辑器编辑源文件:

    sudo vi /etc/apt/sources.list

     这里简单说明一下:这条命令打开文件后按一下键盘ESC键,此时按键盘上的上下左右键可以移动编辑器上的光标,把光标移动到原有的内容上,然后双击“d”键删除一整行,把原来的内容全删了,随后按“i”键进入编辑模式,手动输入源文件内容,输入完成后按ESC键再输入“:wq”保存并退出。(下面提供一个阿里的源,vi编辑器没有看懂操作的可以先百度下。)

    阿里源:

    deb http://mirrors.aliyun.com/kali kali-rolling main non-free contrib
    deb-src http://mirrors.aliyun.com/kali kali-rolling main non-free contrib

     源更换完成后依次执行以下命令:

    sudo apt-get update     #更新源
    sudo apt-get dist-upgrade      #更新软件
    sudo apt-get install kali-linux-large #安装完整版kali

     在安装完整版kali时需要注意后面会出现图形化界面,这里的选择为:有Yes选Yes有OK选OK 最后一个选“from inetd”。

     安装完整kali需要很长时间,请耐心等待,安装完成后一些常用应用就可以使用了。

     至此kali子系统安装完毕。

     

    展开全文
  • 优爱酷在10月份微软刚刚发布安卓子系统(WSA)之后,在第一时间即为全网朋友编辑发布并详细介绍了安卓子系统的下载安装图文和视频教程,帮大家尽可能的避坑,以便节省大家的摸着石头过河的时间,尽快享受Windows与...
  • 在前面章节的讨论中,我们一直基于一个假设:Linux中的时钟事件都是由一个周期时钟提供,不管系统中的clock_event_device是工作于周期触发模式,还是工作于单触发模式,也不管定时器系统是工作于低分辨率模式,还是...
  • Windows10下安装Linux子系统

    万次阅读 热门讨论 2019-07-09 22:45:29
    Windows10下安装Linux子系统 版本说明 版本 作者 日期 备注 0.1 ZY 2019.7.9 初稿 目录 文章目录Windows10下安装Linux子系统版本说明目录一、初衷二、资料收集三、官方安装说明1、准备2、安装所选的 ...
  • Windows 11 安卓子系统安装教程

    千次阅读 2022-01-04 15:10:43
    Windows 11 上周开始引入了对安卓子系统的支持,不过目前只对 Beta 通道用户开放,而正式版系统和 Dev 通道的用户还无法使用,好在 Windows 一向比较开放,我们可以通过「偷渡」的方式自行安装,极客之选今天就把 ...
  • Windows11发布也已经有一段时间了,但是安卓子系统目前还是仅有国外用户可以使用,那么本文将会解决国内用户如何部署安卓子系统 一、总体步骤 更改国家地区设置为:美国 更新为最新版本的Microsoft Store 安装...
  • 今年10月份微软发布了新一代的Windows系统-Windows11,这距离上一代产品Windows10发布已经有6年之久,打破了微软自Windows7之后每三年更新一代操作系统的传统。可以说这一代Win11是蓄势已久,厚积薄发的一代产品。 ...
  • Win11稳定版安装安卓子系统(WSA)

    千次阅读 2022-03-16 21:02:09
    win11稳定版安装安卓子系统(WSA)的简易方法。
  • win11 安卓子系统(WSA)安装教程

    万次阅读 多人点赞 2022-03-13 09:46:49
    目录Windows 11 安卓子系统 (Sub System for Android™) 安装问题汇总准备工作==建议用手机打开本文...前段时间,微软开放了所有美区用户的安卓子系统下载通道,但目前其他国家和地区还没有开放下载,如果你现在就想尝
  • WIN10的所有`非精简版的系统`,都已经`内置Linux内核`,可以直接打开一个PC应用一样使用`Ubuntu子系统`。这是非常Cool的体验,并且经历了一段时间的完善和摸索,现在应用起来都非常成熟。 本教程包含 - **启用linux...
  • 子系统中的采样时间 系统中的采样时间 纯离散系统 纯离散系统完全由离散模块组成,可以使用固定步长或可变步长求解器进行建模。要对离散系统进行仿真,需要仿真器在每个采样时间点执行一个仿真步。对于多速率...
  • 痛点: 在电脑上想要使用linux又想使用windows系统只能安装双系统,因为虚拟机的性能差且使用麻烦,双系统使用起来倒是也还行,ubuntu...Linux子系统 WSL(Windows Subsystem for Linux)详细介绍见官网:https://d...
  • windows10一键安装ubuntu子系统,以及安装完成后基础操作
  • Win10安装Ubuntu子系统及图形化界面详细教程

    万次阅读 多人点赞 2019-02-28 09:40:22
    2.启用子系统功能 2 安装 打开Microsoft Store,搜索Ubuntu,出现如下界面: 选择我红框框出来的两个版本,不要选第一个,那个有坑。安装速度还是挺快的。 安装完了之后会在开始菜单出现linux的bash: ...
  • win10上linux子系统的开启、升级及使用

    万次阅读 多人点赞 2018-06-16 12:24:35
    win10上linux子系统开启、升级及使用 在16年时看到网上有所win10上可以运行ubuntu子系统,就试着下载了试用了下,利用Linux子系统可以直接访问win10的各种文件,相比使用虚拟机很是方便,而且速度也是杠杠的。 ...
  • Linux内核子系统
  • 看看这么多子系统的图标:   第一个是普通子系统(非原子子系统),它的外框是细线条。 ...选择“Treat as atomic unit”后,它就变成了...选择为原子子系统后,可以配置独立的采样时间:   在生成代码时还...
  • 优爱酷在10月份微软刚刚发布安卓子系统(WSA)之后,在第一时间即为全网朋友编辑发布并详细介绍了安卓子系统的下载安装图文和视频教程,帮大家尽可能的避坑,以便节省大家的摸着石头过河的时间,尽快享受Windows与...
  • Win11正式版(21H2)安装Android子系统

    万次阅读 2021-11-11 16:48:09
    本教程的背景是Win11正式版本(21H2)暂未全面推送Android子系统的情况的尝鲜教程。 一. Win11dev版本 wsa在win11测试版本上面已经可以直接使用了,只设置区域为美国,设置->启动关闭windows功能->开启hyper-v...
  • Windows10安装Linux子系统Ubuntu

    万次阅读 多人点赞 2017-05-14 13:12:29
    相信大部分同学都跟我一样都是使用Windows...在Windows10后,我们再也不用那么麻烦了,因为可以直接在Windows上安装子系统。 首先我们要打开Windows功能 在这里把勾打上 然后打开设置》》更新和安全》》针对开发
  • Win10 系统安装 Linux 子系统(WSL)教程1. WSL 简介1.1 什么是 WSL ?1.2 WSL1 与 WSL22. 安装 WSL2.1 安装 WSL1(1)启用“适用于 Linux 的 Windows 子系统”可选功能(2)重启电脑(3)安装 Linux 发行版,如 ...
  • window10下子系统ubuntu安装Docker

    万次阅读 多人点赞 2019-07-04 19:47:30
    文章目录前言准备工作开启window子系统下载Docker for Window安装包ubuntu下安装Docker完了?可以用了?然而并没有!!!安装 Docker for Windows子进程链接宿主机Docker守护进程测试参考资料 前言 本以为这是个很...
  • 如何在Windows下部署Linux子系统

    千次阅读 2018-12-24 22:10:30
    如何在Windows下部署Linux子系统?双系统&amp;虚拟机&amp;子系统的那些事大功告成,好好享受你的Linux子系统吧~~~ 双系统&amp;虚拟机&amp;子系统的那些事 在自己的笔记本中安装Windows与Linux双...
  • windows-sys15:windows11 Android 子系统安装和apk应用安装

    千次阅读 热门讨论 2021-10-23 19:04:10
    1 安装条件 必须是windows11系统,系统版本号为 22000.282 如果你的电脑满足windows11的硬件...2 安装Android 子系统 2.1 Microsoft Store下载安装 下载地址:https://www.microsoft.com/store/productId/9P3395VX91NR
  • 蒙OS开源代码精要解读之—— 系统服务框架子系统(服务启动) 作者介绍: 中科创达OpenHarmony研究组 说明: 中科创达OpenHarmony研究组第一时间对https://codechina.csdn.net/openharmony上开源的代码进行了详尽...
  • 一、开启win10子系统 [ Windows Subsystem for Linux(WSL)] 1. 启动开发者模式: 设置——>更新和安全——>开发者选项——>开发人员模式 2. 打开windows下的Linux子系统 控制面板——>程序和功能...
  • Linux内核中的pinctrl子系统应用实例

    万次阅读 多人点赞 2017-05-31 10:48:23
     由于近期在做一个项目用到了pinctrl子系统,但是对pinctrl子系统了解又不是很多,所以遇到了麻烦,但是找度娘发现很少有同行对pinctrl的具体用法做出说明,所以只能自己去搞了,在经过一段时间对Linux内核源码的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,385,424
精华内容 554,169
关键字:

时间子系统