Chinaunix首页 | 论坛 | 博客

分类: LINUX

2009-11-07 09:58:00

内核内存区(小于896M)称为低内存,kmalloc返回的内存就是该区域的内存。 高于896M的内存称为高内存,只可使用特定的映射来访问。
引导阶段内核将计算并显示这些内存区域的总页数。

内核模式与用户模式
内核模式代码对整个处理器指令集、内存和I/O空间可以无限制访问。如果用户模式下处理需要这些权限,它只得通过设备驱动或系统调用下的内核模式代码。用户模式允许页错误,但内核模式是不允许的。
在2.4以及更早版本内核中仅仅用户模式进程可以进行上下文切换出去并且可被其他进程取代。内核模式独占处理器直到:它自愿放弃CPU 或中断/异常发生。 随着2.6内核的抢占特性的引入,大多内核模式代码也可以进行上下文切换了。

进程上下文与中断上下文
运行在进程上下文中的内核代码是可抢占的。中断上下文却一直运行到结束是不可抢占的。运行在中断上下文中的代码不能做如下事情:
1、休眠或放弃处理器
2、得到信号量(mutex)
3、执行耗时任务
4、访问用户空间虚拟内存

内核定时器
内核的很多工作都是紧密依赖于时间的流逝。内核通过硬件提供的各种不同定时器提供定时器服务,如忙等待和休眠等待。处理器在忙等待时浪费周期而休眠等待时放弃CPU。

HZ和Jiffies
系统定时器以一个可编程频率来中断处理器。这种频率或者说每秒的滴答次数包含在内核的变量HZ中。选取一个HZ值是需要均衡考虑的,大HZ引起更优良的定时粒度和更好的调度方法,但也导致了更大的负载和更高的功耗。
HZ的值是架构相关的。 X86上2.4内核的HZ默认设置为100,2.6内核中则为1000,但在2.6.13中又降低为250. 在ARM平台上2.6内核中HZ为100. 2.6.21内核引入了无滴答内核的支持(CONFIG_NO_HZ),它可以动态触发依赖于系统负载的定时中断。

jiffiers持有自系统引导之后的系统定时经过的时间。 32比特的jiffies(假设HZ=1000)在大约50天后会溢出。由于系统时间可以为那个时长的很多倍,内核也提供了jiffies_64变量来持有64比特的jiffies。32位机器上编译器需要两条指令为它赋值,所以读jiffies_64并不是原子操作。为实现这个,内核提供了get_jiffies_64()函数。 drivers/cpufreq/cpufreq_stats.c中的cpufreq_stats_update()有它的使用例子。

长延迟
一个非最佳的延迟办法是忙循环,见下列例子:

unsigned long timeout = jiffies + HZ;

while (time_before(jiffies, timeout)) continue;
更好的办法是使用休眠等待。你的代码让出处理器给其他进程然后等待延迟的终止,通过schedule_timeout()实现:
unsigned long timeout = jiffies + HZ;
schedule_timeout(timeout);
这种延迟保证是很低的,不管是用户空间或内核空间,都很难精确控制超时时间因为内核调度器在每次定时滴答中会更新时间片。另外,尽管你的进程被调度为在超时后执行,但调度器也可以基于优先级决定让运行队列中另外一个进程先执行。
另外两个睡眠等待工具是wait_event_timeout()和msleep(), 两种都是借助schedule_timeout()实现。wait_event_timeout()用来当指定条件为真或超时发生时你想恢复执行。msleep()可以休眠指定毫秒数。
这些长延迟技巧仅仅在进程上下文中很适用。休眠等待不能在中断上下文中实现因为中断处理函数不允许被调度或休眠。短时的忙等待在中断上下文中是可能的,但中断上下文中太长时间的忙等待却是致命的错误。带有中断禁止的长的忙等待也是致命错误。

内核提供了定时器API以在未来某一时间点上执行一个功能。你可以使用init_timer()动态定义一个定时器活用DEFINE_TIMER()来静态创建。然后产生一个带有处理函数的地址及参数的timer_list,通过add_timer()来注册它:
#include

struct timer_list my_timer;

init_timer(&my_timer);
my_timer.expire = jiffies+n*HZ;
my_timer.function = timer_func;
my_timer.data = func_parameter;

add_timer(&my_timer);

注意这是一次定时。 如果你希望周期性运行定时器func(),你需要添加前面的代码到timer_func()中以让它在下次超时时调度它自己:
static void timer_func(unsigned long func_parameter)
{
    ........
    init_timer(&my_timer);

    my_timer.expire = jiffies+n*HZ;
    my_timer.data = func_parameter;
    my_timer.function = timer_func;
    add_timer(&my_timer);
}

你也可以使用mod_timer()来改变my_timer的失效时间,del_timer()取消my_timer,timer_pending()来查看my_timer是否在某时被挂起。如果你查看kernel/timer.c,可以找到schedule_timeout()内部就是使用这些API的。

用户空间函数如clock_settime()、clock_gettime()用来在用户空间访问内核定时器服务。用户应用程序可以使用setitimer()和setitimer()来控制指定超时失效时的定时器信号传递。

短延时
从内核来看,子滴答延时有资格作为短时延时。对这些的延时轻轻通常来自进程上下文和中断上下文。因为不能使用基于滴答的方法来实现子滴答延时,所以以前讨论的休眠等待方法不能用于小的超时。唯一的解决办法是忙等待。
内核API实现了短延时mdelay()、udelay()、ndelay(),他们分别支持毫秒、微秒以及纳秒延时。他们的实际实现都是处理器相关的。
短延时的忙等待通过测算处理器执行一条指令的时间和必要的反复循环时间实现。内核执行这个测算在引导时完成然后存储值到一个变量loops_per_jiffy. 为在握手处理中得到1微秒的延时,USB主控制器驱动driver/usb/host/ehci-hcd.c调用了内部使用loops_per_jiffy的udelay():
do {
    result = ehci_read(ehci, ptr);

    if (result == done) return 0;
    udelay(1);
    usec--;
}while(usec>0);

实时时钟
RTC在非易失性存储器里面记载完整时间。 在x86 PC上, RTC寄存器规定了通过电池供电的少量CMOS存储器的位置(RTC registers constitute the top few locations of a small chunk of battery-powered CMOS memory.)。在嵌入式系统中,RTC可能位于处理器内部或者通过I2C/SPI
总线来接自外部。
可以用RTC做如下事情:
1、读并设置完整时钟,并产生时钟更新中断。
2、产生频率范围为2HZ到8192HZ的周期性中断。
3、设置闹钟。
一些应用程序需要完整时间或墙上时间的概念。因为jiffies是相对于系统引导后的时间。内核保持墙上时间到一个变量xtime中,在引导过程中xtime通过读RTC初始化为当前墙上时间。当系统停止是墙上时间写回到RTC。可以使用do_getitimeofday()来读硬件支持的高可靠的墙上时间:
#include

static struct timeval curr_time;
do_getitimeofday(&curr_time);
my_timestamp = cpu_to_le32(curr_time.tv_sec);

用户空间中有大量的函数来访问墙上时间,包括:
time() 返回日历时间或自新纪元(1970-1-1 00:00:00)以来的秒数。
localtime() 返回隔断形式的日历时间。
mktime() 和localtime()相反。
gettimeofday() 返回毫秒精度(只有你的平台支持)的日历时间。
用户空间中还有一种使用RTC的方法是通过字符设备/dev/rtc,一次只运行一个进程访问它。

阅读(1024) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~