2015-12-20 10:43:00 yeswenqian 阅读数 1265
  • 嵌入式Linux文件与串口编程

    本课程介绍Linux环境下shell编程,普通文件与设备文件的编程方法,串口介绍与应用编程。 学习条件: 1.C语言编程基础 2.嵌入式Linux开发基础

    7357 人正在学习 去看看 沈寒

关于中断大家应该很熟悉,无论是操作系统的学习也好还是微机原理也好都会接触到中断。以前进行单片机单板开发的时候,会有外部中断,定时器中断以及串口中断,就是有一个事件触发(外部中断则是外部有按键触发(边缘触发和水平触发),串口中断则是串口有数据输入,定时器中断则是内部设置一个定时器,时间到了就触发),然后就去调用对应的中断处理函数。在此之前,需要保护现场,保存中断发生时的状态,比如寄存器,堆栈,各种变量等等,一旦中断程序执行完毕,程序又会回到当初中断发生的地方重新执行。

概念性的东西就不费笔墨了。
当然,linux内核的中断当然要比上面复杂的多,上面只是大致阐述下中断的概念。

具体到一个操作系统,需要对连接到计算机上的硬件设备进行有效管理,就需要硬件对操作系统做出响应(不要问我为什么不是操作系统去向硬件发出请求)。就是让硬件在需要的时候向内核发出信号,这就是中断机制。

不同的设备对应的中断不同,而每个中断都通过一个唯一的数字标志。(参见大学微机原理课程)这样操作系统就知道是谁发出的中断请求,从而给不同的中断提供对应的中断处理程序。

先看代码:

#include <linux/module.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <asm/uaccess.h>
#include <linux/poll.h>
#include <linux/semaphore.h>
#include <linux/fcntl.h>
#include <linux/interrupt.h>

MODULE_LICENSE("Dual BSD/GPL");

#define DEV_SIZE 20
#define WQ_MAJOR 230

#define DEBUG_SWITCH 1
#if DEBUG_SWITCH
    #define P_DEBUG(fmt, args...)  printk("<1>" "<kernel>[%s]"fmt,__FUNCTION__, ##args)
#else
    #define P_DEBUG(fmt, args...)  printk("<7>" "<kernel>[%s]"fmt,__FUNCTION__, ##args)
#endif

static int irq;
static char *devname;

//模块参数,允许在运行insmod加载模块的时候对下面参数赋值
module_param(irq, int, S_IRUGO);
module_param(devname, charp, S_IRUGO);

struct wq_dev{
    char kbuf[DEV_SIZE];//缓冲区
    dev_t devno;//设备号
    unsigned int major;
    struct cdev wq_cdev;
    unsigned int cur_size;//可读可写的数据量
    struct semaphore sem;//信号量
    wait_queue_head_t r_wait;//读等待队列
    wait_queue_head_t w_wait;//写等待队列
    struct fasync_struct *async_queue;//异步通知队列 
};

//struct wq_dev *wq_devp;

//异步通知机制驱动函数
static int wq_fasync(int fd, struct file *filp, int mode)
{
    struct wq_dev *dev = filp->private_data;
    return fasync_helper(fd, filp, mode, &dev->async_queue);//调用内核提供的函数
}

//中断处理程序
static irqreturn_t wq_irq_handler(int irq, void *dev)
{
    struct wq_dev mydev;
    static int count = 1;
    mydev = *(struct wq_dev*)dev;
    printk("key:%d\n",count);
    printk("ISR is working...\n");
    count++;
    return IRQ_HANDLED;
}

int wq_open(struct inode *inodep, struct file *filp)
{
    struct wq_dev *dev;
    dev = container_of(inodep->i_cdev, struct wq_dev, wq_cdev);
    filp->private_data = dev;

    //文件打开的时候,注册中断处理程序,激活给定的中断线
    if(request_irq(irq, wq_irq_handler, IRQF_SHARED, devname, dev) != 0)
    {
        printk("%s request IRQ:%d failed...\n", devname, irq);
        return -1;
    }
    printk("%s request IRQ:%d sucess...\n", devname, irq);

    printk(KERN_ALERT "open is ok!\n");
    return 0;
}

int wq_release(struct inode *inodep, struct file *filp)
{
    printk(KERN_ALERT "release is ok!\n");
    wq_fasync(-1, filp, 0);//从异步通知队列中删除该filp
    return 0;
}

static ssize_t wq_read(struct file *filp, char __user *buf, size_t count, loff_t *offset)
{
    struct wq_dev *dev = filp->private_data;

    P_DEBUG("read data...\n");

    if(down_interruptible(&dev->sem))//获取信号量
    {
        P_DEBUG("enter read down_interruptible\n");
        return -ERESTARTSYS;
    }
    P_DEBUG("read first down\n");
    while(dev->cur_size == 0){//无数据可读,进入休眠lon
        up(&dev->sem);//释放信号量,不然写进程没有机会来唤醒(没有获得锁)
        if(filp->f_flags & O_NONBLOCK)//检查是否是阻塞型I/O
            return -EAGAIN;
        P_DEBUG("%s reading:going to sleep\n", current->comm);
        if(wait_event_interruptible(dev->r_wait, dev->cur_size != 0))//休眠等待被唤醒
        {
            P_DEBUG("read wait interruptible\n");
            return -ERESTARTSYS;
        }
        P_DEBUG("wake up r_wait\n");
        if(down_interruptible(&dev->sem))//获取信号量
            return -ERESTARTSYS;
    }

    //数据已就绪
    P_DEBUG("[2]dev->cur_size is %d\n", dev->cur_size);
    if(dev->cur_size > 0)
        count = min(count, dev->cur_size);

    //从内核缓冲区赋值数据到用户空间,复制成功返回0
    if(copy_to_user(buf, dev->kbuf, count))
    {
        up(&dev->sem);
        return -EFAULT;
    }   
    dev->cur_size -= count;//可读数据量更新
    up(&dev->sem);
    wake_up_interruptible(&dev->w_wait);//唤醒写进程
    P_DEBUG("%s did read %d bytes\n", current->comm, (unsigned int)count);
    return count;
}

static ssize_t wq_write(struct file *filp,const char __user *buf,size_t count, loff_t *offset)
{
    struct wq_dev *dev = filp->private_data;
    //wait_queue_t my_wait;
    P_DEBUG("write is doing\n");    
    if(down_interruptible(&dev->sem))//获取信号量
    {
        P_DEBUG("enter write down_interruptible\n");
        return -ERESTARTSYS;
    }

    P_DEBUG("write first down\n");
    while(dev->cur_size == DEV_SIZE){//判断空间是否已满

        up(&dev->sem);//释放信号量
        if(filp->f_flags & O_NONBLOCK)
            return -EAGAIN;
        P_DEBUG("writing going to sleep\n");
        if(wait_event_interruptible(dev->w_wait, dev->cur_size < DEV_SIZE))
            return -ERESTARTSYS;

        if(down_interruptible(&dev->sem))//获取信号量
            return -ERESTARTSYS;
    }
    if(count > DEV_SIZE - dev->cur_size)
        count = DEV_SIZE - dev->cur_size;

    if(copy_from_user(dev->kbuf, buf, count))//数据复制
        return -EFAULT;
    dev->cur_size += count;//更新数据量
    P_DEBUG("write %d bytes , cur_size:[%d]\n", count, dev->cur_size);
    P_DEBUG("kbuf is [%s]\n", dev->kbuf);
    up(&dev->sem);
    wake_up_interruptible(&dev->r_wait);//唤醒读进程队列

    if(dev->async_queue)
        kill_fasync(&dev->async_queue, SIGIO, POLL_IN);//可写时发送信号

    return count;
}

static unsigned int wq_poll(struct file *filp, poll_table *wait)
{
    struct wq_dev *dev = filp->private_data;
    unsigned int mask = 0;

    if(down_interruptible(&dev->sem))//获取信号量
        return -ERESTARTSYS;
    poll_wait(filp, &dev->w_wait, wait);//添加写等待队列
    poll_wait(filp, &dev->r_wait, wait);//添加读等待队列

    if(dev->cur_size != 0)//判断是否可读取
        mask |= POLLIN | POLLRDNORM;
    if(dev->cur_size != DEV_SIZE)//判断是否可写入
        mask |= POLLOUT | POLLWRNORM;

    up(&dev->sem);//释放信号量
    return mask;
}
struct file_operations wq_fops = {
    .open = wq_open,
    .release = wq_release,
    .write = wq_write,
    .read = wq_read,
    .poll = wq_poll,
    .fasync = wq_fasync,//函数注册
};

struct wq_dev my_dev;

static int __init wq_init(void)
{
    int result = 0;
    my_dev.cur_size = 0;
    my_dev.devno = MKDEV(WQ_MAJOR, 0);
    //设备号分配
    if(WQ_MAJOR)
        result = register_chrdev_region(my_dev.devno, 1, "wqlkp");
    else
    {
        result = alloc_chrdev_region(&my_dev.devno, 0, 1, "wqlkp");
        my_dev.major = MAJOR(my_dev.devno);
    }
    if(result < 0)
        return result;

    cdev_init(&my_dev.wq_cdev, &wq_fops);//设备初始化
    my_dev.wq_cdev.owner = THIS_MODULE;
    sema_init(&my_dev.sem, 1);//信号量初始化
    init_waitqueue_head(&my_dev.r_wait);//等待队列初始化
    init_waitqueue_head(&my_dev.w_wait);

    result = cdev_add(&my_dev.wq_cdev, my_dev.devno, 1);//设备注册
    if(result < 0)
    {
        P_DEBUG("cdev_add error!\n");
        goto err;
    }
    printk(KERN_ALERT "hello kernel\n");
    return 0;

err:
    unregister_chrdev_region(my_dev.devno,1);
    return result;
}

static void __exit wq_exit(void)
{
    cdev_del(&my_dev.wq_cdev);
    unregister_chrdev_region(my_dev.devno, 1);
    free_irq(irq, &my_dev);//卸载驱动的时候,注销相应的中断处理程序,并释放中断线(其释放机制类似于C++中的引用,linux中的描述符的引用...)  
}

module_init(wq_init);
module_exit(wq_exit);

编译后,insmod wqlko.ko irq=1 devname=myirq
然后./app_read(./app_write)。
dmesg看打印信息。
cat /proc/interrupts 查看共享的中断线

尤其这里指定irq=1,是与键盘共享同一根中断线,所以一旦键盘发出中断请求,系统响应后也会执行我们的中断处理程序。

上面只是一个简单的中断处理程序样本,还没有涉及到下半部tasklet等,也不涉及到具体硬件(直接在PC下)。
主要注册中断处理程序和释放中断处理程序。

调用request_irq的正确位置应该是在设备第一次打开,硬件被告知产生中断之前;
调用free_irq的位置是最后一次关闭设备,硬件被告知不再使用中断处理器之后。

对于request_irq函数,内核维护了一个中断信号线的注册表,模块使用中断之前要先请求一个中断通道(或者中断请求IRQ),然后在使用后释放该通道。
我们这里是和其他驱动程序共享中断信号线。

同样的,我们这里依旧跟踪下linux 内核源码,不过与前面不同的是
中断处理系统在linux中的实现是非常依赖体系结构的,实现依赖于处理器,所使用的终端处理器的类型,体系结构的设计及机器本身。

设备产生中断,通过总线把电信号发送给中断控制器,如果中断是激活的(没被屏蔽),那么中断控制器就会把中断发往处理器,如果处理器没禁止该中断,处理器会停止当前的事,跳到内存中预定义的位置开始执行哪里的代码。这个预定义的位置是由内核设置的,是中断处理程序的入口处。

这里写图片描述
在内核中,中断的旅程开始于预定义入口点,这类似于系统调用通过预定义的异常句柄进入内核。对于每条中断线,处理器都会跳到对应的唯一的位置,这样内核就知道所接收中断的IRQ号了。初始入口点只是在栈中保存这个号,并存放当前寄存器的只;然后,内核调用函数do_IRQ。

先看request_irq函数:

/**其实看下面的英文注释就知道这个函数的功能了
 *  request_irq - allocate an interrupt line
 *  @irq: Interrupt line to allocate
 *  @handler: Function to be called when the IRQ occurs
 *  @irqflags: Interrupt type flags
 *  @devname: An ascii name for the claiming device
 *  @dev_id: A cookie passed back to the handler function
 *
 *  This call allocates interrupt resources and enables the
 *  interrupt line and IRQ handling. From the point this
 *  call is made your handler function may be invoked. Since
 *  your handler function must clear any interrupt the board
 *  raises, you must take care both to initialise your hardware
 *  and to set up the interrupt handler in the right order.
 *
 *  Dev_id must be globally unique. Normally the address of the
 *  device data structure is used as the cookie. Since the handler
 *  receives this value it makes sense to use it.
 *  这里写到Dev_id必须是全局唯一的变量,因为对于共享的中断线,需要一个唯一的信息
 *  来区分其上面的多个处理程序,并让free_irq()仅仅删除指定的处理程序。
 *
 *  If your interrupt is shared you must pass a non NULL dev_id
 *  as this is required when freeing the interrupt.
 *
 *  Flags:
 *
 *  IRQF_SHARED     Interrupt is shared
 *
 *  IRQF_DISABLED   Disable local interrupts while processing
 *
 *  IRQF_SAMPLE_RANDOM  The interrupt can be used for entropy
 *
 */

//FIXME - handler used to return void - whats the significance of the change?
int request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs *),
         unsigned long irq_flags, const char * devname, void *dev_id)
{
    unsigned long retval;
    struct irqaction *action;//irqaction结构体

    if (irq >= NR_IRQS || !irq_desc[irq].valid || !handler ||
        (irq_flags & IRQF_SHARED && !dev_id))
        return -EINVAL;

    action = (struct irqaction *)kmalloc(sizeof(struct irqaction), GFP_KERNEL);//新建一个irqaction结构体
    if (!action)
        return -ENOMEM;

    action->handler = handler;//中断处理程序
    action->flags = irq_flags;//标识
    cpus_clear(action->mask);
    action->name = devname;//设备名
    action->next = NULL;
    action->dev_id = dev_id;//标识设备本身

    retval = setup_irq(irq, action);//调用setup_irq注册中断处理程序

    if (retval)
        kfree(action);
    return retval;
}

/*
struct irqaction {
    irqreturn_t (*handler)(int, void *, struct pt_regs *);//中断处理程序
    unsigned long flags;//标志
    cpumask_t mask;//未使用
    const char *name;//设备名
    void *dev_id;//标识设备本身
    struct irqaction *next;//下一个元素,链表中的元素指向共享同一IRQ的硬件设备
    int irq;//IRQ线
    struct proc_dir_entry *dir;//指向与TRQn相关的/proc/irq/n目录的描述符
};
*/

//下面相继就是调用setup_irq();
struct irq_desc *desc = irq_desc + irq;//该语句就是根据中断号搜索到对应的中断描述符结构。
//下面的不追溯了,就是负责整个的注册事宜。

不管引起中断的电路种类如何,所有的I/O中断处理程序都执行四个相通的基本步骤:

在内核态堆栈中保存IRQ的值和寄存器的内容;
为正在给IRQ线服务的PIC发送一个应答,这将允许PIC进一步发出中断;
执行共享这个IRQ的所有设备的中断服务里程(ISR);
跳到ret_from_intr()的地址后终止(这部分是硬件控制的)。
ok,看do_IRQ()

/*
 * do_IRQ handles all normal device IRQ's (the special
 * SMP cross-CPU interrupts have their own specific
 * handlers).
 */
fastcall unsigned int do_IRQ(struct pt_regs *regs)
{   
    /* high bit used in ret_from_ code */
    int irq = ~regs->orig_eax;//保存IRQ值
    ……
    //这部分代码略,请参考arch/i386/kernel/Irq.c 
    //保存寄存器的内容
    ……
        __do_IRQ(irq, regs);

    irq_exit();

    return 1;
}

/**
 * __do_IRQ - original all in one highlevel IRQ handler
 * @irq:    the interrupt number
 * @regs:   pointer to a register structure
 *
 * __do_IRQ handles all normal device IRQ's (the special
 * SMP cross-CPU interrupts have their own specific
 * handlers).
 *
 * This is the original x86 implementation which is used for every
 * interrupt type.
 */
 //上面的英文注释已经大致解释了一番...如果英文好,这条路会好走很多
fastcall unsigned int __do_IRQ(unsigned int irq, struct pt_regs *regs)
{
    struct irq_desc *desc = irq_desc + irq;//找到中断描述符
    struct irqaction *action;
    unsigned int status;

    kstat_this_cpu.irqs[irq]++;
    if (CHECK_IRQ_PER_CPU(desc->status)) {//该线上有中断处理程序
        irqreturn_t action_ret;

        /*
         * No locking required for CPU-local interrupts:
         */
        if (desc->chip->ack)
            desc->chip->ack(irq);
        action_ret = handle_IRQ_event(irq, regs, desc->action);//转调用该函数
        desc->chip->end(irq);//结束中断
        return 1;
    }
    ……
}

irqreturn_t handle_IRQ_event(unsigned int irq, struct pt_regs *regs,
                 struct irqaction *action)
{
    irqreturn_t ret, retval = IRQ_NONE;
    unsigned int status = 0;

    handle_dynamic_tick(action);

    if (!(action->flags & IRQF_DISABLED))
        local_irq_enable_in_hardirq();

//下面通过循环,调用同一共享中断线上的所有中断服务程序
//这也就告诉我们上面的例程与键盘共享同一信号线,一旦键盘有中断请求,处理器响应之后,
//也会调用我们自定的中断服务程序
    do {
        ret = action->handler(irq, action->dev_id, regs);//调用我们前面注册的中断服务程序
        if (ret == IRQ_HANDLED)
            status |= action->flags;
        retval |= ret;
        action = action->next;//链表的下一个元素,也就是共享同一中断线
    } while (action);

    if (status & IRQF_SAMPLE_RANDOM)
        add_interrupt_randomness(irq);
    local_irq_disable();

    return retval;
}

中断返回以后,处理器会控制执行ret_from_intr(),返回内核执行中断的代码。
2
限于篇幅,未完,待续…

2017-04-18 22:12:39 andylauren 阅读数 812
  • 嵌入式Linux文件与串口编程

    本课程介绍Linux环境下shell编程,普通文件与设备文件的编程方法,串口介绍与应用编程。 学习条件: 1.C语言编程基础 2.嵌入式Linux开发基础

    7357 人正在学习 去看看 沈寒

软中断分析

最近工作繁忙,没有时间总结内核相关的一些东西。上次更新博客到了linux内核中断子系统。这次总结一下软中断,也就是softirq。之后还会总结一些tasklet、工作队列机制。

http://alloysystem.blog.chinaunix.net

Andy.yx.deng#gmail.com(#->@)

1. 为什么要软中断

编写驱动的时候,一个中断产生之后,内核在中断处理函数中可能需要完成很多工作。但是中断处理函数的处理是关闭了中断的。也就是说在响应中断时,系统不能再次响应外部的其它中断。这样的后果会造成有可能丢失外部中断。于是,linux内核设计出了一种架构,中断函数需要处理的任务分为两部分,一部分在中断处理函数中执行,这时系统关闭中断。另外一部分在软件中断中执行,这个时候开启中断,系统可以响应外部中断。

关于软件中断的理论各种书籍都有介绍,不多叙述。而要真正体会软件中断的作用就必须从代码的角度来分析。我们做工作时候讲求的是professional,当一个人在某个领域一无所知的时候,我们称他为小白,偶,非苹果电脑。小白的脑子里充满了各种问题。慢慢的当这些疑惑解释完之后,小白就脱白了。此时,我们对这个领域的基本框架有了解,但这和professional还有一定的差距。再加以时日,逐渐融会贯通该领域才能达到专业的境界。

2. 什么时候触发处理软件中断

说了这么多废话,赶快步入正题。初识软中断,脑子里肯定有不少的疑问,首先就是软件中断在什么地方被触发处理?这个问题的答案就是:一个硬件中断处理完成之后。下面的函数在处理完硬件中断之后推出中断处理函数,在irq_exit中会触发软件中断的处理。

asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs) 
{ 
    struct pt_regs *old_regs = set_irq_regs(regs); 
  
    irq_enter(); 
  
    /* 
     * Some hardware gives randomly wrong interrupts.  Rather 
     * than crashing, do something sensible. 
     */  
    if (irq >= NR_IRQS) 
        handle_bad_irq(irq, &bad_irq_desc); 
    else  
        generic_handle_irq(irq); 
  
    /* AT91 specific workaround */  
    irq_finish(irq); 
  
    irq_exit(); 
    set_irq_regs(old_regs); 
}
这里要注意,invoke_softirq必须满足两个条件才能被调用到,一个就是不是在硬件中断处理过程中或者在软件中断处理中,第二个就是必须有软件中断处于pending状态。第二个好理解,有软件中断产生才去处理,没有就不处理。第一个就不好理解了。
/* 
* Exit an interrupt context. Process softirqs if needed and possible:
*/  
void irq_exit(void) 
{ 
    account_system_vtime(current); 
    trace_hardirq_exit(); 
    sub_preempt_count(IRQ_EXIT_OFFSET); 
    if (!in_interrupt() && local_softirq_pending()) 
        invoke_softirq(); 
  
#ifdef CONFIG_NO_HZ 
    /* Make sure that timer wheel updates are propagated */  
    rcu_irq_exit(); 
    if (idle_cpu(smp_processor_id()) && !in_interrupt() && !need_resched()) 
        tick_nohz_stop_sched_tick(0); 
#endif  
    preempt_enable_no_resched(); 
}

linux系统的进程数据结构里,有这么一个数据结构

#define preempt_count() (current_thread_info()->preempt_count)

利用preempt_count可以表示是否处于中断处理或者软件中断处理过程中。

#define PREEMPT_MASK    (__IRQ_MASK(PREEMPT_BITS) << PREEMPT_SHIFT) 
#define SOFTIRQ_MASK    (__IRQ_MASK(SOFTIRQ_BITS) << SOFTIRQ_SHIFT) 
#define HARDIRQ_MASK    (__IRQ_MASK(HARDIRQ_BITS) << HARDIRQ_SHIFT) 
  
#define PREEMPT_OFFSET    (1UL << PREEMPT_SHIFT) 
#define SOFTIRQ_OFFSET    (1UL << SOFTIRQ_SHIFT) 
#define HARDIRQ_OFFSET    (1UL << HARDIRQ_SHIFT)
sub_preempt_count(IRQ_EXIT_OFFSET);
#define in_interrupt() (irq_count())
#define irq_count() (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK))


preempt_count823位记录中断处理和软件中断处理过程的计数。如果有计数,表示系统在硬件中断或者软件中断处理过程中。系统这么设计是为了避免软件中断在中断嵌套中被调用,并且达到在单个CPU上软件中断不能被重入的目的。对于ARM架构的CPU不存在中断嵌套中调用软件中断的问题,因为ARM架构的CPU在处理硬件中断的过程中是关闭掉中断的。只有在进入了软中断处理过程中之后才会开启硬件中断,如果在软件中断处理过程中有硬件中断嵌套,也不会再次调用软中断,because硬件中断是软件中断处理过程中再次进入的,此时preempt_count已经记录了软件中断!对于其它架构的CPU,有可能在触发调用软件中断前,也就是还在处理硬件中断的时候,就已经开启了硬件中断,可能会发生中断嵌套,在中断嵌套中是不允许调用软件中断处理的。Why?我的理解是,在发生中断嵌套的时候,表明这个时候是系统突发繁忙的时候,内核第一要务就是赶紧把中断中的事情处理完成,退出中断嵌套。避免多次嵌套,哪里有时间处理软件中断,所以把软件中断推迟到了所有中断处理完成的时候才能触发软件中断。

3. 软件中断的处理过程

之前我已经说到,软中断的一个很大的目的就是避免中断处理中,处理的操作过多而丢失中断。同时中断还需要考虑到一件事情就是中断处理过程过长就会影响系统响应时间。如果一个中断处理一秒钟,那你一定能感受到串口卡住的现象。从另外一方面说呢,我们又必须考虑中断处理的操作一定的优先度,毕竟是硬件触发的事务,关系到网络、块设备的效率问题。Linux内核就中断方面就必须考虑平衡这三个方面的问题。而下面我要分析的__do_softirq函数就恰似在这三者之间打太极,游刃有余,面面俱到!

/* 
* We restart softirq processing MAX_SOFTIRQ_RESTART times, 
* and we fall back to softirqd after that. 
* 
* This number has been established via experimentation. 
* The two things to balance is latency against fairness - 
* we want to handle softirqs as soon as possible, but they 
* should not be able to lock up the box. 
*/  
#define MAX_SOFTIRQ_RESTART 10  
  
asmlinkage void __do_softirq(void) 
{ 
    struct softirq_action *h; 
    __u32 pending; 
    int max_restart = MAX_SOFTIRQ_RESTART; 
    int cpu; 
  
    pending = local_softirq_pending(); 
    account_system_vtime(current); 
  
    __local_bh_disable((unsigned long)__builtin_return_address(0)); 
    trace_softirq_enter(); 
  
    cpu = smp_processor_id(); 
restart: 
    /* Reset the pending bitmask before enabling irqs */  
    set_softirq_pending(0); 
  
    local_irq_enable(); 
  
    h = softirq_vec; 
  
    do  
    { 
        if (pending & 1) 
        { 
            int prev_count = preempt_count(); 
  
            h->action(h); 
  
            if (unlikely(prev_count != preempt_count())) 
            { 
                printk(KERN_ERR "huh, entered softirq %td %p"  
                       "with preempt_count %08x,"  
                       " exited with %08x?\n", h - softirq_vec, 
                       h->action, prev_count, preempt_count()); 
                preempt_count() = prev_count; 
            } 
  
            rcu_bh_qsctr_inc(cpu); 
        } 
        h++; 
        pending >>= 1; 
    } 
    while (pending); 
  
    local_irq_disable(); 
  
    pending = local_softirq_pending(); 
    if (pending && --max_restart) 
        goto restart; 
  
    if (pending) 
        wakeup_softirqd(); 
  
    trace_softirq_exit(); 
  
    account_system_vtime(current); 
    _local_bh_enable(); 
}

__do_softirq函数处理软件中断过程如下图流程分析

4. 首先调用local_softirq_pending函数取得目前有哪些位存在软件中断

5. 调用__local_bh_disable关闭软中断,其实就是设置正在处理软件中断标记,在同一个CPU上使得不能重入__do_softirq函数

6. 重新设置软中断标记为0set_softirq_pending重新设置软中断标记为0,这样在之后重新开启中断之后硬件中断中又可以设置软件中断位。

7. 开启硬件中断

8. 之后在一个循环中,遍历pending标志的每一位,如果这一位设置就会调用软件中断的处理函数。在这个过程中硬件中断是开启的,随时可以打断软件中断。这样保证硬件中断不会丢失。

9. 之后关闭硬件中断,查看是否又有软件中断处于pending状态,如果是,并且在本次调用__do_softirq函数过程中没有累计重复进入软件中断处理的次数超过10次,就可以重新调用软件中断处理。如果超过了10次,就调用wakeup_softirqd();唤醒内核的一个进程来处理软件中断。设立10次的限制,也是为了避免影响系统响应时间。


4. 处理软中断内核线程

之前我说到不能让CPU长时间来处理中断事务,这样会影响系统的响应时间,严重影响用户和系统之间的交互式体验。所以在之前的__do_softirq中最多将循环执行10次,那么当执行了10次仍然有软中断在pending状态,这个时候应该怎么处理呢?系统将唤醒一个软件中断处理的内核进程,在内核进程中处理pending中的软件中断。这里要注意,之前我们分析的触发软件中断的位置其实是中断上下文中,而在软中断的内核线程中实际已经是进程的上下文。

这里说的软中断上下文指的就是系统为每个CPU建立的ksoftirqd进程。

看完这个函数,我不得不佩服这个函数设计的精巧!而我更多的从中体会到其中蕴藏的一种做人的道理。那就是做人要霸道一点,太谦和太恭维不行,但是又不能横行霸道,原则的问题要公平讲理,一定的时候顾及别人的利益,好处不能一个人独吞。这就跟下面ksoftirqd处理过程一样,该狠的时候禁止抢占,其它进程别想调度到哦,但是自己占用CPU时间过长的话,也自觉的问一问是不是该释放CPU给其它进程了。

下面我们就来分析一下这个处理过程怎么就体现了上面的这种说法呢?软中断的内核进程中主要有两个大循环,外层的循环处理有软件中断就处理,没有软件中断就休眠。内层的循环处理软件中断,并每循环一次都试探一次是否过长时间占据了CPU,需要调度释放CPU给其它进程。具体的操作在注释中做了解释。

static int ksoftirqd(void *__bind_cpu) 
{ 
    set_current_state(TASK_INTERRUPTIBLE); 
  
    while (!kthread_should_stop()) 
    { 
        /*不管三七二十一首先禁止抢占,我掌握CPU,并全凭我自己掌握调度*/  
        preempt_disable(); 
        if (!local_softirq_pending()) 
        { 
            preempt_enable_no_resched(); 
            /*如果没有软中断在pending,那就让出CPU来吧*/  
            schedule(); 
           /*我被唤醒了,首先掌握CPU,不让自己被抢占,自己决定自己的是否要调度*/  
            preempt_disable(); 
        } 
  
        __set_current_state(TASK_RUNNING); 
  
        while (local_softirq_pending()) 
        { 
            /* Preempt disable stops cpu going offline. 
               If already offline, we'll be on wrong CPU: 
               don't process */  
            if (cpu_is_offline((long)__bind_cpu)) 
                goto wait_to_die; 
            /*处理软中断*/  
            do_softirq(); 
            /*虽然我自己掌握是否要调度,虽然我可以一直不调度,但是我是 
            个正直的人,运行一段时间后我会看看是否需要调度,还其它进程运行*/  
            preempt_enable_no_resched(); 
            cond_resched(); 
            preempt_disable(); 
            rcu_qsctr_inc((long)__bind_cpu); 
        } 
        preempt_enable(); 
        set_current_state(TASK_INTERRUPTIBLE); 
    } 
    __set_current_state(TASK_RUNNING); 
    return 0; 
  
wait_to_die: 
    preempt_enable(); 
    /* Wait for kthread_stop */  
    set_current_state(TASK_INTERRUPTIBLE); 
    while (!kthread_should_stop()) 
    { 
        schedule(); 
        set_current_state(TASK_INTERRUPTIBLE); 
    } 
    __set_current_state(TASK_RUNNING); 
    return 0; 
} 


2017-05-25 09:23:34 linuxweiyh 阅读数 843
  • 嵌入式Linux文件与串口编程

    本课程介绍Linux环境下shell编程,普通文件与设备文件的编程方法,串口介绍与应用编程。 学习条件: 1.C语言编程基础 2.嵌入式Linux开发基础

    7357 人正在学习 去看看 沈寒

1.uC/OS-III对从中断发布消息或信号的处理有两种模式:直接发布和延迟发布。
在文件os_cfg.h中,将OS_CFG_ISR_POST_DEFERRED_EN设置成0,使用直接发布模式;设置成1,则使用延迟发布模式。

2.直接发布模式使用关闭中断的方式来保护临界段代码。

3.延时发布模式通过给调度器上锁的方式来保护临界段代码。

4.延时发布模式与直接发布模式相比,主要是减少了关闭中断的时间,因而减少了中断延迟、中断响应和中断恢复的时间,但是因为使用的是给任务调度器上锁的方式保护临界段代码,反而使得任务延迟时间变长了。

5.基于uC/OS-III的系统通常需要底层平台提供一个周期性的定时信号,成为时钟节拍,或系统节拍。
uC/OS-III的时钟节拍的作用是通过时钟节拍对任务进行整数个节拍的延迟,并为等待事件的任务提供超时判断。
时钟节拍中断必须调用OSTimeTick()函数,该函数的代码位于文件os_time.c中,521行 - 565行

void  OSTimeTick (void)
{
    OS_ERR  err;
#if OS_CFG_ISR_POST_DEFERRED_EN > 0u              // 延迟发布模式
    CPU_TS  ts;
#endif

    OSTimeTickHook();                             // 介入函数--该函数允许uC/OS-III移植者在时钟节拍中断到来后进行额外的操作

#if OS_CFG_ISR_POST_DEFERRED_EN > 0u              // 延时发布模式
    ts = OS_TS_GET();                             // 获取时间戳
    OS_IntQPost((OS_OBJ_TYPE) OS_OBJ_TYPE_TICK,   // 将发布函数调用请求和相关参数写入到中断队列中
                (void      *)&OSRdyList[OSPrioCur],
                (void      *) 0,
                (OS_MSG_SIZE) 0u,
                (OS_FLAGS   ) 0u,
                (OS_OPT     ) 0u,
                (CPU_TS     ) ts,
                (OS_ERR    *)&err);

#else                                             // 直接发布模式
   (void)OSTaskSemPost((OS_TCB *)&OSTickTaskTCB,  // 向时钟节拍任务发送信号量
                       (OS_OPT  ) OS_OPT_POST_NONE,
                       (OS_ERR *)&err);

#if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u              // 允许时间片轮转调度
    OS_SchedRoundRobin(&OSRdyList[OSPrioCur]);
#endif

#if OS_CFG_TMR_EN > 0u                            // 允许定时器任务
    OSTmrUpdateCtr--;
    if (OSTmrUpdateCtr == (OS_CTR)0u) {
        OSTmrUpdateCtr = OSTmrUpdateCnt;
        OSTaskSemPost((OS_TCB *)&OSTmrTaskTCB,    // 向定时器任务发送信号
                      (OS_OPT  ) OS_OPT_POST_NONE,
                      (OS_ERR *)&err);
    }
#endif

#endif
}

注:uC/OS-III一定需要时钟节拍,这是一个错误的概念,实际上,一些低功耗应用是不需要时钟节拍的。

2013-09-03 17:04:40 hellowxwworld 阅读数 3795
  • 嵌入式Linux文件与串口编程

    本课程介绍Linux环境下shell编程,普通文件与设备文件的编程方法,串口介绍与应用编程。 学习条件: 1.C语言编程基础 2.嵌入式Linux开发基础

    7357 人正在学习 去看看 沈寒
GPIO 驱动的 LED 由于操作简单和可视化即可以在板上直接看到其闪烁时长和频率,我们可以通过LED这种特性用于调试开发过程当中各种情景, 如统计某个中断出发频率,某些在linux 内核比较难以调试的环境, 比如休眠唤醒模式, soc各种低功耗模式等环境, 这样即使在普通串口打印不能正常工作的环境,我们也可以利用LED辅助这些环境下的调试。

0 GPIO LED 设备驱动分析

区分于keyboard的led驱动(由input 子设备管理),GPIO LEDS使用led-clas驱动框架, 用户空间通过/sys/class/leds/board-led/ 访问led的各种属性,其中max_brightness  代表最大亮度,brightness代表亮度, 复杂的led  系统支持delay_{on,  off} 用来控制led交替闪烁哦的时长, trigger 用来触发led事件,例如开启关闭

配置内核CONFIG

CONFIG_LEDS_GPIO=y
CONFIG_LEDS_TRIGGERS=y

1 重要数据结构

o 描述gpio led 设备结构
/* For the leds-gpio driver */
struct gpio_led {
    const char *name; // 每个led的名字
    const char *default_trigger;  // 指定默认触发源
    unsigned     gpio; // 控制led 的io pin
    unsigned    active_low : 1; // low 时为off
    unsigned    retain_state_suspended : 1;  // 休眠时是否保存状态,等到唤醒后恢复
    unsigned    default_state : 2; // 默认状态, 0:开, 1:关, 2:保持
    /* default_state should be one of LEDS_GPIO_DEFSTATE_(ON|OFF|KEEP) */
};
o int led_classdev_register(); gpio led注册时会创建sys接口,由以下结构体控制
struct led_classdev {
    const char        *name;
    int             brightness; // 当前亮度
    int             max_brightness; // 最大亮度
    int             flags;  // 反映led状态

    /* Lower 16 bits reflect status */
#define LED_SUSPENDED        (1 << 0)
    /* Upper 16 bits reflect control information */
#define LED_CORE_SUSPENDRESUME    (1 << 16)

    /* Set LED brightness level */
    /* Must not sleep, use a workqueue if needed */
    void        (*brightness_set)(struct led_classdev *led_cdev,
                      enum led_brightness brightness); // 亮度设置回调
    /* Get LED brightness level */
    enum led_brightness (*brightness_get)(struct led_classdev *led_cdev); // 亮度获取回调

    /*
     * Activate hardware accelerated blink, delays are in milliseconds
     * and if both are zero then a sensible default should be chosen.
     * The call should adjust the timings in that case and if it can't
     * match the values specified exactly.
     * Deactivate blinking again when the brightness is set to a fixed
     * value via the brightness_set() callback.
     */
    int        (*blink_set)(struct led_classdev *led_cdev,
                     unsigned long *delay_on,
                     unsigned long *delay_off); // 硬件加速闪烁回调, 毫秒级别

    struct device        *dev;
    struct list_head     node;            /* LED Device list */ // 每个led驱动加入双向循环链表管理
    const char        *default_trigger;    /* Trigger to use */ // 默认trigger, 一般设置为dummy

    unsigned long         blink_delay_on, blink_delay_off; // blink delay
    struct timer_list     blink_timer; // 定时器实现 blink时长控制
    int             blink_brightness; //  闪烁亮度

#ifdef CONFIG_LEDS_TRIGGERS
    /* Protects the trigger data below */
    struct rw_semaphore     trigger_lock;  // 读写信号量处理竞态

    struct led_trigger    *trigger; // 事件触发结构体
    struct list_head     trig_list; // 触发源列表
    void            *trigger_data; // 数据pointer
#endif
};
o led事件触发回调
struct led_trigger {
    /* Trigger Properties */
    const char     *name; // 触发源
    void        (*activate)(struct led_classdev *led_cdev); //亮灯回调
    void        (*deactivate)(struct led_classdev *led_cdev); // 关灯回调

    /* LEDs under control by this trigger (for simple triggers) */
    rwlock_t      leddev_list_lock; // 读写所,防止竟态
    struct list_head  led_cdevs; // 双向循环链表控制每个led的触发处理handler

    /* Link to next registered trigger */
    struct list_head  next_trig; // 管理同个led的不同触发处理handler
}

2 注册GPIO LEDS设备

static struct gpio_led __initdata gpio_leds[] = {
    [0] = {
        .name = "gpio-led0",
        .default_trigger = "cpuidle",
        .gpio = LED_ID1,
        .active_low = true,
        .retain_state_suspended = false,
        .default_state = LEDS_GPIO_DEFSTATE_OFF,
    },
    [1] = {
        .name = "gpio-led1",
        .default_trigger = "mmc",
        .gpio = LED_ID2,
        .active_low = true,
        .retain_state_suspended = true,
        .default_state = LEDS_GPIO_DEFSTATE_OFF,
    },
    ret = gpio_led_register_device(-1, &gpio_led); // 注册gpio leds设备
        -> ret = platform_device_register_resndata();  // 注册gpio leds设备


driver probe
    gpio_led_probe() // 驱动加载后probe
        -> create_gpio_led() // 申请gpio, 初始化设备并且填充上述相关回调
            -> gpio_request()
            -> gpio_direction_output()
            -> INIT_WORK(&led_dat->work, gpio_led_work) // 使用工作队列处理例如闪烁,开关等事件
            -> led_classdev_register(parent, &led_dat->cdev); // 建立sys接口,提供用户空间控制度

2 调试时只需用到led的开关足以,无需blink闪烁的功能

o 用于调试低功耗
定义不同低功耗模式的各个led,在cpu进入idle, aftr和lpa模式后就会点亮对应的leds,退出以后就会关闭相应的leds,四个cpu每个对应一个led,aftr和lpa各用一个,一共6个。
根据上述驱动配置,具体的对应关系如下:
调试对象        LED灯编号
AFTR            gpio-led0
LPA                gpio-led5
CPU0 IDLE:        gpio-led2
CPU1~3 IDLE        分别对应其他的三个LEDS

可以按照以下步骤利用led调试
o 以dummy为例,定义一个led trigger
DEFINE_LED_TRIGGER(dummy_led_trigger)
o 绑定该led trigger到驱动中的那个leds灯
led_trigger_register_simple("dummy", &cpuidle_led_trigger);
o 通过开、关led灯来定义某个dummy问题的原因
比如说,我们怀疑某个驱动中某一个操作有问题,那么可以在前后加上点灯和灭灯的动作,如果这个操作导致系统挂起,那led等点亮后就不会灭掉,因而可以用来确认问题的位置,例如:
led_trigger_event(aftr_led_trigger, LED_FULL);
/* Some potentially problematic operations */
led_trigger_event(aftr_led_trigger, LED_OFF);
    -> led_set_brightness()
        -> gpio_set_value();
3 更简单用法
直接封装gpio的操作已达到操作led亮灭的目的
void led1_on() {
    s3c_gpio_cfgpin(LED_ID1, S3C_GPIO_OUTPUT);
    s3c_gpio_setpull(LED_ID1, S3C_GPIO_PULL_NONE);
    gpio_set_value(LED_ID1, 1);
}
void led1_off() {
    s3c_gpio_cfgpin(LED_ID1, S3C_GPIO_OUTPUT);
    s3c_gpio_setpull(LED_ID1, S3C_GPIO_PULL_NONE);
    gpio_set_value(LED_ID1, 0);
}


2014-02-24 23:32:07 wjs1033 阅读数 758
  • 嵌入式Linux文件与串口编程

    本课程介绍Linux环境下shell编程,普通文件与设备文件的编程方法,串口介绍与应用编程。 学习条件: 1.C语言编程基础 2.嵌入式Linux开发基础

    7357 人正在学习 去看看 沈寒
一,什么是回调函数
      回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
二,为什么使用回调函数 
     回调可用于通知机制,例如在我们的项目中,播放器在后台运行,前台通过控制条上暂停,播放,拖拽等行为对播放器进行操作。但是当你进行这些动作的时候,后台的播放器在相当于一个暗盒里运行,你并不知道操作成功了,还是由于某个原因失败了。这个时候你就要使用回调函数,来通知前台的操作是否成功。觉个更简单的例子,你女朋友要出远门,你给你女朋友一个电话,让她每过了一个地方给你回个电话,在这个过程中,你女朋友通过电话告诉你她位置的变化这个过程就是回调函数。

3。简单的回调函数的实现

#include <stdio.h>

typedef void (*rand_num)(void* ,int);

void GetCallBack(void* lpVoid,rand_num callback)
{
    int a = 1;
       callback(lpVoid,a); //step 3 在这里给Int参数传值

}

class B
{
public:
    B(){};
    void myprintf(int a);
        void test(void);
    static void fCallBack(void* lpVoid,int a);//把回调函数设为类的静态成员函数。

};

void B::fCallBack(void *lpVoid, int a)
{
    B* p = (B*)lpVoid; //该回调函数的其中一个参数是该类的对象指针

    p->myprintf(a);
}
void B::myprintf(int a)
{
    switch(a)
    {
    case 0 :
        printf("nnnnnn....\n");
        break;
    case 1:
        printf("i get it ....value is %d\n",a);
        break;
    default:
        break;
    }
}

void B::test()
{
    GetCallBack(this,fCallBack);//step 2

}

int main(void)
{
  B b;
  b.test(); //step 1

}


没有更多推荐了,返回首页