JIFFIES——系统滴答

本文详细解析了Linux内核中的时间管理和系统滴答原理,包括mktime函数如何计算从1970年以来的秒数,RTC如何初始化以及Jiffies如何用于系统定时。此外,还深入探讨了定时器中断处理函数do_timer的工作流程。

CPU内部有一个RTC,会在上电的时候调用mktime函数算出从1970年1月1日0时开始到当前开机点所过的秒数  

long kernel_mktime (struct tm *tm)
{
  long res;
  int year;

  year = tm->tm_year - 70;	// 从70 年到现在经过的年数(2 位表示方式),
  // 因此会有2000 年问题。
  /* magic offsets (y+1) needed to get leapyears right. */
  /* 为了获得正确的闰年数,这里需要这样一个魔幻偏值(y+1) */
  res = YEAR * year + DAY * ((year + 1) / 4);	// 这些年经过的秒数时间 + 每个闰年时多1 天
  res += month[tm->tm_mon];	// 的秒数时间,在加上当年到当月时的秒数。
  /* and (y+2) here. If it wasn't a leap-year, we have to adjust */
  /* 以及(y+2)。如果(y+2)不是闰年,那么我们就必须进行调整(减去一天的秒数时间)。 */
  if (tm->tm_mon > 1 && ((year + 2) % 4))
    res -= DAY;
  res += DAY * (tm->tm_mday - 1);	// 再加上本月过去的天数的秒数时间。
  res += HOUR * tm->tm_hour;	// 再加上当天过去的小时数的秒数时间。
  res += MINUTE * tm->tm_min;	// 再加上1 小时内过去的分钟数的秒数时间。
  res += tm->tm_sec;		// 再加上1 分钟内已过的秒数。
  return res;			// 即等于从1970 年以来经过的秒数时间。
}

而给mktime函数传来的时间结构体的赋值是由初始化时从RTC(CMOS

)中读出的

// 该子程序取CMOS 时钟,并设置开机时间 startup_time(为从1970-1-1-0 时起到开机时的秒数)。
static void time_init(void)
{
	struct tm time;

	do {// 参见后面CMOS 内存列表。
		time.tm_sec = CMOS_READ(0);
		time.tm_min = CMOS_READ(2);
		time.tm_hour = CMOS_READ(4);
		time.tm_mday = CMOS_READ(7);
		time.tm_mon = CMOS_READ(8);
		time.tm_year = CMOS_READ(9);
	} while (time.tm_sec != CMOS_READ(0));
	BCD_TO_BIN(time.tm_sec);
	BCD_TO_BIN(time.tm_min);
	BCD_TO_BIN(time.tm_hour);
	BCD_TO_BIN(time.tm_mday);
	BCD_TO_BIN(time.tm_mon);
	BCD_TO_BIN(time.tm_year);
	time.tm_mon--;
	startup_time = kernel_mktime(&time);
}

可以看一下里面还有一个do while循环,是怕没有读到当前的值。

从main.c文件里进行的初始化。可以看到通过CMOS进行赋值的。这个就和硬件有关系了,咱也不懂,就当它能知道当前的时间吧。

所以这时就可以把时间存入全局变量中,会为JIFFIES所用

 

JIFFIES是一个系统的时钟滴答,一个系统滴答是10ms,也是一个定时器。

 

10ms一个系统滴答---->每隔10ms会引发一个定时器中断

 而这个中断服务函数(timer_interrupt

)中,首先进行了jiffies的自加

_timer_interrupt:
	push ds ;// save ds,es and put kernel data space
	push es ;// into them. %fs is used by _system_call
	push fs
	push edx ;// we save %eax,%ecx,%edx as gcc doesn't
	push ecx ;// save those across function calls. %ebx
	push ebx ;// is saved as we use that in ret_sys_call
	push eax
	mov eax,10h ;// ds,es 置为指向内核数据段。
	mov ds,ax
	mov es,ax
	mov eax,17h ;// fs 置为指向局部数据段(出错程序的数据段)。
	mov fs,ax
	inc dword ptr _jiffies    //jiffies自加
;// 由于初始化中断控制芯片时没有采用自动EOI,所以这里需要发指令结束该硬件中断。
	mov al,20h ;// EOI to interrupt controller ;//1
	out 20h,al ;// 操作命令字OCW2 送0x20 端口。
;// 下面3 句从选择符中取出当前特权级别(0 或3)并压入堆栈,作为do_timer 的参数。
	mov eax,dword ptr [R_CS+esp]
	and eax,3 ;// %eax is CPL (0 or 3, 0=supervisor)
	push eax
;// do_timer(CPL)执行任务切换、计时等工作,在kernel/shched.c,305 行实现。
	call _do_timer ;// 'do_timer(long CPL)' does everything from
	add esp,4 ;// task switching to accounting ...
	jmp ret_from_sys_call

就是中断程序的正常流程,自加之后会调用一个do_timer函数

// 参数cpl 是当前特权级0 或3,0 表示内核代码在执行。
// 对于一个进程由于执行时间片用完时,则进行任务切换。并执行一个计时更新工作。
void do_timer (long cpl)
{
	extern int beepcount;		// 扬声器发声时间滴答数(kernel/chr_drv/console.c,697)
	extern void sysbeepstop (void);	// 关闭扬声器(kernel/chr_drv/console.c,691)

  // 如果发声计数次数到,则关闭发声。(向0x61 口发送命令,复位位0 和1。位0 控制8253
  // 计数器2 的工作,位1 控制扬声器)。
	if (beepcount)
		if (!--beepcount)
			sysbeepstop ();

  // 如果当前特权级(cpl)为0(最高,表示是内核程序在工作),则将超级用户运行时间stime 递增;
  // 如果cpl > 0,则表示是一般用户程序在工作,增加utime。
	if (cpl)
		current->utime++;
	else
		current->stime++;

// 如果有用户的定时器存在,则将链表第1 个定时器的值减1。如果已等于0,则调用相应的处理
// 程序,并将该处理程序指针置为空。然后去掉该项定时器。
	if (next_timer)
	{				// next_timer 是定时器链表的头指针(见270 行)。
		next_timer->jiffies--;
		while (next_timer && next_timer->jiffies <= 0)
		{
			void (*fn) ();	// 这里插入了一个函数指针定义!!!??

			fn = next_timer->fn;
			next_timer->fn = NULL;
			next_timer = next_timer->next;
			(fn) ();		// 调用处理函数。
		}
	}
// 如果当前软盘控制器FDC 的数字输出寄存器中马达启动位有置位的,则执行软盘定时程序(245 行)。
	if (current_DOR & 0xf0)
		do_floppy_timer ();
	if ((--current->counter) > 0)
		return;			// 如果进程运行时间还没完,则退出。
	current->counter = 0;
	if (!cpl)
		return;			// 对于超级用户程序,不依赖counter 值进行调度。
	schedule ();
}

CPL变量就是内核中用来指示被中断程序的特权,也就是0表示内核进程,3表示用户进程。

current就是指向当前进程数据结构的指针,指向task_struct

所以utime就是用户程序的运行时间,stime是内核程序的运行时间

而这个next_timer就是一个时间链表的指针,也就是个定时器链表的指针

// 定时器链表结构和定时器数组。
static struct timer_list
{
	long jiffies;			// 定时滴答数。
	void (*fn) ();		// 定时处理程序。
	struct timer_list *next;	// 下一个定时器。
}
timer_list[TIME_REQUESTS], *next_timer = NULL;

通过这个数据结构再看上面的程序代码,就可以知道,其实上面那段,就是在进行定时器中断的时候,会调用这个do_timer函数,而这个函数里面就会遍历判断这个定时器链表有没有当前定时器为0的,如果为0则证明到定的时间了,就会执行这个next_timer的fn,这个定时处理的函数。

而下面的current->counter

current上面提到了是执行进程的指针,而这个counter就是我们常听到的时间片。

如果时间片--后大于0,就直接返回,就是此进程还有时间片

CPU调度的,后面学了再写吧。

 

了解了这个系统滴答,和这个中断函数do_timer之后,就可以搞懂很多东西了,比如说异步的操作,可能是我太笨了,一开始对异步这种东西,完全没一点头绪,知道它能干什么,但是不知道它是怎么来了,后来再我看了点redis源码后,发现redis里面有一个时间事件time event这个东西,才明白异步就是通过定时器来完成的

 

而学习了这个linux内核的系统滴答后,才真正明白了这个操作是怎么完成的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值