加入收藏 | 设为首页 | 会员中心 | 我要投稿 温州站长网 (https://www.0577zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 服务器 > 搭建环境 > Linux > 正文

Linux 驱动程序之字符驱动

发布时间:2022-11-04 13:03:01 所属栏目:Linux 来源:网络
导读: Linux 驱动程序之字符驱动
系统调用、内核、驱动程序的关系

主要驱动类型:
-> 字符设备 ( c)
-> 块设备 (b)
-> 网络设备 (ifconfig)
字符设备和块设备的主要区别是 : 在对字符 设备发出读

Linux 驱动程序之字符驱动

系统调用、内核、驱动程序的关系

在这里插入图片描述

主要驱动类型:

-> 字符设备 ( c)

-> 块设备 (b)

-> 网络设备 (ifconfig)

字符设备和块设备的主要区别是 : 在对字符 设备发出读 / 写请求时,实际的硬件 I/O 一 般就紧接着发生了,块设备则不然,它利用 一块系统内存作缓冲区 主设备号 , 从设备号 用 ll 命令可以观察,设备号规范在

docmention/devices.txt 中

模块

对于每一个内核模块来说,必定包含下面两个函数: int init_module

() : 这个函数在插入内核时启动,在内核中注册一定的功能函数,或者用

他的代码代替内和中某些函数的内容。

int cleanup_module() :当内核模块卸载时调用,它能将模块从内核中清

除。 (#include )

在这里插入图片描述

编译 :Makefile(-D__KERNEL__ -DMODULE)

在包含 module.h 前定义 NO_VERSION

使用模块: insmod,lsmod,rmmod,depmod

字符型驱动程序

驱动程序的初始化、卸载

设备驱动程序所提供的入口点,在设备驱动程序初始化的时候向系统进行登记,以便系统在适当的时候调用。LINUX系统里,通过调用

register_chrdev向系统注册字符型设备驱动程序。

register_chrdev定义为:

 #include
 #include  
 int register_chrdev(unsigned int major, const char*name, struct file_operations *fops);

其中,major是为设备驱动程序向系统申请的主设备号,如果为0则系统为此驱动程序动态地分配一个主设备号。name是设备名。fops就是对各个调用的入口点的说明。此函数返回0表示成功。返回-EINVAL表示申请的主设备号非法,一般来说是主设备号大于系统所允许的最大设备号。返回-EBUSY表示所申请的主设备号正在被其它设备驱动程序使用。如果是动态分配主设备号成功,此函数将返回所分配的主设备号。如果

register_chrdev操作成功,设备名就会出现在/proc/devices文件里。

初始化部分一般还负责给设备驱动程序申请系统资源,包括内存、中断、时钟、I/O端口等,这些资源也可以在open子程序或别的地方申请。在这些资源不用的时候,应该释放它们,以利于资源的共享。

驱动程序的功能实现:

两个重要的数据结构

struct file { 
mode_t f_mode;/*标识文件是否可读或可写,FMODE_READ 或 FMODE_WRITE*/ 
dev_t f_rdev; /* 用于/dev/tty */ 
off_t f_pos; /* 当前文件位移 */ 
unsigned short f_flags; /* 文件标志,如 O_RDONLY、O_NONBLOCK 和 O_SYNC */ 
unsigned short f_count; /* 打开的文件数目 */ 
unsigned short f_reada; 
struct inode *f_inode; /*指向 inode 的结构指针 */ 
struct file_operations *f_op;/* 文件索引指针 */ 

linux 查看文件编码_linux编码_linux修改字符集编码

};

struct file_operations { 
loff_t (*llseek) (struct file *, loff_t, int); 
ssize_t (*read) (struct file *filp, char *buff, size_t count, loff_t *offp); 
ssize_t (*write) (struct file *filp, const char *buff, size_t count, loff_t *offp); 
int (*readdir) (struct file *, void *, filldir_t); 
unsigned int (*poll) (struct file *, struct poll_table_struct *); 
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned 
long); 
int (*mmap) (struct file *, struct vm_area_struct *); 
int (*open) (struct inode *, struct file *); 
int (*flush) (struct file *); 
int (*release) (struct inode *, struct file *); 
int (*fsync) (struct file *, struct dentry *); 
int (*fasync) (int, struct file *, int); 
int (*check_media_change) (kdev_t dev); 
int (*revalidate) (kdev_t dev); 
int (*lock) (struct file *, int, struct file_lock *); 
};

在这里插入图片描述

Linux中的I/O子系统向内核中的其他部分提供了一个统一的标准设备接口,这是通过include/linux/fs.h中的struct file_operations来完成的

(1) lseek,移动文件指针的位置,显然只能用于可以随机存取的设备

(2) read,进行读操作,参数buf为存放读取结果的缓冲区,count为所要读取的数据长度。返回值为负表示读取操作发生错误,否则返回实际读取的字节数。对于字符型,要求读取的字节数和返回的实际读取字节数都必须是inode->i_blksize的的倍数

(3)write,进行写操作,与read类似。

(4)readdir,取得下一个目录入口点,只有与文件系统相关的设备驱动程序才使用

(5)select,进行选择操作,如果驱动程序没有提供select入口,select操作将会认为设备已经准备好进行任何的I/O操作

(6)ioctl,进行读、写以外的其它操作,参数cmd为自定义的的命令

(7)mmap,用于把设备的内容映射到地址空间,一般只有块设备驱动程序使用

(8)open,打开设备准备进行I/O操作。返回0表示打开成功,返回负数表示失败。如果驱动程序没有提供open入口,则只要/dev/driver文件存在就认为打开成功

(9)release,即close操作

read:

在这里插入图片描述

阻塞I/O模式与非阻塞I/O模式

当执行读写I/O的系统调用时,执行或等待I/O操作的过程中进程阻塞,直到I/O操作完成,调用才结束,唤醒进程继续向下进行。缺省情况下套接口的读写操作就是阻塞I/O方式。阻塞I/O模式的流程图如下:

在这里插入图片描述

设备驱动程序通过调用request_irq函数来申请中断,通过free_irq来释放中断。

开关中断:disable_irq(intirq);enable_irq(intirq);

中断处理注意的方面,中断不处于进程上下文,处于中断模式

1.不允许访问用户空间,没有到达与进程关联的用户空间路径

2.current指针在中断模式下是无效的

3.不能执行睡眠。不可以调用schedule或者sleep_on; kmalloc(…,GFP_KERNEL)

4.中断处理函数不能太长

作为系统核心的一部分,设备驱动程序在申请和释放内存时不是调用

malloc和free,而代之以调用kmalloc和kfree

高1G(内核空间)的内存分配

物理区 || 8M隔离 || vmalloc区 || 8K隔离 || 4M的高端映射区 || 固定映射区 || 128K保留区

申请与释放IO:check_region,request_region,release_region

在设备驱动程序里,一般都需要用到计时机制。在LINUX系统中,时钟是由

系统接管,设备驱动程序可以向系统申请时钟。与时钟有关的系统调用有:

#include

#include

void add_timer(struct timer_list * timer);

int del_timer(struct timer_list * timer);

时间流:

Linux的时间系统

一般PC机中有两个时钟,分别是RTC时钟和OS时钟。OS时钟产生于主板上的定时/计数芯片,其基本单位就是计数芯片的计数周期。在开机时通过RTC来初始化芯片。定时/计数芯片的每一个输出脉冲周期叫做一个“时钟滴答”,计算机中的时间就是以“滴答”为单位的 ,每一次滴答时间会加一。根据当前的滴答数就可以得到秒等其他单位

计时器大概每10ms向CPU送入一个脉冲,就可以触发一个时钟中断。系统利用时钟中断维持系统时间,促使进程和环境发生切换,进行记帐等工作以确定动态优先级等等。

中断:

arm 的中断向量表可以放在地址 0 开始linux编码,也可以指定为 0xFFFF0000 开始。 中断发生后的执行过程是:

R14_irq = 下一条指令的地址 Spsr_irq = CPSR

Cpsr[4:0] = 0b10010 ; 表示进入 irq

Cpsr[7] = 1 ; 关闭 irq, 防止中断嵌套

If high vector table then

Pc = 0xFFFF0018

Else

Pc = 0x00000018

Linux 中的中断向量表是在 void __init trap_init(void) //

arch/arm/kernle/trap.c

中安装的,其中调用汇编函数 __trap_init((void *)vectors_base()); 宏 vectors_base() 决定项量表是从 0 开始还是从 0xFFFF0000 开始

保存中断现场用宏 get_irqnr_and_base 取得中断号代码 do_IRQ ,此时 R0 为中断号, R1 指向堆栈中保存的寄存器起始地址 。do { // 逐个处理中断处理 handler 链表中的每个函数

status |= action->flags;

action->handler(irq, action->dev_id, regs);

action = action->next; } while (action);if (softirq_pending(cpu))

// 执行软中断处理过程 do_softirq();

软中断概况:

软中断是利用硬件中断的概念,用软件方式进行模拟,实现宏观上的异步 执行效果。

-> bottom half

在 Linux 内核中, bottom half 通常用“ bh” 表示,最初用于在特权级较 低的上下文中完成中断服务的非关键耗时动作,现在也用于一切可在低优 先级的上下文中执行的异步动作。

-> task queue

原始的 bottom half 机制有几个很大的局限,最重要的一个就 是个数限制在 32 个以内,随着系统硬件越来越多,软中断的应用范围越来 越大,这个数目显然是不够用的,而且,每个 bottom half 上只能挂接一 个函数,也是不够用的。因此,在 2.0.x 内核里,已经在用 task queue ( 任务队列)的办法对其进行了扩充

-> tasklet

之所以引入 tasklet ,最主要的考虑是为了更好的支持 SMP ,提高 SMP 多个

CPU 的利用率:不同的 tasklet 可以同时运行于不同的 CPU 上。

(编辑:温州站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!