八。内核中的中断机制

来源:互联网 发布:金昌印花软件 编辑:程序博客网 时间:2024/05/17 04:21
如有什么错误,请指出,大家一起学习:

1、修改进程为实时进程,linux不支持实时进程,这里的实时不是真正的实时进程,只是进程优先级比较高
    修改进程优先级的函数:sched_setscheduler
        int sched_setscheduler(pid_t pid, int policy, const struct sched_param *param);
        pid:哪个进程,当前是 0
        policy:调度策略,SCHED_FIFO,最高优先级
        struct sched_param *param:当前策略中的优先级

    修改当前策略中的较高优先级:
        sched_get_priority_max  让进程在当前策略下获得最高权限
        sched_get_priority_min    让进程在当前策略下获得最低权限

2、中断:
    内核:统一的接口,不分平台,内核会自动根据平台选择相应的底层函数,无需个人操作硬件
    
    内核使用的是 irq,不使用VIC,保证可移植性
    内核产生 irq异常时,不往 0x18跳,而往高地址 0xffff0018 跳
        mmu ---> page table -映射-> 0xffff0018(物理地址假如是 0x54000000)
    由此可见,内核使用的是高端向量表,不使用 0x18是因为内核启动后的 0 地址是不去确定的,只能通过高端向量表经页表映射,分配到任意物理地址

    使用高端向量表的设置:
        1.设置向量表的位置
        2.打开 MMU
        3.查询到相应的页表
 
    S3C6410的中断控制器:SP890(在arm里面)
    64个中断源 ---> 中断号
    127个外部中断 ---> 0 1 32 33 53

3、中断线:
    内核响应中断 irq ---> 跳到 0xffff0018 ---> 找到中断响应函数 ---> 内核切换到 SVC 模式 --->
        运行中断处理函数 ---> 扫描中断线(查看该中断源对应的中断线上注册了哪些处理函数) ---> 依次执行处理函数

    内核响应的中断可以有多个处理函数,线上的函数一般是依次执行的
        
    每条中断线上默认有一个中断函数,向线上有多个中断函数,需要中断共享

    查看中断线和中断源的对应关系:
        文件目录:arch/arm/mach-s3c64xx/include/mach/irqs.h

        外部中断也单独分配了中断线,所以中断线的数目肯定大于中断源的数目

        IRQ_EINT(x):使用它能确保平台无关,外部中断 0组,0 ~ 27
        IRQ_EINT_GROUP(group, no):其它组的外部中断的申请

4、上下文(content)
        与环境变量相似
            PATH=/user/bin  不同的环境变量是会影响程序的执行

        上下文在内核中就是程序运行的环境

        进程上下文:代表进程执行的代码处在进程上下文
        中断上下文:代表中断执行的代码处在中断上下文
            <********- 中断上下文不能睡眠(记住) -**********>
            因为:进程队列:进程调度器找 task_struct ->     task_struct -> task_struct -> ...
                进程睡眠后可通过进程调度器再度唤醒,但是中断没有调度器这种东西,一旦睡眠就会放弃 CPU,睡眠之后就没有什么能够唤醒中断,这样会造成内核的崩溃

        进程调度器就是一个软件,运行在中断上下文中

        判断上下文情况:
            in_interrupt()
                返回非 0 代表中断上下文
                返回 0 代表进程上下文
        
        time ls 命令
        real    xxx        下面的两个加上睡眠时间
        user    xxx        用户态执行的时间
        sys        xxx        命令在内核中执行的时间

5、函数:
    中断函数必须包含的头文件 <linux/interrupt.h>

    在linux下这些函数是跨硬件平台的
    request_irq():
        static inline int __must_check request_irq(unsigned int irq, \
                                        irq_handler_t handler, unsigned long flags, \
                                                        const char *name, void *dev)
        irq:中断线
        handler:中断响应函数,必须短小精悍,处理完马上退出
            typedef irqreturn_t (*irq_handler_t)(int, void *);
                int 参数:历史遗留问题,以前作为大数组的下标使用,现在的内核已不用
                void * 参数:内核会把 void * dev 指针传给这个参数。与 proc 中的 void * 传给read、write 相似
        flags:中断响应方式:上升沿、、下降沿等,有相应的宏定义对应,在这个函数文件的上方
            IRQF_DISABLED:禁止其它一切中断
            IRQF_SAMPLE_RANDOM:将中断间隔时间假如熵池    
            IRQF_TIMER:给系统定时器用
            IRQF_SHARED:共享中断线
        name:给申请的中断线起个名字
        dev:用于共享中断线,这个参数会传给中断响应函数的 void * 参数,在删除响应函数时,用于标识哪一个
    free_irq():
        void free_irq(unsigned int, void *)
        参数1:中断线
        参数2:
            中断线不共享时,参数2没什么作用
            中断线共享时,参数2传的是中断线的名字,释放的是某个函数
            所以一般在使用的时侯,不管中断线是否共享,都传中断线的名字

    local_irq_enable():
        内核默认情况下,中断是打开的

    local_irq_disable():
        与 cpsid i 等同
        内核是不允许关闭中断的,你手动关了,内核会提示你,并且自主的打开
 
    cat /proc/interrupt  下查看已经注册的中断线的信息
        中断线    中断相应的次数        VIC中断源/外部中断        中断线的名字

    用中断保证某段代码不会被打断
     方法一:
        local_irq_disable();
        ...;
        local_irq_enable();
        但是这种方法可能会产省错误

     方法二:
        unsigned long flag;
        local_irq_save(flag);    这个函数代表两步:1.保存状态 2.禁止中断
        ...;
        local_irq_restore(flag);    恢复状态 恢复中断

6、中断共享:
        一个中断线上可以有多个中断处理函数
    
        在申请中断线(requert_irq),flags 位加上 IRQF_SHARED(共享) | IRQF_SAMPLE_RANDOM(熵池,两次中断响应的时间差,放在熵池中做以后的随机数使用)

        第一中断函数与前面无异,申请第二个以后的函数时,要有其它规则:
            irq、flags、name:必须相同
            void *dev:指针必须保证与 free_irq 中的参数2 指向的是同一个地址

7、中断下半部分:
    需要下半部分的原因:
    中断处理函数占用的时间不能太长
    中断处理函数处在中断上下文,不能阻塞(睡眠等待,区别于死等)
    中断处理函数不能打断
        与内核版本相关,2.6.28版本内核默认情况是可以被打断的,3.4.24是不可被打断的
            申请中断时,加标志(flag):IRQF_DISABLED

    在中断处理函数中应该做的:
        不允许打断的
        必须马上处理的
        跟硬件相关的
    其它的都放到下半部分

    下半部分有以下几种:
    软中断:(内核提供的一种机制,是在内核编译的时候就定死了,处在中断上下文)
        内核编译好后就不能改了,但是可以被其它中断打断(保护、打断,执行完别的后恢复)
        软中断个数有限,最多 32 个;因为永乐一个 32 位数表示每一个软中断
        在中断处理函数退出后检测有无软中断挂起,如果有,依次执行软中断处理函数
        同一个软中断允许在不同的 CPU 上同时执行
    
            内核源码 <path>/include/linux/interrupt.h(423行的枚举是内核已经使用的软中断)
                枚举里所列的是软中断号
                struct softirq_action 每一个软中断对应这么一个结构体,内核自动生成

                软中断函数源码的实现:<path>/kernel/softirq.c
                     irq_exit  在中断返回时执行

        软中断的使用:    (一般不用)
            1.枚举 <path>/include/linux/interrupt.h(423行的枚举是内核已经使用的软中断)
                在枚举里的 RCU_SOFTIRQ 之前添加 MY_SOFTIRQ
                将在 <path>/kernel/softirq.c 中的数组 char *softirq_to_name[NR_SOFTIRQS] 中添加软中断的名字
                在此文件中,将下面将会用到两个函数标号共享 EXPORT_SYMBOL(函数名)
                重新编译内核‘
            2.函数 open_softirq(枚举, 函数)
            3.位图 raise_softirq(变量是 0 ~ 31 位),功能是将相应位置 1

    tasklet:小任务机制
        基于软中断实现
        tasklet 处理函数在中断上下文,可以被打断

        <path>/include/linux/interrupt.h 中的枚举的 HI_SOFTIRQ 和 TASKLET_SOFTIRQ 对应两个链表,每个成员都对应 struct tasklet_struct 这么一个结构体
            struct tasklet_struct {
                struct tasklet_struct *next;
                 unsigned long state;
                    //初始化是 0,即没有被调度,也没有被执行;调度为 1, 执行为2;下面的有枚举标识
                    //    enum {
                                  TASKLET_STATE_SCHED,    /* Tasklet is scheduled for execution */
                                TASKLET_STATE_RUN   /* Tasklet is running (SMP only) */
                        };

                 atomic_t count;//标识这个tasklet能否运行;
                        只有count为 0 的时候,该tasklet才能被运行
                void (*func)(unsigned long); //执行的函数指针
                unsigned long data; //
                };

        同一个 tasklet 不能同时在不用的CPU上执行
        同一个 tasklet 不能被重复调用

        使用:
            1.创建 struct tasklet_struct
                利用 tasklet_init()     初始化一下
            2.放进链表
                tasklet_schedule 加了判断,防止链表被打断,加上检查是否被调度过
                    基于这个函数 __tasklet_schedule
                tasklet_hi_schedule
                    基于这个函数 __tasklet_hi_schedule
            
            tasklet_kill
                拿掉被调度的 tasklet
                以睡眠的方式等待一个 tasklet 结束,结束了才会被拿走

    workqueue
        核心头文件  <include/linux/workqueue.h>
        基于内核线程实现的,在默认情况下每一个 cpu 核linux都会创建一个工作队列,每一个工作队列对应一个内线成,内核线程会定时运行
        处理函数处于进程上下文,可以睡眠
        一个内核线程对应一个工作队列
    
        同一个工作不能重复加入工作队列,只有在工作结束后才可以再添加

        两种工作(任务):
        任务结构体:
            struct work_struct {
                 atomic_long_t data;
                struct list_head entry;
                work_func_t func;
            #ifdef CONFIG_LOCKDEP
                struct lockdep_map lockdep_map;
            #endif
            };
        延时任务结构体:
        struct delayed_work {
            struct work_struct work;
            struct timer_list timer;
        };
        
        定义一个工作并初始化
        DECLARE_WORK()
        DECLARE_DELAYED_WORK()

        初始化一个工作
        INIT_WORK()
        INIT_DELAYED_WORK()

        把一个工作(任务)添加到工作队列
        schedule_work()
        schedule_delayed_work()

        以睡眠的方式等待所以的工作完成
        flush_schedule_work()

        每一个工作队列都对应一个结构体, struct workqueue_struct
        自己创建一个工作队列:
            struct workqueue_struct 要通过函数创建
            创建工作队列
                create_workqueue()
            往相应的工作队列中中加入工作
                queue_work()
            向相应的工作队列中添加延时的任务
                queue_delayed_work()
            以睡眠的额方式等待队列上的所有任务完成
                flush_workqueue()
            销毁一个工作队列
                destroy_workqueue()


















原创粉丝点击