嵌入式培训机构四个月实训课程笔记(完整版)-Linux ARM驱动编程第六天-ARM Linux编程之高级驱动基础 (物联技术666)

链接:https://pan.baidu.com/s/1V0E9IHSoLbpiWJsncmFgdA?pwd=1688
提取码:1688

教学内容:

1 、内核中断

Linux操作系统下同裸机程序一样,需要利用中断机制来处理硬件的异步事件,但用户态不允许中断事件,因此中断必须由设备驱动程序来接收与处理,如果CPU接收到一个中断,它会停止一切工作,调用中断处理函数,此时进程调度也会停止,所以就要求我们的中断处理一定要快。

中断处理程序运行在中断上下文中,注意事项如下:

  • 中断处理程序不能停止运行
  • 不能使用导致睡眠的处理机制(互斥锁、信号量等),用自旋锁代替
  • 中断处理函数不能与用户空间直接交互数据
  • 中断处理函数不必是可重用的
  • 中断处理函数可以被嵌套(不同优先级之间)
  • 中断处理函数执行时间尽可能短,中断处理通常分两个部分:

顶半部:为下半部做准备,并表示已经服务了此中断(相当于登记中断)

底半部:完成重大工作负载,执行过程中所有中断都是使能的(具体处理中断)

对于,底半部的中断处理,为了不占用CPU太多时间,可以采用队列和微线程的方式处理底半部中断程序,顶半部的登记工作一般在__init或open函数中完成。

#include <linux/interrupt.h> //申请中断

int request_irq(unsigned int irq,

irqreturn_t(*handler)(int,void*),

unsigned long irqflag,

const char *devname, void *dev_id);

参数1:中断号,所申请的中断向量,比如EXIT0中断等并定义在mach/irqs.h

参数2:中断服务程序

参数3:中断属性设置,定义在linux/interrupt.h

IRQF_DISABLED, 用于保证中断不被打断和嵌套(值为0)

IRQF_SHARED, 申请子中断时,共享中断源

IRQF_SAMPLE_RANDOM, 中断可能被用来产生随机数

参数4:中断名字,cat /proc/interrupts 可察看系统中断申请与使用情况

参数5:中断参数,作为共享中断时用于区别不同中断的参数irqreturn_t的第二个参数

返回0为成功,失败返回-INVAL或-EBUSY

void free_irq(unsigned int irq, void *dev_id); // 释放中断

参数1:所要清除中断服务程序的中断号

参数2:中断参数,同申请时的值一致

void enable_irq(unsigned int irq); // 使能中断

参数:中断号

void disable_irq(unsigned int irq); // 关闭中断,并等待中断处理完成后返回

参数:中断号

void disable_irq_nosync(unsigned int irq); // 关闭中断,立即返回

参数:中断号

注意:当用户在中断处理函数中关闭中断时应当使用disable_irq_nosync,否则会形成死锁

如果中断程序比较短,就不必要把中断分为二个部分,

对于中断的设置,可以使用

#include <linux/irq.h>

int set_irq_type(int irq, int edge);

参数1:中断号 #include <mach/irqs.h>

参数2:外部中断触发方式定义在linux/irq.h

IRQ_TYPE_LEVEL_LOW,

IRQ_TYPE_LEVEL_HIGH,

IRQ_TYPE_EDGE_FALLING,

IRQ_TYPE_EDGE_RISING,

IRQ_TYPE_EDGE_BOTH

具体程序如下:

//**************************************************

#include <linux/module.h> /*module_init()*/

#include <linux/kernel.h> /* printk() */

#include <linux/init.h> /* __init __exit */

#include <linux/fs.h> /* file_operation */

#include <asm/uaccess.h> /* copy_to_user, copy_from_user */

#include <linux/device.h> /*class ,class_create ,device_create 等*/

#include <linux/errno.h> /* Error number */

#include <linux/delay.h> /* udelay */

#include <mach/regs-gpio.h> /*S3C2410_GPGCON ,S3C2410_GPIO_IRQ*/

#include <linux/pci.h> /*S3C24XX_VA_GPIO*/

#include <linux/irq.h> //set_irq_type ,IRQ_TYPE_EDGE_FALLING

#include <linux/interrupt.h> //request_irq , free_irq

#include <mach/irqs.h> //IRQ_EINT2

#include <linux/gpio.h> //s3c2410_gpio_cfgpin();

#define DRIVER_NAME "key13_eint"

static int MAJOR_NR = 0; /* Driver Major Number */

static int MINOR_NR = 0; //次设备起始号

struct class *my_class;

static struct semaphore readable; //信息号,阻塞read调用,后续知识

static unsigned char key;

static irqreturn_t buttons_interrupt(int irq, void *dev_id) //和request_irq第二参数对应

{

key = (unsigned int)dev_id;

up(&readable);

return 0;

}

static int keyDriver_open(struct inode *inode, struct file *file)

{return 0;}

static int keyDriver_release(struct inode *inode, struct file *file)

{ return 0;}

static int keyDriver_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)

{

int ret = 0;

if(down_interruptible(&readable) != 0){return 0;}

ret = copy_to_user(buff, &key, sizeof(key));

if(ret != 0){

PRINTK("in function read,copy_to_user function run failed!\n");

return ret;

}

return sizeof(key);

}

static struct file_operations keyDriver_fops = {

.owner = THIS_MODULE,

.read = keyDriver_read,

.open = keyDriver_open,

.release = keyDriver_release,

};

static int __init myModule_init(void)

{

int ret;

/* Module init code */

PRINTK("keyDriver_init\n");

sema_init(&readable, 0); //信号量初始化

ret = set_irq_type(IRQ_EINT2,IRQ_TYPE_EDGE_FALLING);//设置外部中断

if(ret != 0){

PRINTK("IRQ_EINT2 set irq type failed!\n");

return ret;

}

ret = set_irq_type(IRQ_EINT3,IRQ_TYPE_EDGE_FALLING);

if(ret != 0){

PRINTK("IRQ_EINT3 set irq type failed!\n");

return ret;

}

ret = set_irq_type(IRQ_EINT4,IRQ_TYPE_EDGE_FALLING);

if(ret != 0){

PRINTK("IRQ_EINT4 set irq type failed!\n");

return ret;

}

ret = request_irq(IRQ_EINT2,buttons_interrupt, IRQF_DISABLED,"KEY1", (void *)1);

ret = request_irq(IRQ_EINT3,buttons_interrupt, 0,"KEY2", (void *)2);

ret = request_irq(IRQ_EINT4,buttons_interrupt, 0,"KEY3", (void *)3);

MAJOR_NR = register_chrdev(MAJOR_NR, DRIVER_NAME, &keyDriver_fops);

if(MAJOR_NR < 0)

{

PRINTK("register char device fail!\n");

return MAJOR_NR;

}

my_class=class_create(THIS_MODULE,"udev_key13_eint");

device_create(my_class,NULL, MKDEV(MAJOR_NR, MINOR_NR), NULL,DRIVER_NAME);

PRINTK("register myDriver OK! Major = %d\n", MAJOR_NR);

return 0;

}

static void __exit myModule_exit(void)

{

PRINTK("exit in\n");

free_irq(IRQ_EINT2, (void *)1);

free_irq(IRQ_EINT3, (void *)2);

free_irq(IRQ_EINT4, (void *)3);

if(MAJOR_NR > 0)

{

unregister_chrdev(MAJOR_NR, DRIVER_NAME);

device_destroy(my_class,MKDEV(MAJOR_NR, MINOR_NR));

class_destroy(my_class);

PRINTK("myModule_exit ok\n");

}

return;

}

module_init(myModule_init);

module_exit(myModule_exit);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("HB");

//**************************************************

如果中断处理程序比较大,可以使用上下文分开的形式,对于下文的执行,可以使用队列或者微线程方式。

2 、内核微线程:(参考 7 、嵌入式 linux 设备驱动 \02 、字符设备驱动 \somecode\1_basic_cdriver\2_advanced_cdriver\1_interrupt\1_key13_eint

创建方法一:

//直接利用宏直接创建

DECLARE_TASKLET(name, func, data);

参数分别为:微线程名称、任务处理函数、任务处理函数的参数

//微线程调度函数

void tasklet_schedule(struct tasklet_struct * );

函数功能:将tasklet加入tasklet_(hi_)vec后,激发软件中断,并立即返回

//微线程销毁函数

void tasklet_kill(struct tasklet_struct * );

//*****************************************

#include <linux/interrupt.h>

void tasklet_function( unsigned long data );//声明已定义的任务处理函数

DECLARE_TASKLET(my_tasklet, tasklet_function, tasklet_data);

tasklet_schedule(&my_tasklet); //中断处理程序中调用

......

//使用完毕后,调用以下函数销毁已初始化的微线程

tasklet_kill(&key_tasklet);

//*****************************************

创建方法二:

//微线程初始化函数

void tasklet_init(struct tasklet_struct *t,

void (*func)(unsigned long), unsigned long data);

参数1:用户已定义tasklet_struct 变量的地址

参数2:tasklet的任务处理函数

参数3:func函数的参数

//微线程调度函数

void tasklet_schedule(struct tasklet_struct * );

函数功能:将tasklet加入tasklet_(hi_)vec后,激发软件中断,并立即返回

//微线程销毁函数

void tasklet_kill(struct tasklet_struct * );

//**************

其他的功能:

微线程禁止、打开、激发软中断:

//禁止微线程调度,其中tasklet_disable_nosync( )函数和disable_irq_nosync( )类似

void tasklet_disable(struct tasklet_struct * );

void tasklet_disable_nosync(struct tasklet_struct * );

//允许微线程调度

void tasklet_enable(struct tasklet_struct * );

3 、工作队列:

对于队列方式可以实现阻塞进程的唤醒,具体队列程序如下:

1)、定义工作结构体和自定义队列结构体

static struct workqueue_struct *key_workqueue;

static struct work_struct key_work;

//static struct workqueue_struct *key_workqueue;

//struct delayed_work key_work;

2)、在open或者__init中创建队列和初始化任务

key_workqueue = create_workqueue("key_queue");

INIT_WORK(&key_work,keyScan_Handler);

//key_workqueue = create_workqueue("key_queue");

//INIT_DELAYED_WORK(&dwork, func);

3)、在中断(适当的地址)中把任务添加到队列中去

queue_work(key_workqueue,&key_work);

//int queue_delayed_work(key_workqueue,&key_work,unsigned long delay);

4)、在退出时候,把队列任务清空,销毁队列(在__exit)

flush_workqueue(key_workqueue);

destroy_workqueue(key_workqueue);

注意:在队列中也可以不使用自定义队列,使用全局内核队列,这样就不需要定义、创建、清空、销毁等动作,只需把任务添加全局队列即可;绿色的为带延时的队列。

//添加任务到内核全局工作队列

int schedule_work(struct work_struct *work);

参数:已初始化的工作

4 、内核并发处理

当多个进程、线程或中断、正常用户程序同时访问同一个资源,可能导致错误,因此内核需要提供并发控制机制,对公共资源的访问进行同步控制,确保共享资源的安全访问。

一般linux中包含了众多的互斥与同步机制,包括信号量、互斥体、自旋锁、原子操作、读写锁等来实现并发机制的实现。

情况一、单核处理器

对于单核处理器来说,进程只是在宏观上并行,而在微观上是串行的,所以对于一个公共区域操作,几个进程之间对区域操作不存在竞发状态,只要防止在进程对区域读写的时候,不被打断即可,为此分二种情况,一种是抢占性内核、即高优先级进程能够打断低优先级进程,另一种是非抢占性内核,只有中断才可能打断进程;所以在抢占性单核情况要使用自旋锁和关中断,在非抢占内核情况下仅仅只要关中断。

情况二、多核处理器(SMP系统)

对于上述单核的情况只能防止一个处理器的竞发,如果多核的情况就不能防止了,为此,只能在临界区段(即公共区域)加上自旋锁。

手段一:中断屏蔽

Local_irq_disable(); //屏蔽所有中断

...... //临界区代码

Local_irq_enable(); //开放所有中断

注:使用该函数有时会出现问题,如果在屏蔽中断前,中断已被关闭,那么在开放中断时之前默认处于关闭状态的中断被打开。

我们可以通过下面的方法避免这种情况:

Local_irq_save(); //保存中断状态,关闭所有中断

...... //临界区代码

Local_irq_restore(); //打开中断,恢复关闭前中断状态

手段二:自旋锁

1)、定义自旋锁

static struct spinlock my_spin_lock;

//static spinlock_t my_spin_lock;

2)、初始化自旋锁

spin_lock_init(&my_spin_lock);

3)、在程序区加锁,离开解锁,加解锁有四种形式

//获得自旋锁,(可自旋等待,可被软、硬件中断)

void spin_lock(spinlock_t *my_spinlock);

//获得自旋锁,(可自旋等待,保存中断状态并关闭软、硬件中断)

void spin_lock_irqsave(spinlock_t *my_spinlock, unsigned long flags);

//获得自旋锁,(可自旋等待,不保存中断状态关闭软、硬件中断)

void spin_lock_irq(spinlock_t *my_spinlock);

//获得自旋锁,(可自旋等待,不保存中断状态关闭硬件中断)

void spin_lock_bh(spinlock_t *lock);

注意:进程在获取自旋锁失败自旋等待时都不可以被系统消息打断

/释放自旋锁,退出临界区,严格与上面加锁对应

void spin_unlock(spinlock_t *lock)

void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)

void spin_unlock_irq(spinlock_t *lock)

void spin_unlock_bh(spinlock_t *lock)

//尝试获得自旋锁,(不自旋等待,成功返回1、失败则返回0)

int spin_trylock(spinlock_t *lock)

//*************************************

#include <linux/spinlock.h>

spinlock_t my_spinlock;

spin_lock_init(&my_spinlock);

//或者spinlock_t my_spinlock = SPIN_LOCK_UNLOCKED;

spin_lock(&my_spinlock);

....... //临界区代码

spin_unlock(&my_spinlock);

//***************************************

手段三:互斥和信号量(和 linux 系统编程类似)

1)、定义信号量

struct semaphore sem;

2)、初始化信号量

void sema_init(struct semaphore *sem, int val);

val:信号值

3)、加或减信号量

// 获取(等待)信号量,(减操作,不能被系统消息打断,会导致调用者睡眠)

void down(struct semaphore *sem);

// 获取(等待)信号量,(减操作,可以被系统消息打断,会导致调用者睡眠)

int down_interruptible(struct semaphore *sem);

//尝试获得信号量,成功返回0,失败返回非0,不会导致调用者睡眠

int down_trylock(struct semaphore *sem);

// 释放信号量,即使信号量加1(如果线程睡眠,将其唤醒)

void up(struct semaphore *sem);

//************************************

#include <linux/semaphore.h>

struct semaphore my_sema; //信号量

sema_init(&my_sema, 1); //信号量初始化为1,与init_MUTEX(my_sema)等效

//定义一个semaphore变量,并初始化为1

//DECLARE_MUTEX(my_sema);

down_interruptible(&my_sema); //获取信号量(减操作)

..... //临界区代码

up(&bufflock); //释放信号量(加操作)

//**************************************

1)、互斥锁定义和初始化

struct mutex mut;

mutex_init(&mut);

//DEFINE_MUTEX(mutexname) //一步定义和初始化

2)、加、解锁

//获得互斥体(加锁),(不能被系统消息打断,若获取该锁失败则进入睡眠)

extern void mutex_lock(struct mutex *lock);

//获得互斥体(加锁),(可以被系统消息打断,若获取该锁失败则进入睡眠)

extern int mutex_lock_interruptible(struct mutex *lock);

//尝试获得互斥体(加锁),成功返回0,失败返回错误值

extern int mutex_trylock(struct mutex *lock);

//释放互斥体(解锁)

extern void mutex_unlock(struct mutex *lock);

//***********************************

#include <linux/mutex.h>

struct mutex my_mutex;

mutex_init(&my_mutex);

//或者static DEFINE_MUTEX(my_mutex);

mutex_lock(&my_mutex); //获取信号量(减操作)

..... //临界区代码

mutex_unlock(&my_mutex); //释放信号量(加操作)

//***************************************

5 、延时与内核定时器

Linux系统定时器能以可编程的频率中断处理器,此频率为每秒的定时器节拍数,对应内核变量HZ,即1秒中产生的节拍数。

每当系统定时器中断产生,内部计数器值就加1,此计数器是一个64位的变量"jiffies_64",开发者常使用unsigned long型的变量"jiffies",此或者和jiffies_64相同或者为jiffies_64的低32位(jiffies和jiffies_64相同与否取决于平台),该变量记录了自上次操作系统引导以来的时钟滴答数。

内核定时器:

#include <linux/timer.h>

// 定时器结构体类型

struct timer_list

{

struct list_head entry;

//用来形成链表,由内核管理,申请一个定时器,自动加入链表

unsigned long expires;

//定时器到期时间(指定一个时刻,即, jiffies计数值)

void (*function)(unsigned long);

// 定时器处理函数

unsigned long data;

// 作为参数被传入定时器处理函数

......

};

// 初始化定时器

void init_timer(struct timer_list *timer);

//添加定时器。定时器开始计时

void add_timer(struct timer_list * timer);

//删除定时器,在定时器到期前禁止一个已注册定时器

int del_timer(struct timer_list * timer);

//如果定时器函数正在执行则在函数执行完后返回,其它情况同上

int del_timer_sync(struct timer_list *timer);

//更新定时器到期时间,并开启定时器

int mod_timer(struct timer_list *timer, unsigned long expires);

//查看定时器是否正在被调度运行//return value: 1 if the timer is pending, 0 if not.

int timer_pending(const struct timer_list *timer);

设计步骤:

1)、创建struct timer_list结构体,并且把赋值定时时间,函数入口,函数参数等。

struct timer_list my_timer;

my_timer.data = 0L;

my_timer.function = timer_handler;

my_timer.expires = jiffies + 1*HZ;

2)、初始化定时器

init_timer(&my_timer);

3)、启动定时

add_timer(&my_timer);

4)、如需重复执行, 需要重新初始化并启动定时器

my_timer.expires = jiffies + 1 * HZ/n; //1/n秒定时

add_timer(&my_timer);

//mod_timer(&my_timer, jiffies+1*HZ);

5)、关闭或者卸载的时候把定时器销毁

del_timer_sync(&my_timer);

//**************************************

struct timer_list myTimer;

init_timer(&myTimer);

myTimer.expires = jiffies + 3 * HZ;

myTimer.data = 0L;

myTimer.function = timerHandler;

add_timer(&myTimer); //添加定时器到内核

// 定时器处理函数,计数次数 n = t*HZ(t单位为秒)

void timerHandler(unsigned long data)

{

// 如需重复执行, 需要重新初始化并启动定时器

myTimer.expires = jiffies + 3 * HZ;

add_timer(&myTimer);

}

......

del_timer_sync(&myTimer); //使用完毕删除定时器

//*********************************************

内核延时:

内核延时分为,等待延时和睡眠延时,对于等待延时很短,在延时期间会一直忙等待(即占据CPU);另一种如果延时较长,进程会进入睡眠延时(即让出CPU)

短延迟 **-**不依赖于时钟滴答,与具体的CPU类型相关

#include <linux/delay.h>

void ndelay(unsigned long nsecs); //纳秒级延时

void udelay(unsigned long usecs); //微秒级延时

void mdelay(unsigned long msecs); //毫秒级延时

注:这三个延时函数使任务进入忙等待,一般不太长的时间可以用它

长延时 **-**长于一个时钟滴答,为了提高系统运行效率,当前进程需要进入睡眠,让出处理器。

void msleep(unsigned int millisecs);

unsigned long msleep_interruptible(unsigned int millisecs);

void ssleep(unsigned int seconds);

以上三个函数本质都是依赖于等待队列来实现进程睡眠,让出处理器的。

队列实现一:(自建队列)

#include<linux/wait.h>

long wait_event_timeout(wait_queue_head_t q, condition,long timeout);

long wait_event_interruptible_timeout(wait_queue_head_t q, condition,long timeout);

/*这些函数在给定队列上睡眠, 但是它们在超时(以 jiffies 表示)到后返回。如果超时,函数返回 0; 如果这个进程被其他事件唤醒,则返回以 jiffies 表示的剩余的延迟实现;返回值从不会是负值*/

//*****************

#include <linux/wait.h>

wait_queue_head_t wait;

init_waitqueue_head(&wait);

wait_event_interruptible_timeout(wait,0,delay);

//********************

队列实现二:

#include <linux/sched.h>

signed long schedule_timeout(signed long timeout);

注:等待队列wait_event_timeout函数内部也是通过该函数实现,使用schedule_timeout可以避免声明和使用多余的等待队列头

/*timeout 是要延时的 jiffies 数。除非这个函数在给定的 timeout 流失前返回,否则返回值是 0 。schedule_timeout 要求调用者首先设置当前的进程状态。为获得一个不可中断的延迟, 可使用 TASK_UNINTERRUPTIBLE 代替。如果你忘记改变当前进程的状态, 调用 schedule_time 如同调用 shcedule,建立一个不用的定时器。*/

使用实例:

//*************************

#include <linux/sched.h>

......

long delay = 100;

set_current_state(TASK_INTERRUPTIBLE);

schedule_timeout(delay);

.....

//*************************

set_current_state为设置当前进程的状态,调度器在定时器到时且进程状态变为TASK_RUNNING时才运行该进程,若实现不可中断的延迟可用TASK_UNINTERRUPTIBLE。若使用前忘记改变进程的状态,则定时器不起作用,不能实现延迟

6 Linux 驱动阻塞与非阻塞

阻塞操作,是指在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作条件后再进行各项操作,实现阻塞一般使用等待队列或信号量的方法。

非阻塞,进程在不能进行设备操作时,并不挂起,它或者放弃,或者不停地查询,直到可以进行操作为止,对于非阻塞而言,如果要知道能不能操作,就必须不断的查询,一般使用轮询操作。

阻塞实现:

等待队列在Linux内核中用来阻塞或唤醒一个进程,也可以用来同步对系统资源的访问,还可以实现延迟功能。

实现方法 1--- 信号量:

1)、定义信号并初始化

static struct semaphore readable;

sema_init(&readable, 0); //信号量初始化为0

2)、在需要阻塞的系统调用中,使用down操作(假设read阻塞)

if(down_interruptible(&readable) != 0) //read阻塞实现

return 0;

3)、在把数据放入之后,能够让用户层能读取到字符的地方,up操作(假设在中断中)

up(&readable);

注:down_interruptible会在没信号量的情况下,阻塞,而睡眠。

实现方法 2-- 等待队列:

单任务情况:

1)、定义和初始化等待队列头

//定义一个等待队列头

wait_queue_head_t my_queue;

//初始一个等待队列头

init_waitqueue_head(&my_queue);

//定义并初始化一个等待队列头

//DECLARE_WAIT_QUEUE_HEAD(my_queue);

2)、定义一个条件flag,非0为真,0为假;依据条件把当前进程加入队列

static int flag = 0;

wait_event_interruptible(my_queue,flag);

3)、在需要的地址唤醒进程

flag = 1;

wake_up_interruptible(&my_queue);

//*******************************

#include <linux/wait.h>

//定义等待队列头

static DECLARE_WAIT_QUEUE_HEAD(my_queue);

static int flag = 0;

.....

//判断flag条件如果为假,将当前进程推入等待队列并将其睡眠

wait_event_interruptible(my_queue,flag);

flag = 0;

.....

void func()

{

flag = 1;

wake_up_interruptible(&my_queue);

}

//******************************

1)、定义和初始化等待队列头

static DECLARE_WAIT_QUEUE_HEAD(key_waitqueue_head);

2)、将当前进程推入等待队列将其睡眠

interruptible_sleep_on(&key_waitqueue_head);

3)、在需要的地址唤醒进程

wake_up_interruptible(&key_waitqueue_head);

非阻塞实现:

select()函数是提供给用户的接口函数,该函数通过系统调用最终会引发设备驱动中的poll()函数被执行,该机制可以实现一个用户进程对多个设备驱动文件的监测,在设备驱动中的轮询编程poll()函数,为file_operation成员之一。

轮询方法:

#include <sys/select.h>

//在应用程序中调用的文件描述符监测函数

int select(int numfds, fd_set *readfds,

fd_set *writefds, fd_set *exceptfds,

struct timeval *timeout);

参数numfds:待监听中最大描述符值加一

参数readfds:监听读操作的文件描述符集合

参数writefds:监听写操作的文件描述符集合

参数exceptfds:监听异常处理的文件描述符集合

参数timeout:监听等待超时退出select()

返回值 如果参数timeout设为NULL则表示select()没有timeout。

错误代码 执行成功则返回文件描述词状态已改变的个数,如果返回0代表在描述词状态改变前已超过timeout时间,当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds,exceptfds和timeout的值变成不可预测。

//文件描述符集合的变量的定义

fd_set fds;

//清空描述符集合

FD_ZERO(fd_set *set);

//加入一个文件描述符到集合中

FD_SET(int fd, fd_set *set);

//从集合中清除一个文件描述符

FD_CLR(int fd, fd_set *set);

//判断文件描述符是否被置位

FD_ISSET(int fd, fd_set *set);

返回非0,表示置位(该文件描述集合中有文件可进行读写操作,或产生错误)

//添加等待队列到wait参数指定的轮询列表中

void poll_wait(struct file *filp, wait_queue_heat_t *wq,poll_table *wait);

程序设计步骤:

1)、定义和初始化头队列

static DECLARE_WAIT_QUEUE_HEAD(key_waitq);

2)、在file_operations中添加poll函数

.poll = keyDriver_poll,

3)、在poll函数中把添加等待队列到wait参数指定的轮询列表

poll_wait(file, &key_waitq, wait);

4)、在中断等需要地方,唤醒等待

wake_up_interruptible(&key_waitq);

7 Linux 内核线程

内核线程类似于用户进程,通常用于并发处理些工作,它是一种在内核空间实现后台任务的方式,并且可以参与时间片轮转调度。

线程一般在__init函数里面就定义、开启,而结束线程一般是在__exit函数中停止。

//创建内核线程(方法一),返回值为创建线程的指针

struct task_struct *kthread_create(int (*threadfn)(void *data),

void *data,const char namefmt[], ...);

参数1:线程函数指针,线程开启后将运行此函数

参数2:函数参数data,传递给线程函数

参数3:线程名称,这个函数可以像printk一样传入某种格式的线程名

//内核线程创建后不会马上运行,需要通过以下函数启动

int wake_up_process(struct task_struct *p);

//***************************

static int key_kthread_create(void)

{

int err = 0;

key_thread = kthread_create(key_thread_func, &key, "key13_task");

if(IS_ERR(key_thread)){

PRINTK("Unable to start kernel thread.\n");

err = PTR_ERR(key_thread);

key_thread = NULL;

return err;

}

wake_up_process(key_thread);

return 0;

}

//*****************************

static void key_kthread_stop(void)

{

if(key_thread){

kthread_stop(key_thread);

key_thread = NULL;

}

return;

}

//*****************************

static int key_thread_func(void *data)

{

PRINTK("in function key_thread!\n");

//daemonize("key_thread_func");

//allow_signal(SIGKILL);

for(;;){ //while循环实现线程任务

if(kthread_should_stop()) //检查是否stop

// if(signal_pending(current))

break;

if(key = translateKey(GPFDAT & 0x1c)){

wake_up_interruptible(&my_queue);

condition = 1;

}

schedule(); //进程间切换

}

return 0;

}

//***********************************************

disable_irq关闭中断并等待中断处理完后返回, 而disable_irq_nosync立即返回

下面从内核代码来找一下原因:

先看一下disable_irq_nosync,内核代码中是这样解释的:

/**

* disable_irq_nosync - disable an irq without waiting

* @irq: Interrupt to disable

*

* Disable the selected interrupt line. Disables and Enables are

* nested.

* Unlike disable_irq(), this function does not ensure existing

* instances of the IRQ handler have completed before returning.

*

* This function may be called from IRQ context.

*/

void disable_irq_nosync(unsigned int irq)

{

struct irq_desc *desc = irq_to_desc(irq);

unsigned long flags;

if (!desc)

return;

chip_bus_lock(irq, desc);

spin_lock_irqsave(&desc->lock, flags);

__disable_irq(desc, irq, false);

spin_unlock_irqrestore(&desc->lock, flags);

chip_bus_sync_unlock(irq, desc);

}

关闭中断后程序返回, 如果在中断处理程序中, 那么会继续将中断处理程序执行完.

/**

* disable_irq - disable an irq and wait for completion

* @irq: Interrupt to disable

*

* Disable the selected interrupt line. Enables and Disables are

* nested.

* This function waits for any pending IRQ handlers for this interrupt

* to complete before returning. If you use this function while

* holding a resource the IRQ handler may need you will deadlock.

*

* This function may be called - with care - from IRQ context.

*/

void disable_irq(unsigned int irq)

{

struct irq_desc *desc = irq_desc + irq;

if (irq >= NR_IRQS)

return;

disable_irq_nosync(irq);

if (desc->action)

synchronize_irq(irq);

}

关闭中断并等待中断处理完后返回.从代码中可以看到, disable_irq先是调用了disable_irq_nosync, 然后检测desc->action是否为1. 在中断处理程序中, action是置1的, 所以进入synchronize_irq函数中.

/**

* synchronize_irq - wait for pending IRQ handlers (on other CPUs)

* @irq: interrupt number to wait for

*

* This function waits for any pending IRQ handlers for this interrupt

* to complete before returning. If you use this function while

* holding a resource the IRQ handler may need you will deadlock.

*

* This function may be called - with care - from IRQ context.

*/

void synchronize_irq(unsigned int irq)

{

struct irq_desc *desc = irq_to_desc(irq);

unsigned int status;

if (!desc)

return;

do {

unsigned long flags;

/*

* Wait until we're out of the critical section. This might

* give the wrong answer due to the lack of memory barriers.

*/

while (desc->status & IRQ_INPROGRESS)

cpu_relax();

/* Ok, that indicated we're done: double-check carefully. */

spin_lock_irqsave(&desc->lock, flags);

status = desc->status;

spin_unlock_irqrestore(&desc->lock, flags);

/* Oops, that failed? */

} while (status & IRQ_INPROGRESS);

/*

* We made sure that no hardirq handler is running. Now verify

* that no threaded handlers are active.

*/

wait_event(desc->wait_for_threads, !atomic_read(&desc->threads_active));

}

注释中说明该函数是在等待中断处理程序的结束, 这也是disable_irq与disable_irq_nosync不同的主要所在. 但是在中断处理函数中调用会发生什么情况呢? 进入中断处理函数前IRQ_INPROGRESS会被__setup_irq设置, 所以程序会一直陷在while循环中, 而此时内核以经被独占, 这就导致系统死掉.

总结:

由于在disable_irq中会调用synchronize_irq函数等待中断返回, 所以在中断处理程序中不能使用disable_irq, 否则会导致cpu被synchronize_irq独占而发生系统崩溃.

头尾介绍了,当然还的说说中断的心脏了,即中断服务程序,即申请中断时的第二个参数

irqreturn_t handler(int irq,void *dev_id)

{

...... // 中断处理

return IRQ_HANDLED;

}

该中断服务程序可以被多个中断调用,所以使用的时候,为防止产生冲突,需要加上自旋锁,此时即涉及到内核的并发控制

抢占内核:必须配置 make menuconfig

kernel feature

freemption model

相关推荐
草莓熊Lotso2 分钟前
【C++】递归与迭代:两种编程范式的对比与实践
c语言·开发语言·c++·经验分享·笔记·其他
NiKo_W1 小时前
Linux 初识
linux·运维·服务器
Aczone281 小时前
硬件(六)arm指令
开发语言·汇编·arm开发·嵌入式硬件·算法
我爱挣钱我也要早睡!3 小时前
Java 复习笔记
java·开发语言·笔记
磊灬泽5 小时前
【日常错误】鼠标无反应
linux·windows
汇能感知8 小时前
摄像头模块在运动相机中的特殊应用
经验分享·笔记·科技
阿巴Jun8 小时前
【数学】线性代数知识点总结
笔记·线性代数·矩阵
茯苓gao8 小时前
STM32G4 速度环开环,电流环闭环 IF模式建模
笔记·stm32·单片机·嵌入式硬件·学习
是誰萆微了承諾8 小时前
【golang学习笔记 gin 】1.2 redis 的使用
笔记·学习·golang
DKPT9 小时前
Java内存区域与内存溢出
java·开发语言·jvm·笔记·学习