usleep的--系统调用流程 Android4.0.1

1.由于在不同的硬件平台上经常遇到usleep不准确的问题,比如usleep(2*1000),结果sleep了10ms,是不是有点过分,测试代码如下:

[cpp]  view plain copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3.   
  4. int main(int argc,char **argv)  
  5. {  
  6.   
  7.     struct timeval oldTime, newTime;  
  8.     int iStime,i,j;  
  9.     iStime=5;  
  10.     for(i=0;i<60;i++)  
  11.     {  
  12.         for(j=0;j<10;j++)  
  13.         {  
  14.             gettimeofday( &oldTime, NULL );  
  15.             usleep( iStime * 1000 );  
  16.             gettimeofday( &newTime, NULL );  
  17.             printf("iStime:%d,actual time:%lld\n",iStime,((long long)(newTime.tv_sec*1000 + newTime.tv_usec/1000)-(long long)(oldTime.tv_sec*1000 + oldTime.tv_usec/1000)));  
  18.         }  
  19.         iStime++;  
  20.     }  
  21. }  

当然为防止出现意外,禁止测试期间设置系统时间。

 

2. 根据以前的经验,此usleep不准主要是由于Kernel中系统timer的rating值过高引起的。

 

3. 下面从源码的角度分析一下usleep的实现细节,并进一步分析其原因。以下以Android4.0.1为例进行分析。注此问题主要与Kernel有关,与glibc或bionic无关。

 

4. 首先找到usleep的源码:

[cpp]  view plain copy
  1. //位于/bionic/libc/unistd/usleep.c  
  2. #include <time.h>  
  3. #include <errno.h>  
  4.   
  5. int usleep(unsigned long usec)  
  6. {  
  7.   struct timespec ts;  
  8.   
  9.   ts.tv_sec  = usec/1000000UL;  
  10.   
  11. #ifdef __arm__  
  12.     /* avoid divisions and modulos on the ARM */  
  13.   ts.tv_nsec = (usec - ts.tv_sec*1000000UL)*1000;  
  14. #else  
  15.   ts.tv_nsec = (usec % 1000000UL) * 1000UL;  
  16. #endif  
  17.   
  18.   for (;;)  
  19.   {  
  20.     if ( nanosleep( &ts, &ts ) == 0 )  
  21.         return 0;  
  22.   
  23.     // We try again if the nanosleep failure is EINTR.  
  24.     // The other possible failures are EINVAL (which we should pass through),  
  25.     // and ENOSYS, which doesn't happen.  
  26.     if ( errno != EINTR )  
  27.         return -1;  
  28.   }  
  29. }  


它也很懒的,就调用了nanosleep,哪就看看nanasleep的源码吧! 不幸是只找到一个extern int  nanosleep(const struct timespec *, struct timespec *); 它位于/bionic/libc/include/sys/linux-unistd.h,并没有找到它的实现。其实看看Linux系统调用,早就知道它是一个系统调用,哪就分析一下是如何进行系统调用的,以前只是讲过原理,并没有实例,在此把它完成了。

 

5. 寻找系统调用函数

如果这个函数没有实现,哪肯定是不能调用的,就像MIT教授在公开课上所讲的,搞计算机的不像搞别的,做不了假,别人不管你怎么设计的,只看你实现的结果,很有道理。也证明了搞if else的人不能做弊。哪就从它的Android.mk入手吧,看看还Link了什么东东。打开libc的Android.mk发现,其中有一行

include $(LOCAL_PATH)/arch-$(TARGET_ARCH)/syscalls.mk

这就是关键所在,syscalls系统调用,不正是我们要找的吗?进入arch-arm/syscalls.mk一看,其中一大片.s,Search一下,看有没有nanosleep.s,还真有这么一行,真是大快人心:syscall_src += arch-arm/syscalls/nanosleep.S

赶紧去瞧瞧,ARM汇编水平不高,能看懂吗?先把代码贴上再说,不懂就问google.

[cpp]  view plain copy
  1. /* autogenerated by gensyscalls.py */  
  2. #include <sys/linux-syscalls.h>  
  3.   
  4.     .text  
  5.     .type nanosleep, #function  
  6.     .globl nanosleep  
  7.     .align 4  
  8.     .fnstart  
  9.   
  10. nanosleep:  
  11.     .save   {r4, r7}  
  12.     stmfd   sp!, {r4, r7}  
  13.     ldr     r7, =__NR_nanosleep  
  14.     swi     #0  
  15.     ldmfd   sp!, {r4, r7}  
  16.     movs    r0, r0  
  17.     bxpl    lr  
  18.     b       __set_syscall_errno  
  19.     .fnend  

__NR_nanosleep是个什么东东,凭直觉,肯定在sys/linux-syscalls.h中有定义。打开/libc/include/sys/linux-syscalls.h并search __NR_nanosleep, 明白了,它定义了__NR_nanosleep的值为(__NR_SYSCALL_BASE + 162),其实就是定义了其系统调用号。这就与前一文swi连接起来了。上面的代码把系统调用号传递给r7,然后触发了一个软中断,从而进入内核态执行。

 

6. 软中断处理流程

根据常识,既然是软中断,就一定有一个对应的ISR,打开/kernel/arch/arm/kernel/entry-common.S,发现其中有一个ENTRY(vector_swi),这就是我们要找的ISR,其详细代码如下:

[cpp]  view plain copy
  1.     .align  5  
  2. ENTRY(vector_swi)  
  3.     sub sp, sp, #S_FRAME_SIZE  
  4.     stmia   sp, {r0 - r12}          @ Calling r0 - r12  
  5.  ARM(   add r8, sp, #S_PC       )  
  6.  ARM(   stmdb   r8, {sp, lr}^       )   @ Calling sp, lr  
  7.  THUMB( mov r8, sp          )  
  8.  THUMB( store_user_sp_lr r8, r10, S_SP  )   @ calling sp, lr  
  9.     mrs r8, spsr            @ called from non-FIQ mode, so ok.  
  10.     str lr, [sp, #S_PC]         @ Save calling PC  
  11.     str r8, [sp, #S_PSR]        @ Save CPSR  
  12.     str r0, [sp, #S_OLD_R0]     @ Save OLD_R0  
  13.     zero_fp  
  14.   
  15.     /* 
  16.      * Get the system call number. 
  17.      */  
  18.   
  19. #if defined(CONFIG_OABI_COMPAT)  
  20.   
  21.     /* 
  22.      * If we have CONFIG_OABI_COMPAT then we need to look at the swi 
  23.      * value to determine if it is an EABI or an old ABI call. 
  24.      */  
  25. #ifdef CONFIG_ARM_THUMB  
  26.     tst r8, #PSR_T_BIT  
  27.     movne   r10, #0             @ no thumb OABI emulation  
  28.     ldreq   r10, [lr, #-4]          @ get SWI instruction  
  29. #else  
  30.     ldr r10, [lr, #-4]          @ get SWI instruction  
  31.   A710( and ip, r10, #0x0f000000        @ check for SWI     )  
  32.   A710( teq ip, #0x0f000000                     )  
  33.   A710( bne .Larm710bug                     )  
  34. #endif  
  35. #ifdef CONFIG_CPU_ENDIAN_BE8  
  36.     rev r10, r10            @ little endian instruction  
  37. #endif  
  38.   
  39. #elif defined(CONFIG_AEABI)  
  40.   
  41.     /* 
  42.      * Pure EABI user space always put syscall number into scno (r7). 
  43.      */  
  44.   A710( ldr ip, [lr, #-4]           @ get SWI instruction   )  
  45.   A710( and ip, ip, #0x0f000000     @ check for SWI     )  
  46.   A710( teq ip, #0x0f000000                     )  
  47.   A710( bne .Larm710bug                     )  
  48.   
  49. #elif defined(CONFIG_ARM_THUMB)  
  50.   
  51.     /* Legacy ABI only, possibly thumb mode. */  
  52.     tst r8, #PSR_T_BIT          @ this is SPSR from save_user_regs  
  53.     addne   scno, r7, #__NR_SYSCALL_BASE    @ put OS number in  
  54.     ldreq   scno, [lr, #-4]  
  55.   
  56. #else  
  57.   
  58.     /* Legacy ABI only. */  
  59.     ldr scno, [lr, #-4]         @ get SWI instruction  
  60.   A710( and ip, scno, #0x0f000000       @ check for SWI     )  
  61.   A710( teq ip, #0x0f000000                     )  
  62.   A710( bne .Larm710bug                     )  
  63.   
  64. #endif  
  65.   
  66. #ifdef CONFIG_ALIGNMENT_TRAP  
  67.     ldr ip, __cr_alignment  
  68.     ldr ip, [ip]  
  69.     mcr p15, 0, ip, c1, c0      @ update control register  
  70. #endif  
  71.     enable_irq  
  72.   
  73.     get_thread_info tsk  
  74.     adr tbl, sys_call_table     @ load syscall table pointer  
  75.     ldr ip, [tsk, #TI_FLAGS]        @ check for syscall tracing  
  76.   
  77. #if defined(CONFIG_OABI_COMPAT)  
  78.     /* 
  79.      * If the swi argument is zero, this is an EABI call and we do nothing. 
  80.      * 
  81.      * If this is an old ABI call, get the syscall number into scno and 
  82.      * get the old ABI syscall table address. 
  83.      */  
  84.     bics    r10, r10, #0xff000000  
  85.     eorne   scno, r10, #__NR_OABI_SYSCALL_BASE  
  86.     ldrne   tbl, =sys_oabi_call_table  
  87. #elif !defined(CONFIG_AEABI)  
  88.     bic scno, scno, #0xff000000     @ mask off SWI op-code  
  89.     eor scno, scno, #__NR_SYSCALL_BASE  @ check OS number  
  90. #endif  
  91.   
  92.     stmdb   sp!, {r4, r5}           @ push fifth and sixth args  
  93.     tst ip, #_TIF_SYSCALL_TRACE     @ are we tracing syscalls?  
  94.     bne __sys_trace  
  95.   
  96.     cmp scno, #NR_syscalls      @ check upper syscall limit  
  97.     adr lr, BSYM(ret_fast_syscall)  @ return address  
  98.     ldrcc   pc, [tbl, scno, lsl #2]     @ call sys_* routine  
  99.   
  100.     add r1, sp, #S_OFF  
  101. 2:  mov why, #0             @ no longer a real syscall  
  102.     cmp scno, #(__ARM_NR_BASE - __NR_SYSCALL_BASE)  
  103.     eor r0, scno, #__NR_SYSCALL_BASE    @ put OS number back  
  104.     bcs arm_syscall   
  105.     b   sys_ni_syscall          @ not private func  
  106. ENDPROC(vector_swi)  


7. 找与nanosleep对应的处理函数

从上面的代码中可以看出,它将调用sys_call_table中的某个函数。在同一个文件中寻找sys_call_table,其代码如下:

[cpp]  view plain copy
  1.     .type   sys_call_table, #object  
  2. ENTRY(sys_call_table)  
  3. #include "calls.S"  


看看linux/arch/arm/kernel/calls.S中的内容:

[cpp]  view plain copy
  1. /* 0 */     CALL(sys_restart_syscall)  
  2.         CALL(sys_exit)  
  3.         CALL(sys_fork_wrapper)  
  4.         CALL(sys_read)  
  5.         CALL(sys_write)  
  6.                 ...  
  7. /* 160 */   CALL(sys_sched_get_priority_min)  
  8.         CALL(sys_sched_rr_get_interval)  
  9.         CALL(sys_nanosleep)  
  10.         CALL(sys_mremap)  
  11.         CALL(sys_setresuid16)  


原来nanosleep系统调用在Kernel中的函数为sys_nanosleep,现在去分析一下是如何实现高精度的sleep的,是忙等(执行nop指令),还是闲等(让出CPU使用权)呢? 马上就会有答案了。由于小弟知识有限,没哪么简单,我找了2个小时也没有找到答案,惭愧啊!

 

8. 先看看熟悉的系统调用open吧!

也不幸运,没有sys_open这样的函数。反正知道这个东东在fs/open.c中,基本原理应该是一样的。在此文件中找到了下面这个函数:

SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode)

 

linux/syscalls.h定义如下:

asmlinkage long sys_open(const char __user *filename,int flags, int mode); (asmlinkage就是一个extern "C")

 

这兄弟俩长得太像了,再看看SYSCALL_DEFINE3的定义,看看能不能找到二者的关系。

哈哈哈哈哈哈.....,终于在linux/syscalls.h中找到答案了,SYSCALL_DEFINE3的定义如下:

[cpp]  view plain copy
  1. #define __SYSCALL_DEFINEx(x, name, ...) asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__))  
  2. #define SYSCALL_DEFINEx(x, sname, ...)  __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)  
  3. #define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)   


把SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode)还原就变成了:

asmlinkage long sys_open(const char __user *filename,int flags, int mode);是不是与要找的函数一模一样呢?终于找到如何看这个代码的方法了!

 

9. 继续找sys_nanosleep的实现代码

先看看linux/kernel/hrtimer.c中的commnets:

 *  High-resolution kernel timers
 *
 *  In contrast to the low-resolution timeout API implemented in
 *  kernel/timer.c, hrtimers provide finer resolution and accuracy
 *  depending on system configuration and capabilities.
 *
 *  These timers are currently used for:
 *   - itimers
 *   - POSIX timers
 *   - nanosleep
 *   - precise in-kernel timing

看到上面的nanosleep了吗?说明有机会找到了。

SYSCALL_DEFINE2(nanosleep, struct timespec __user *, rqtp,  struct timespec __user *, rmtp)这不就是我要找的吗? 由于这是一个宏,在SourceInsight中查找函数nanosleep是找不到的,search字符串nanosleep是可行的。其代码如下:

[cpp]  view plain copy
  1. SYSCALL_DEFINE2(nanosleep, struct timespec __user *, rqtp,  
  2.         struct timespec __user *, rmtp)  
  3. {  
  4.     struct timespec tu;  
  5.   
  6.     if (copy_from_user(&tu, rqtp, sizeof(tu)))  
  7.         return -EFAULT;  
  8.   
  9.     if (!timespec_valid(&tu))  
  10.         return -EINVAL;  
  11.   
  12.     return hrtimer_nanosleep(&tu, rmtp, HRTIMER_MODE_REL, CLOCK_MONOTONIC);  
  13. }  


hrtimer_nanosleep实现如下:

[cpp]  view plain copy
  1. long hrtimer_nanosleep(struct timespec *rqtp, struct timespec __user *rmtp,  
  2.                const enum hrtimer_mode mode, const clockid_t clockid)  
  3. {  
  4.     struct restart_block *restart;  
  5.     struct hrtimer_sleeper t;  
  6.     int ret = 0;  
  7.     unsigned long slack;  
  8.   
  9.     slack = current->timer_slack_ns;  
  10.     if (rt_task(current))  
  11.         slack = 0;  
  12.   
  13.     hrtimer_init_on_stack(&t.timer, clockid, mode);  
  14.     hrtimer_set_expires_range_ns(&t.timer, timespec_to_ktime(*rqtp), slack);  
  15.     if (do_nanosleep(&t, mode))  
  16.         goto out;  
  17.   
  18.     /* Absolute timers do not update the rmtp value and restart: */  
  19.     if (mode == HRTIMER_MODE_ABS) {  
  20.         ret = -ERESTARTNOHAND;  
  21.         goto out;  
  22.     }  
  23.   
  24.     if (rmtp) {  
  25.         ret = update_rmtp(&t.timer, rmtp);  
  26.         if (ret <= 0)  
  27.             goto out;  
  28.     }  
  29.   
  30.     restart = ¤t_thread_info()->restart_block;  
  31.     restart->fn = hrtimer_nanosleep_restart;  
  32.     restart->nanosleep.index = t.timer.base->index;  
  33.     restart->nanosleep.rmtp = rmtp;  
  34.     restart->nanosleep.expires = hrtimer_get_expires_tv64(&t.timer);  
  35.   
  36.     ret = -ERESTART_RESTARTBLOCK;  
  37. out:  
  38.     destroy_hrtimer_on_stack(&t.timer);  
  39.     return ret;  
  40. }  
[cpp]  view plain copy
  1. static int __sched do_nanosleep(struct hrtimer_sleeper *t, enum hrtimer_mode mode)  
  2. {  
  3.     hrtimer_init_sleeper(t, current);  
  4.   
  5.     do {  
  6.         set_current_state(TASK_INTERRUPTIBLE);  
  7.         hrtimer_start_expires(&t->timer, mode);  
  8.         if (!hrtimer_active(&t->timer))  
  9.             t->task = NULL;  
  10.   
  11.         if (likely(t->task))  
  12.             schedule();  
  13.   
  14.         hrtimer_cancel(&t->timer);  
  15.         mode = HRTIMER_MODE_ABS;  
  16.   
  17.     } while (t->task && !signal_pending(current));  
  18.   
  19.     __set_current_state(TASK_RUNNING);  
  20.   
  21.     return t->task == NULL;  
  22. }  



调用流程如下:

nanosleep()--> sys_nanosleep()--> hrtimer_nanosleep()--> do_nanosleep()-->hrtimer_start()--> enqueue_hrtimer() -->hrtimer_enqueue_reprogram()-->hrtimer_reprogram()-->int tick_program_event(ktime_t expires, int force)->
  (struct clock_event_device *dev = __get_cpu_var(tick_cpu_device).evtdev; 获得clock_event_device)

int tick_dev_program_event(struct clock_event_device *dev, ktime_t expires, int force)->

int clockevents_program_event(struct clock_event_device *dev, ktime_t expires,ktime_t now) ->

dev->set_next_event((unsigned long) clc, dev)<在注册的clock_event_device中提供此函数,其主要功能是设置相关寄存器,以设置此超时事件>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值