首页 > 代码库 > 详细分析contrex-A9的汇编代码__switch_to(进程切换)

详细分析contrex-A9的汇编代码__switch_to(进程切换)

//函数原型:版本linux-3.0.8

struct task_struct *__switch_to(structtask_struct *,struct thread_info *, struct thread_info *);

 

#define switch_to(prev,next,last)                                       \

do {                                                                   \

        last =__switch_to(prev,task_thread_info(prev), task_thread_info(next));        \

} while (0)

//首先我们看一下下面的宏:

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE*)0)->MEMBER)

//下面其实就是指向对应的struct成员

/*

CC_STACKPROTECT补丁是Tejun Heo在年给主线kernel提交的一个用来防止内核堆栈溢出的补丁。

默认的config是将这个选项关闭的,可以在编译内核的时候,修改.config文件为CONFIG_CC_STACKPROTECTOR=y

来启用。未来飞天内核可以将这个选项开启来防止利用内核stack溢出的day攻击。这个补丁的防

溢出原理是:在进程启动的时候,在每个buffer的后面放置一个预先设置好的stack canary,你

可以把它理解成一个哨兵,当buffer发生缓冲区溢出的时候,肯定会破坏stack canary的值,当

stack canary的值被破坏的时候,内核就会直接当机。那么是怎么判断stack canary被覆盖了呢?

其实这个事情是gcc来做的,内核在编译的时候给gcc加了个-fstack-protector参数.

*/

DEFINE(TSK_STACK_CANARY,     offsetof(struct task_struct,stack_canary));

//task_struct

DEFINE(TI_TASK,              offsetof(struct thread_info, task));

//

/*

 * Domain types

 */

/*

#define DOMAIN_NOACCESS 0

#define DOMAIN_CLIENT  1//是用户的域(执行程序,访问数据),以及由所述接入加以防护

                        //个别章节和页面组成域的权限。

#ifdef CONFIG_CPU_USE_DOMAINS

#define DOMAIN_MANAGER 3//控制域的行为(当前域的sections和page,以及域访问)。

#else

#define DOMAIN_MANAGER 1

#endif

*/

//对应图

//这个domain通过协处理器设置寄存器DomainAccess Control

DEFINE(TI_CPU_DOMAIN,        offsetof(struct thread_info,cpu_domain));

/*

struct cpu_context_save {

        __u32   r4;

        __u32   r5;

        __u32   r6;

        __u32   r7;

        __u32   r8;

        __u32   r9;

        __u32   sl;

        __u32   fp;

        __u32   sp;

        __u32   pc;

        __u32   extra[2];               /* Xscale ‘acc‘ register, etc */

};

*/

DEFINE(TI_CPU_SAVE,          offsetof(struct thread_info,cpu_context));

 

/*

在下面有个set_tls,对应我的平台set_tls_v6k

        .macroset_tls_v6k, tp, tmp1, tmp2

        mcr     p15, 0, \tp, c13, c0, 3         @ set TLS register

        .endm

 

tp_value就是为了设置TLS register的值

在多线程应用程序,其中一个进程共享相同的地址空间中的所有线程,还有经常出现需要维护的数据是唯一

的一个线程。TLS或线程本地存储,因为你也许可以从它的名字现在弄清楚,是用于线程抽象的概念。它是

一种快速和有效的方式来存储每个线程的本地数据。线程的本地数据的偏移量是通过TLS寄存器(H / W或S

/ W块),它指向线程各自的线程控制块访问。

之前ARM内核,甚至ARM9和ARM11核心的一些不具备这种TLS注册物理上可用。操作系统(Linux从这里开始)

需要效仿的软件。新一代的ARM内核。Cortex-AX起,确实有这TLS的寄存器可用(CP15)。

 

内核对TLS需要做的事情是能够让用户态程序(通常是nptl——一个pthread的实现)在某个时刻能够设置

线程唯一的基址值到内核的线程信息结构内。

*/

DEFINE(TI_TP_VALUE,          offsetof(struct thread_info, tp_value));

 

/*

 * These are the reasoncodes for the thread notifier.

 */

#define THREAD_NOTIFY_FLUSH    0

#define THREAD_NOTIFY_EXIT     1

#define THREAD_NOTIFY_SWITCH   2

#define THREAD_NOTIFY_COPY     3

/*
 * Register switch for ARMv3 and ARMv4 processors
 * r0 = previous task_struct, r1 = previous thread_info, r2 = next thread_info
 * previous and next are guaranteed not to be the same.
 */
ENTRY(__switch_to)
 UNWIND(.fnstart        )
 UNWIND(.cantunwind     )
        //ip就是上一个线程的thread_info里面的cpu_context的地址
        add     ip, r1, #TI_CPU_SAVE
		//r3里面存着下一个线程tp值
        ldr     r3, [r2, #TI_TP_VALUE]
 //存储r4 - sl, fp, sp, lr到thread_info->cpu_context里。分别使用arm和thumb实现
 //这就是保存现场。
 ARM(   stmia   ip!, {r4 - sl, fp, sp, lr} )    @ Store most regs on stack
 THUMB( stmia   ip!, {r4 - sl, fp}         )    @ Store most regs on stack
 THUMB( str     sp, [ip], #4               )
 THUMB( str     lr, [ip], #4               )
#ifdef CONFIG_CPU_USE_DOMAINS
        //r6存着下一个线程的DOMAIN属性
        ldr     r6, [r2, #TI_CPU_DOMAIN]
#endif
        //set_tls 上面已分析
        set_tls r3, r4, r5
#if defined(CONFIG_CC_STACKPROTECTOR) && !defined(CONFIG_SMP)
        ldr     r7, [r2, #TI_TASK]//下一个线程的task_struct
        ldr     r8, =__stack_chk_guard//r8里面是__stack_chk_guard地址
        ldr     r7, [r7, #TSK_STACK_CANARY]//到这里,r7里面是stack_canary值
#endif
#ifdef CONFIG_CPU_USE_DOMAINS
		//设置domain寄存器。
        mcr     p15, 0, r6, c3, c0, 0           @ Set domain register
#endif
		//r5里面是上一个线程的task_struct
        mov     r5, r0
		//r4就是下一个线程的thread_info里面的cpu_context的地址
        add     r4, r2, #TI_CPU_SAVE
        //r4 r5只是临时保存一下

        //下面的thread_notify_head通知链,下面例子说明
        ldr     r0, =thread_notify_head
        mov     r1, #THREAD_NOTIFY_SWITCH
        bl      atomic_notifier_call_chain
#if defined(CONFIG_CC_STACKPROTECTOR) && !defined(CONFIG_SMP)
        str     r7, [r8]//__stack_chk_guard = (next)threadinfo->task->stack_canary
#endif
 THUMB( mov     ip, r4                     )//ip指向线程的thread_info里面的cpu_context的地址
        mov     r0, r5//r0从新指向上一个线程的task_struct
        //下面对应了上面的保存现场,这里就是恢复现场。pc对应了下个进程的cpu_context->pc
		//从上面看到这个cpu_context->pc就是之前保存现场的lr,就是下个线程要执行的地方。
 ARM(   ldmia   r4, {r4 - sl, fp, sp, pc}  )    @ Load all regs saved previously
 THUMB( ldmia   ip!, {r4 - sl, fp}         )    @ Load all regs saved previously
 THUMB( ldr     sp, [ip], #4               )
 THUMB( ldr     pc, [ip]                   )
 UNWIND(.fnend          )
ENDPROC(__switch_to)

实验代码:

#include <linux/kernel.h>
#include <linux/notifier.h>
#include <linux/module.h>
#include <asm/thread_notify.h>

MODULE_LICENSE("GPL");

static int test_event(struct notifier_block *this, unsigned long event, void *ptr)
{
    printk(KERN_INFO "In Event: Event Number is %ld\n",event);

    return NOTIFY_DONE;
}

static struct notifier_block test_notifier =
{
    .notifier_call = test_event,
};

static int __init reg_notifier(void)
{
    int err = 0;
    printk(KERN_INFO "Begin to register:\n");

    err = thread_register_notifier(&test_notifier);
    if (err)
    {
        printk(KERN_ERR "register test_notifier error\n");

        goto fail1;
    }

    printk(KERN_INFO "register reboot_notifier completed\n");

    return 0;

fail1:
    return err;
}


static void __exit unreg_notifier(void)
{
    thread_unregister_notifier(&test_notifier);

    printk(KERN_INFO "Unregister finished\n");
}

module_init(reg_notifier);
module_exit(unreg_notifier);

打印:

2都是THREAD_NOTIFY_SWITCH,当然会不断的切换!