0.基础知识
1.进程级的文件描述符表
每一个文件描述符会与一个打开文件相对应,同时,不同的文件描述符也会指向同一个文件。相同的文件可以被不 同的进程打开也可以在同一个进程中被多次打开。系统为每一个进程维护了一个文件描述符表,该表的值都是从0 开始的,所以在不同的进程中你会看到相同的文件描述符,这种情况下相同文件描述符有可能指向同一个文件,也 有可能指向不同的文件。具体情况要具体分析,要理解具体其概况如何,需要查看由内核维护的3个数据结构。
2.打开文件表(file结构体)
内核对所有打开的文件的文件维护有一个打开文件表(file结构体),并将file结构体中各条目称为打开文件句柄 。 一个file结构体句柄存储了与一个打开文件相关的全部信息,如下所示:
当前文件偏移量(调用read()和write()时更新,或使用lseek()直接修改)
打开文件时所使用的状态标识(即,open()的flags参数)
文件访问模式(如调用open()时所设置的只读模式、只写模式或读写模式)
与信号驱动相关的设置
对该文件i-node对象的引用
文件类型(例如:常规文件、套接字或FIFO)和访问权限
一个指针,指向该文件所持有的锁列表
文件的各种属性,包括文件大小以及与不同类型操作相关的时间戳
3.文件系统的i-node表(inode结构体)
下图展示了文件描述符、打开的文件表以及i-node之间的关系,图中,两个进程拥有诸多打开的文件描述符。
一.几个shell命令
cat /proc/devices 显示已经存在的 组设备号+驱动名字
ismod 名字.ko 安装驱动
rmmod 名字.ko 卸载驱动
lsmod 显示有的驱动
ismod 名字.ko 安装驱动
rmmod 名字.ko 卸载驱动
lsmod 显示有的驱动
在/sys/class/下面有以驱动为名的文件夹,进入这个文件夹ls可以看到这个驱动包含的设备节点名
二.几个驱动常用函数讲解(先阅读0基础知识部分)
1.static int first_drv_open(struct inode *inode, struct file *file)
static int s3c24xx_leds_open(struct inode *inode, struct file *file)
第一个参数:打开文件表里面的inode
结构体的指针
第二个参数:打开文件表指针(file
结构体指针)
2.static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
static ssize_t s3c24xx_leds_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
第一个参数:打开文件表指针(file结构体指针)
第二个参数:用户空间buff地址
第三个参数:接收数据的长度
第四个参数:
static ssize_t
first_drv_read(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
static ssize_t
s3c24xx_leds_read(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
第一个参数:打开文件表指针(file结构体指针)
第二个参数:用户空间buff地址
第三个参数:传送数据的长度
第四个参数:
3.
unsigned long
copy_from_user(void *to, const void __user *from, unsigned long n)
从用户空间拷贝数据到内核空间
第一个参数:内核空间地址
第二个参数:用户空间地址
第三个参数:需要拷贝数据的长度
4.unsigned long copy_to_user(void *to, const void __user *from, unsigned long n)
从内核空间拷贝数据到用户空间
第一个参数:用户空间地址
第二个参数:内核空间地址
第三个参数:需要拷贝数据的长度
5.int register_chardev (unsigned int major, const char *name, struct file_operations *fops)
功能:注册设备
返回:组设备号
第一个参数:组设备号,写0则自动分配组设备号(返回值也是这个)
第二个参数:name 设备名称
第三个参数:
fops file_operations 结构体变量地址(指针)。
6.unregister_chrdev(unsigned int major,const char *name);
功能:卸载设备
第一个参数:组设备号
第二个参数:设备名称
7.struct class *class_create(struct module *owner, const char *name)
内核中定义了struct class结构体,一个struct class 结构体类型变量对应一个类,内核同时提供了class_create()函数,可以用它来创 建一个类,这个类存放于sysfs下面,一旦创建了这个类,再调用device_create()函数在/dev目录下创建相应的设备节点。这样,加载模块 的时候,用户空间中的udev会自动响应device_create()函数,去/sysfs下寻找对应的类而创建设备节点。
说明:
在2.6较早的内核版本中,device_cerate()函数名称是calss_device_create()。
第一个参数:
结构体struct module在内核中代表一个内核模块,通过insmod(实际执行init_module系统调用)把自己编写的内核模块插入内核 时,模块便与一个 struct module结构体相关联,并成为内核的一部分。
我们插入一个内核模块,一般会使用工具insmod,该工具实际上调用了系统调用init_module,在该系统调用函数中,首先调用 load_module,把用户空间传入的整个内核模块文件创建成一个内核模块,返回一个struct module结构体。内核中便以这个结构体代表这个内核模块。
第二个参数:name是模块的名字,一般会拿模块文件的文件名作为模块名。它是这个模块的一个标识。
8.
struct class_device *class_device_create(struct class *cls,
struct class_device *parent
dev_t devt,
struct device *device,
const char *fmt, ...)
9.class_device_unregister
10.class_destroy
11.static inline void __iomem *ioremap(unsigned long offset, unsigned long size)
将物理地址转换为虚拟地址,返回虚拟地址
第一个参数:物理地址
第二个参数:需要空间的大小
12.unregister_chrdev(major, "first_drv"); // 卸载设备
三.LED驱动程序实现(驱动代码)
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
/*建立major组设备号对应的设备节点*/
static struct class *firstdrv_class;//一个类
static struct class_device *firstdrv_class_dev;//在上面类下建立设备
volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;
static int first_drv_open(struct inode *inode, struct file *file)
{
//printk("first_drv_open\n");
/* 配置GPF4,5,6为输出 */
*gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2)));
*gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2)));
return 0;
}
static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
int val;
//printk("first_drv_write\n");
copy_from_user(&val, buf, count); // copy_to_user();
if (val == 1)
{
// 点灯
*gpfdat &= ~((1<<4) | (1<<5) | (1<<6));
}
else
{
// 灭灯
*gpfdat |= (1<<4) | (1<<5) | (1<<6);
}
return 0;
}
static struct file_operations first_drv_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = first_drv_open,
.write = first_drv_write,
};
int major;//组设备号
/*
major作用
在VFS(虚拟文件系统)中实际是用一个数组来管理文件,而这个数组就是以组设备号来所谓索引
在注册驱动的时候register_chrdev函数会按照major作为索引在VFS对应位置填入file_operation
*/
static int first_drv_init(void)
{
major = register_chrdev(0, "first_drv", &first_drv_fops); // 注册, 告诉内核.写0是系统自动分配组设备号(找空缺的组设备号)
/*建立major组设备号对应的设备节点*/
firstdrv_class = class_create(THIS_MODULE, "firstdrv");//创建一个类,<span style="font-family: Arial, Helvetica, sans-serif;">THIS_MODULE,</span><span style="font-family: Arial, Helvetica, sans-serif;">这是一个宏.</span><span style="font-family: Arial, Helvetica, sans-serif;">推向编译模块时自动创建的__this_module变量 </span>
firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz 在上面创建的类下创建设备*/
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
gpfdat = gpfcon + 1;
return 0;
}
static void first_drv_exit(void)
{
unregister_chrdev(major, "first_drv"); // 卸载
class_device_unregister(firstdrv_class_dev);
class_destroy(firstdrv_class);
iounmap(gpfcon);
}
module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");