您的位置:首页 > 运维架构 > Linux

linux 驱动理论学习阶段

2015-12-01 20:56 239 查看
1、 linux设备驱动中的重要数据结构:(具体的数据结构在资料93)

a、 file 结构体

b、 inode结构体 : 它记录了文件访问设备号、权限、属主、组、大小、生成时间、访问时间、等信息

2、 unsigned int iminor(struct inode *Inode) : 获取主设备号

unsigned int imajor(struct inode *Inode) : 次设备号

查看 /proc/devices 文件可以获知系统中注册的设备

内核Docments目录下的devices文件描述了linux设备号的分配情况

3、 devfs 设备文件系统:

a、可以通过程序在设备初始化时在/dev目录下创建设备文件,卸载时删除它

b、设备驱动程序可以指定设备名、所有者、和权限位,用户空间仍可以修改所有者和权限位

c、不再需要为设备驱动程序分配主设备号以及处理此设备号

但是在2.6版本中,udev代替了devfs!!!

4、udev文件系统:

a、kobject内核对象,这个数据结构使所有设备在底层都具有统一的接口

其他的关于设备模型及其 udev的使用,参考 《驱动开发详解》第五章

这部分资料需要自己以后再 补充,因为 尼玛 真的是看的晕

<这部分笔记写的较早了,改天会在别的博客中详解说明 驱动模型>

---------------------------------------------------------------------------------------------------------------------------------------------------------------

字符驱动

1、struct cdev {

struct kobject kobj;

struct modules *owner;

struct file_operations *opts;

struct list_head list;

dev_t dev;

unsigned int count;

};

MAJOR(dev_t dev); MINOR(dev_t dev) : 获得主次设备号

MKDEV(int major, int minor) 生成dev_t

2、 cdev结构体

void cdev_init(struct cdev *, struct file_operations *): 初始化Cdev,建立file_operations 与cdev的联系

int cdev_add(struct cdev *, dev_t, unsigned int); 向系统添加一个cdev,完成字符设备的注册 ,发生在 字符设备驱动模块加载函数中

void cdev_del(struct cdev *) 向系统注销一个cdev,发生在 字符设备驱动模块卸载函数中

3、 分配和释放设备号

现在一般使用动态分配主设备号

int alloc_chrdev_region(dev_t *dev /*作为输出*/, unsigned int baseminor, unsigned int count, const char * name);

/*这里的dev参数作为输出存在的,于是需要自己 利用 struct cdev* cdev_alloc(void) ; 函数来自行分配一个cdev空间*/

当使用cdev_del函数从系统注销字符设备之后,应该使用unregister_chrdev_region(dev_t from, unsigned int count);
释放原先申请的设备号

struct file_operations{

struct modules *owner;

loff_t (*llseek) (struct file *, loff_t , int);

ssize_t (*read) (struct file *, char __user *, size_t , loff_t *)

ssize_t (*aio_read) (struct kiocb

....

}; 更详细的说明在 《linux设备驱动程序3E_中文》 的 第 64 Page

《linux设备驱动详解》 的 117 Page

ioctl 函数,一般使用统一的命令码。 命令码的格式如下:

设备类型 序列号 方向 数据尺寸

8bit 8bit 2bit 13/14bit

命令码的设备类型字段为1个’幻数‘, 可以是 0~0xff的值,

内核中的ioctl-number.txt 给出了一些推荐的和被使用的值,

新设备定义的幻数要注意避免与其冲突

方向字段 (2bit) : 表示数据传送的方向,可能值 为 IDC_NONE、 IOC_READ、 IOC_WRITE、 IOC_READ|IOC_WRITE

-------------------------------------------------------------------------------------------------------------------------------

内核编程并发

1、原子操作:

atomic_t v= ATOMIC_INTI(1) ; 定义变量 并初始化

void atomic_set(atomic_t *v, int i ) ;设置原子变量为i

atomic_read(atomic_t *v); 获得原子变量的值

void atomic_add(int i, atomic_t *v); 加上i

void atomic_sub(int i, atomic_t *v); 减去i

void atomic_inc(atomic_t *v) ;自增1

void atomic_dec(atomic_t *v); 自减1

int atomic_inc_and_test(atomic_t *v);

int atomic_dec_and_test(...);

int atomic_sub_and_test(...);

这三个都是先进行数值的操作之后, 再进行 判断是否为0, 如果为0则返回true

int atomic_add_return(int i ,atomic_t *v);

int atomic_sub_return(inti , atomic_t *v);

int atomic_inc_return(atomic_t *v);

int atomic_dec_return(atomic_t *v);

2、自旋锁 、读写自旋锁、 顺序锁 、 RCU 、

自选锁的核心规则: 任何代码必须在持有自旋锁时,是原子性的,它不能睡眠,不能因为任何原因放弃处理器,除了五福中断

持有一个自旋锁时,应该避免 进程的睡眠。

自旋锁必须是一直尽可能短时间的持有。

void spin_lock (spinlock_t *lock); 不禁止任何中断

void spin_lock_irqsave(spinlock_t *lock); 在获得自旋锁前(在获得之前,一直在进行自旋操作),禁止中断,中断状态保存到flags里面。

void spin_lock_irq
(spinlock_t *lock); 获得自旋锁并且在释放自旋锁后,打开中断(如果别的地方禁止中断,可能会冲突,此时应该避免这种情况,也就是说在使用这个函数之前,先确认代码没有禁止中断的操作),不保持跟踪flags

void spin_lock_bh
(spinlock_t *lock); 在获得锁之前禁止软件中断,硬件中断保留

--------------------------------------------------------------------------------------------------------

(中断处理程序中,发生自旋时,如何使得自旋结束????即使在中断之前的进程也没没有持有自旋锁)

--------------------------------------------------------------------------------------------------------

3、信号量的使用 (信号量不会自旋,但是会导致休眠)

struct semaphore sem; 定义一个信号量, 在另一本书中叫做旗标

void init_MUTEX(struct semaphore *sem); 初始化化一个互斥的信号量,等同于 sema_init(struct semaphore *sem, 1)

void init_MUTEX_LOCKED(struct semaphore *sem); 初始化化一个信号量,等同于 sema_init(struct semaphore *sem, 0)

这两个宏函数定义并初始化信号量

DECLARE_MUTEX(name);

DECLARE_MUTEX_LOCKED(name);
http://www.embedu.org/Column/Column240.htm 改地址说明了进程上下文和中断上下文的概念及其区别

获得信号量:

1、 void down(struct semaphore *sem); 获得信号量,得不到的话会导致休眠,而且如果一直获取不到信号量的话,该进程一直休眠,

不能被外界信号所打断(这里的信号我认为指的是 sigint等信号)。

2、 void down_interruptible(struct semaphore *sem) ; 可以被信号中断,故大部分情况都使用这个函数,如果因为信号导致函数返回,这个

时候函数的返回值 非 0!!

if(down_interruptible(&sem))

{

return -ERESTARSYS;

}

上述两个 会有休眠,所以不能出现在 中断 上下文

3、int down_trylock(struct semaphore *sem)

尝试获取信号量,并立即返回,获得返回0, 否者返回非 0,不会导致 休眠, 故可以在 中断上下文中使用

释放信号量:

void up(struct semphore *sem);

信号量初始化为0时, 用于同步。 同步意味着一个执行单位的继续执行需要等待另一个执行单位完成某事,保证了执行的先后顺序

4、完成量Completion

1、struct completion my_completion; 定义一个completion

2、初始化 :init_completion(&my_completion);

DECLARE_COMPLETION(my_completion); 定义并初始化

3、 等待完成量

void wait_for_completion(struct completion *c);

这个函数是一个不可被打断的等待,如果你的代码调用wait_for_completion并且没有人完成这个任务,结果会是一个不可杀死的进程

4、 唤醒信号量

void complete( &mycompletion );

void complete_all( &my_completion );

如果多与一个线程 在等待同一个completion事件,complete唤醒一个线程,complete_all唤醒所有的线程。

INIT_COMPLETION(struct completion c) ;completion机制没有down之类的 释放函数, 故 在 一次使用之后必须使用该函数初始化。然后重新使用

void complete_and_exit(struct completion *c, long retval); 当模块准备好被清理时,该甘薯告知线程退出并且结束等待。 故该函数放在 模块卸载函数里。

10.28

---------------------------------------------------------------------------------------------------------------------------------------------------

IO设备的阻塞与非阻塞

1、 阻塞式的操作会使进程进入休眠状态, 因此,必粗确保有个地方可以唤醒 休眠的进程, 唤醒进程的地方最大可能

发生在中断里面,因为硬件资源获得同时旺旺会伴随着一个中断。

在队列中常使用的一个变量是current ,表示当前进程

2、等待队列头

a、 定义等待队列头 : wait_queue_head_t myqueue_t;

b、 初始化队列头 : init_waitqueue_head(&myqueue_t);

struct __wait_queue_head{

spinlock_t lock; //一个队列头在很多进程中共同使用使用,是一个共享资源,数据结构是一个链表, 用自旋锁来 保证唯一修改

struct list_head task_list; //其他的进程都有一个和当前进程绑定在一起的 队列结构,形成一个链表

};

typedef struct __wait_queue_head wait_queue_head_t;

struct list_head{

struct list* next;

struct list* prev;

}

说明了 队列头中的成员变量是 一个 双向链表

声明并初始化一个队列头: DECLARE_WAIT_QUEUE_HEAD(myqueue_t);

3、等待队列节点元素

struct _wait_queue{

unsigned iny flags;

#define WQ_FLAG_EXCLUSIVE 0x01

void *private;

wait_queue_fun_t fuc;

struct list_head task_list;

};

typedef struct __wait_queue wait_queue_t ;

DECLARE_WAITQUEUE(wq, current);

等待队列节点中的 成员 flags如果为0时,表示该进程是一个 非互斥进程 ,如果为1 表示是一个互斥进程。

通过核心函数__wake_up_common我们可以看出,当指定唤醒的互斥进程数为0(nr_exclusive == 0)时,此函数会唤醒处在同一等待队列上的所有节点。

当指定唤醒的互斥进程数为非0时,此函数会唤醒同一等待队列上的所有的非互斥进程和一个互斥进程。我们通常将等待相同条件的等待进程加入到同一个等待队列上。

如果等待队列上有互斥进程,那么依据互斥对临界区的访问规则,我们只需唤醒其中一个互斥进程即可,唤醒多个互斥进程的结果还是会让他们因抢占临界区而进入竞态,

如果等待队列上没有互斥进程,那么在执行wake_up系列函数时,我们全唤醒即可。

上述的应用在与 ,当写满了之后,写进程被阻塞,读进程工作之后,唤醒写进程,如果此时的写进程有多个,此时如果在多个写进程中设置为1,那么在唤醒的时候,就只会

唤醒其中一个,至于唤醒那个,目前就不知道。 此时,我就不需要加入互斥体来防止竞争。

另外在wait__event系列函数中, 会有一个无线循环用来再次检查资源是否可用, 那么此时,我可以在 加入一个 资源是否可用的状态标识,但是还是必须使用自旋锁来保证不会同时检测。但是这种做法没有什么意义。
http://www.cnblogs.com/watson/p/3543320.html
你的进程不能睡眠除非你确信其他进程在某处将唤醒它,做唤醒工作的代码必须能够找到你的进程来做他的工作。(这个错误就发生我的打印机程序中,会发生写进程一直卡在那里,因为我的读进程被杀了)

Tasks:

1、上次的打印机驱动程序,需要做一下测试。 在应用测试程序中,读取某个文件并打印出来。手动控制现在是否写, 让后在 另一个程序中,读出来。

2、这个阻塞的程序,有两个方面,已是在系统给出的函数中,借助它的自旋锁来实现读写互斥。二是利用上述flags来实现。

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

select 和poll 应用层轮询操作:(此时 设备应该以非阻塞方式打开,即fd = open("/dev/globalfifo", O_RDONLY | O_NONBLOCK);)

在写网络程序时,我们当时做过套接字 的轮询,在 字符设备中,有时也需要使用轮询操。

1.1、select 和poll是 系统调用函数,为了支持这两个函数 , 在驱动层,我们需要做一下操作。

函数操作结构体中 必须实现 unsigned int(*poll)(struct file *filp, poll_table *wait);

上述函数必须实现poll_wait(struct file *, wait_queue_head_t *, poll_table *);

在前面我们使用的等待队列 , 它 使用于 阻塞的使用设备, 因为阻塞意味着进程睡眠, 而等待队列就是用来唤醒睡眠的工具。 而轮询就是使用在非阻塞下的 常用操作。

Linux 异步通知编程 (异步通知 使得在访问设备时,由驱动通知应用程序进行访问, 这样非阻塞IO无需使用轮询设备是否可用,在阻塞访问下,异步通知类似于‘中断’ )

1、 Linux信号

1.1、信号的接收

void ( *signal(int signum, void (*handler))(int)) (int) ;

上述函数分解为如下:

typedef void (*sighandler_t)(int);

signhandler_t signal(int signum, sighandler_t handler);

------------------------------------------------------------------------------------------------------------

在 linux环境开发 的时候, 当时也接触到了 信号的使用, 我们没有使用fcntl 来设置什么参数,那么为什么会有这些差异列?

我个人认为是因为一下原因:

在以前的应用程序中,我们没有涉及到 文件描述符,,此时当前进程设置信号,然后接受信号,我们不需要设置F_SETOWN控制命令

因为在 驱动异步通知时, 设备文件的拥有者 应该是不确定的,也就是说设备文件驱动释放的信号此时是 不知道该发给谁。

疑问: 进程打开一个文件时,该进程会维护一个指向该文件的 内核结构应该就是struct file。 那么为什么还需要去设置设备文件的拥有者为

本进程咧? 因为本身已经唯一确定了啊。

根据其他人的说法,如果不指定的话,那么内核向文件所在的进程或进程组发送信号.

那么此时设置 F_SETOWN的意义是什么? 而且如果有多个 读进程,那么岂不是只能异步通知到一个进程?

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------

2、 aio_suspend 函数其实也是可以 同时发起大量的IO传输操作,调用者提供了一个aiocb引用列表,其中任何一个完成都会导致aio_suspend函数的返回

,但是在请求返回之前, 进程是一直休眠的。(这个可能不止一次系统调用,因为可能出现读 、写不同的请求)

aio_suspend(const struct aiocb *const cblist[], int n, const struct timespec *timeout);

3、 lio_listio函数也可用于同时发起大量的IO操作。(一次系统调用也就是一次内核上下文的切换(请求的操作要么是读要么是写))。通过mode参数LIO_WAIT或者LIO_NOWAIT

来设置是否阻塞。前面一个设置是阻塞, 该函数会等待所有的请求都完成为止。\

int lio_listio(int mode, struct aiocb *list[], int nent, struct sigevent *sig);

----------------------------------------------------------------------------------------------------------------------------------

疑问: liotio或者是 aio_suspend函数使用信号来作为AIO的通知 有一下几个疑惑:

1、 在驱动层,我们该如何发送信号? 上层如何区分到底是谁发的信号,如何确定。 怎么判断是否接收完成

2、

--------------------------------------------------------------------------------------------------------------------------------------------------------

中断和定时器

1、中断分为向量中断 和非 向量中断:

向量中断 由 硬件提供中断服务程序入口地址, 非向量中断由软件提供中断服务程序入口地址

硬中断时外部设备对CPU的中断,软中断通常是硬中断服务程序对内核的中断,信号则是由内核或者其他进程对某个进程的中断。

2、申请中断

int request_irq (unsigned int irq, void (*handler)(int irq, void *dev_id, struct pt_regs *regs),

unsigned long irqflags, const char * devname, void *dev_id );

同步中断: 指令执行时由cpu控制单元产生的, 被称为 异常(exception) (貌似系统调用产生软中断就是通过这种方式)

异步中断: 其他硬件设备异常cpu时钟信号随机产生的 (IRQ) 称为中断

中断 和异常

中断:

1、可屏蔽中断 : 被送到处理器的INTR引脚,所有的I/O 设备发出的IRQ均可引起可屏蔽中断。

2、不可屏蔽中断 : 被送到处理器的NMI,中断不能关闭,只有几个紧急的事件,比如硬件故障。

异常:

Processor-detected exceptions 可以分为3类:

1、故障(fault) 重新执行一起故障的指令

2、陷阱( Trap) 下面的编程异常使用

3、异常结束(Abort) 终止相应的进程

编程异常,由编程者请求时发生,是由int 或者int3指令触发。一般有两个用途: 执行系统调用 或者 给调试程序通报特定事件

中断和异常向量:

从0 ~ 31的向量对应于 异常和不可屏蔽中断

从32 ~ 47的向量被分配给可屏蔽中断, 即由IRQ引起的中断

48 ~ 255 的向量 用来标识软中断,linux只使用了128 即0x80向量 用来实现系统调用

linux内核驱动模型

疑问1: kset依赖kobj维护引用计数,kset的引用计数实际上就是内嵌的Kobject对象的引用计数 , 但是内嵌的kobj对象 是 下面多个kobj的parent。 那么这个引用计数究竟指的是什么列?
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: