嵌入式培训机构四个月实训课程笔记(完整版)-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

相关推荐
哎呦喂-ll35 分钟前
Linux进阶:环境变量
linux
Rverdoser36 分钟前
Linux环境开启MongoDB的安全认证
linux·安全·mongodb
PigeonGuan1 小时前
【jupyter】linux服务器怎么使用jupyter
linux·ide·jupyter
东华果汁哥1 小时前
【linux 免密登录】快速设置kafka01、kafka02、kafka03 三台机器免密登录
linux·运维·服务器
咖喱鱼蛋2 小时前
Ubuntu安装Electron环境
linux·ubuntu·electron
ac.char2 小时前
在 Ubuntu 系统上安装 npm 环境以及 nvm(Node Version Manager)
linux·ubuntu·npm
肖永威2 小时前
CentOS环境上离线安装python3及相关包
linux·运维·机器学习·centos
tian2kong2 小时前
Centos 7 修改YUM镜像源地址为阿里云镜像地址
linux·阿里云·centos
布鲁格若门2 小时前
CentOS 7 桌面版安装 cuda 12.4
linux·运维·centos·cuda