linux设备
来源:互联网 发布:淘宝运营托管公司 编辑:程序博客网 时间:2024/05/18 17:04
PROT_EXEC: 映射区可被执行PROT_READ: 映射区可被读取PROT_WRITE: 映射区可被写入
vMAP_SHARED: 写入映射区的数据会复制回文件, 且允许其他映射该文件的进程共享。vMAP_PRIVATE: 对映射区的写入操作会产生一个映射区的复制(copy-on-write), 对此区域所做的修改不会写回 原文件。
字符设备驱动总体框架:
1. 合成获取的设备号
2. 分配设备号
3. 注册设备
4. 卸载驱动
在XXX_init(void)中合成和分配设备号:
1. 合成设备号:MKDEV(major, minor);
2. 分配设备号:如果静态分配,则register_chrdev_region;
如果动态分配,则alloc_chrdev_region,并且利用MAJOR(dev_t xxx)分离出主设备号
3. 调用XXX——setup_cdev(void)注册设备函数
在XXX_setup_cdev(void)中注册设备:
1. 合成设备号:MKDEV(major,minor);
2. 分配空间: cdev_alloc(可选);
3. 初始化cdev:cdev_init(...);
4. 填充所属者:dev.cdev.owner = THIS_MODULE;
5. 添加到内核:cdev_add(...);
在XXX_exit卸载函数中的相关操作:
1. 删除cdev结构体:cdev_DEL(...);
2. 卸载设备号: unregister_chrdev_region(...);
填充file_operations,用到什么函数,填充什么函数。
实现file_operations中所填充的函数。
-----------------------------------设备号相关操作-----------------------------------
/*
通常而言,主设备号标识设备对应的驱动,次设备号由内核使用,用于正确确定设备文件所指的设备文件所指的设备,
我们可通过次设备号获得指向内核设备的直接指针,也可将次设备号当作设备本地数组的引索。
同一类设备使用相同备号,不同类的设备使用不同的主设备号,用次设备号来描述使用该驱动的设备的序号,
序号一般从0开始 ,在调用cdev_add()函数向系统注册字符设备之前,该首先分配设备号
可通过命令:cat /proc/devices 来查看系统使用的设备号
*/
MAJOR(dev_t dev); //将设备编号转换为主设备号 ,dev_t 是 32 位的数, 12 位用作主编号, 20位用作次编号.
MINOR(dev_t dev); //将设备编号转换为次设备号
MKDEV(int major, int minor)//将主设备号转换为设备编号,minor一般取0,
可查看Documentation/device.txt文件确定可用的设备号
int register_chrdev_region(dev_t first,unsigned int count, char *name)
//分配从first开始count个设备编号,first为预先设定的设备号,
成功返回0,失败返回负的错误码,name 是应当连接到这个编号范围的
//设备的名子; 它会出现在 /proc/devices 和 sysfs 中.
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor,unsigned int count, char *name)
//动态分配设备号, firstminor通常为0,分配的结果保存在第一个参数里
void unregister_chrdev_region(dev_t first, unsigned int count) //卸载设备号
//驱动程序需要将设备编号和内部函数连接起来,这些内部函数用来实现设备的操作
//一般采用一些方式分配设备号:
if(xxx_major)
{
xxx_devno=MKDEV(xxx_major,xxx_minor); //将主设备号和此设备号转换成设备编号
result=register_chrdev_region(xxx_devno,1,DEVICE_NAME);
//在指定设备主次设备号的情况下,通过这种方式分配设备编号
}
else
{
result=alloc_chrdev_region(&xxx_devno,0,1,DEVICE_NAME);
//如果没有指定主次设备号,则采用这种方式进行动态分配主
xxx_major=MAJOR(led_devno);
}
--------------------------------file_operations结构----------------------------------
struct file_operations { //文件操作
struct module *owner;
//它并不是一个操作,它指向“拥有”该结构的模块的指针,内核使用这个字段以避免在模块操作正在被使用时卸载该模块,
几乎所有的情况下,该成员被初始化为
//THIS_MODULE,它是定义在<linux/module.h>中的一个宏
loff_t (*llseek) (struct file *file, loff_t offset, int origin);
//llseek用来修改文件的当前读写位置,并将新位置(正的)作为返回值返回,
参数loff_t是一个长偏移量,即使在32位平台上也至少
//占用64位的数据宽度,出错返回负的返回值,如果这个函数指针为NULL,
对seek的调用将会以某种不可预期的方式修改file结构中的位置计数器
ssize_t (*read) (struct file *filp, char __user *buf, size_t count, loff_t *f_pos);
//用来从设备读取数据,该函数指针被赋为NULL时,将导致read系统调用出错并返回-EINVAL,
函数返回非负值表示成功读取的字节数
//注意更新文件的位置f_pos
//如果返回值传输的字节数小于count,则应该重新传输数据
//返回0表示已经到达文件尾
//如果数据暂时没有到达则应该阻塞
ssize_t (*write) (struct file *filp,const char __user *buf, size_t count, loff_t *f_pos);
//向设备发送数据,如果没有这个函数,将导致write系统调用出错并返回-EINVAL,函数返回非负值表示成功写入的字节数
//注意更新文件的位置f_pos
ssize_t (*aio_read) (struct kiocb *iocb, const struct iovec *iov,unsigned long nr_segs, loff_t pos);
//初始化一个异步的读取操作---即在函数返回之前可能不会完成的读取操作,如果该方法为NULL,所有的操
//作将通过read(同步)处理
ssize_t (*aio_write) (struct kiocb *iocb, const struct iovec *iov,unsigned long nr_segs, loff_t pos);//初始化一个异步的写入操作
unsigned int (*poll) (struct file *filp, poll_table *wait);
//poll方法是poll、epoll、select这三个系统调用的后端实现,这三个系统调用可用查询某个或多个文件描述符上读写是否阻塞,poll方法应该返回一位掩码,用来指定非阻塞的读取或写入是否可能,并且也会向内核提供将调用进程至于休眠状态直至I/O变可能时的信息,如果驱动程序将poll方法定义为NULL,则设备被认为即可读也可写,并且不会阻塞
int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); //系统调用ioctl提供了一钟执行设备特定命令的方法(如格式化磁盘,这个既不是读操作也不是写操作),另外,内核 还能识别一部分ioctl命令,而不必调用fops表中的ioctl,如果设备不提供ioctl入口点,对于任何内核未预先定义的请求,ioctl系统调用将返回错误
long (*unlocked_ioctl) (struct file *filp, unsigned int ioctl, unsigned long arg);
//不使用BLK的文件系统,将使用此种函数指针代替ioctl
long (*compat_ioctl) (struct file *file, unsigned cmd, unsigned long arg);
//在64位系统上,32位的ioctl调用,将调用此函数指针代替
int (*mmap) (struct file *file, struct vm_area_struct *vma);
//mmap用于请求将设备内存映射到进程空间,如果设备没有实现这个方法,那么mmap系统调用将返回-ENODEV
int (*open) (struct inode *inode,struct file *filp);
//尽管这是对设备文件的第一个操作,然而却并不要求驱动程序一定要声明一个相应的方法,如果这个入口为NULL,设备的打开操作永远成功,但系统不会通知驱动程序
int (*flush) (struct file *filp, fl_owner_t id);
//对flush操作的调用发生在进程关闭设备文件描述符副本的时候,它应该执行(并等待)设备上尚未完结的操作,目前,flush仅仅用于少数几个驱动程序 ,如果flush被设置为NULL,内核将简单低忽略用于的应用程序请求
int (*release) (struct inode *inode, struct file *filp);
//当file结构被释放时即关闭设备文件,将调用这个操作,与open相仿,也可以将release设置为NULL
int (*fsync) (struct file *filp, struct dentry *dentry, int datasync);
//该方法是fsync系统调用的后端实现,用户调用它来刷新待处理的数据,如果驱动程序没有实现这一方法,fsync系统调用将返回-EINVAL
int (*aio_fsync) (struct kiocb *iocb, int datasync); //这是fsync方法的异步版本
int (*fasync) (int fd, struct file *filp, int mode);
//这个操作用来通知设备其FASYNC标志发生了变化,如果设备不支持异步通知,该字段可以使NULL
int (*lock) (struct file *filp, int cmd, struct file_lock *fl); //lock方法用于实现文件锁定,锁定是常规文件不可缺少的特性,但设备驱动程序几乎不会实现这个方法
unsigned long (*get_unmapped_area)(struct file *file,unsigned long addr, unsigned long len,unsigned long pgoff,unsigned long flags);
//该方法的目的是在进程的地址空间找到一个合适的位置,以便将底层设备中的内存映射到该位置,大部分驱动程序可设置改方法为 NULL
int (*check_flags)(int); //该方法允许模块检查传递给fcntl调用的标志
ssize_t (*splice_write)(struct pipe_inode_info *pipe, struct file *out,loff_t *ppos, size_t len, unsigned int flags); //由VFS调用,将管道数据粘接到文件
ssize_t (*splice_read)(struct file *in, loff_t *ppos,struct pipe_inode_info *pipe, size_t len,unsigned int flags); //由VFS调用,将文件数据粘接到管道
};
struct file_operations xxx_fops={
.owner=THIS_MODULE,
.open=xxx_open,
.read=xxx_read,
.write=xxx_write,
.ioctl=xxx_ioctl,
.poll=xxx_poll,
.release=xxx_close,
......
};
-----------------------------file 结构---------------------------------------------
/*
*文件结构代表一个打开的文件(文件描述符). (它不特定给设备驱动; 系统中每个打开的文件有一个关联的 struct file 在内核空间). 它由内核在 open 时创建, 并传递 给在文件上操作的任何函数, 直到最后的关闭. 在文件的所有实例都关闭后,内核释放这个数据结构
.*/
struct file { //file结构,指向该结构的指针为filep/file(文件指针)
/*
* fu_list becomes invalid after file_free is called and queued via
* fu_rcuhead for RCU freeing
*/
union {
struct list_head fu_list;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
#define f_dentry f_path.dentry
#define f_vfsmnt f_path.mnt
const struct file_operations *f_op;
//与文件相关操作,内核在执行open操作时对这个指针赋值,以后需要处理这些操作时就读取这个指针,filep->f_op中的值决不会为方便引用而保存起来 ,也就是说,我们可以在任何时候修改文件的关联操作,在返回给调用者之后,新的操作方法就会立即生效
spinlock_t f_lock; /* f_ep_links, f_flags, no IRQ */
atomic_long_t f_count;
unsigned int f_flags;
//文件标志,如O_RDONLY O_NONBLOCK 和O_SYNC,为了检查用户请求的是否是非阻塞式的操作,
//驱动程序需要检查O_NONBLOCK标志,而其他标志很少用到,所有的标志在<linux/fcntl.h>中
fmode_t f_mode;
//文件模式,它通过FMODE_READ和FMODE_WRITE位来标识文件是否可读或可写或可读写,驱动程序无需为此而作额外的判断
loff_t f_pos;
//当前的读/写位置,loff_t是一个64位数(gcc中long long定义),如果驱动程序需要知道文件中的当前位置,可以读取这个值,但不需要修改它,read/write
//会使用它们接收到最后的那个指针参数来更新这一位置,而不是直接对filep->f_pos进行操作
struct fown_struct f_owner;//用来保存异步通知的属主进程的进程的ID号,目的是为了让内核知道应该通知哪一个进程
const struct cred *f_cred;
struct file_ra_state f_ra;
u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;
//open系统调用在调用驱动程序的open方法之前将这个指针设置为NULL,private_data在跨系统调用时保存状态信息是非常有用的资源,我们大部分示例都使用它 ,struct xxx_dev *xxx_dev; dev=container_of(inode->i_cdev,struct xxx_dev,cdev) //xxx_dev是我们自己定义的设备私有结构体,其中包含 struct cdev cdev成员,这样今后就可以方便对该指针的访问了,container_of是通过结构体内部成员的地址来获取整个结构体的地址
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
#ifdef CONFIG_DEBUG_WRITECOUNT
unsigned long f_mnt_write_state;
#endif
};
--------------------------------------------------------------------------------------
---------------------------------inode结构---------------------------------------------
//inode 结构由内核在内部用来表示文件. 因此, 它和代表打开文件描述符的文件结构是不同的. 可能有代表单个文件的多个打开的文件描述符的file结构, 但是它们都指向一个单个 inode 结构
struct inode { //inode结构,此结构包含了大量的文件信息
struct hlist_node i_hash;
struct list_head i_list;
struct list_head i_sb_list;
struct list_head i_dentry;
unsigned long i_ino;
atomic_t i_count;
unsigned int i_nlink;
uid_t i_uid;
gid_t i_gid;
dev_t i_rdev; //对表示设备文件的inode结构,该字段包含了真正的设备编号
u64 i_version;
loff_t i_size;
#ifdef __NEED_I_SIZE_ORDERED
seqcount_t i_size_seqcount;
#endif
struct timespec i_atime;
struct timespec i_mtime;
struct timespec i_ctime;
unsigned int i_blkbits;
blkcnt_t i_blocks;
unsigned short i_bytes;
umode_t i_mode;
spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
struct mutex i_mutex;
struct rw_semaphore i_alloc_sem;
const struct inode_operations *i_op;
const struct file_operations *i_fop; /* former ->i_op->default_file_ops */
struct super_block *i_sb;
struct file_lock *i_flock;
struct address_space *i_mapping;
struct address_space i_data;
#ifdef CONFIG_QUOTA
struct dquot *i_dquot[MAXQUOTAS];
#endif
struct list_head i_devices;
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;//若是块设备,其对应的是block_device结构体指针
struct cdev *i_cdev; //struct cdev 是表示字符设备的内核的内部结构,当inode指向一个字符设备文件时,该字段包含了指向struct cdev结构的指针(指向我们初始化并注册的struct cdev结构)
};
从inode中获得主设备号和次设备号:
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);
--------------------------------------------------------------------------------------
-----------------------------------------字符设备注册-----------------------------------
struct xxx_dev{ //描述设备结构体
struct cdev cdev; //字符设备cdev结构体
unsigned char button_value; //状态标志,=1表示有按键按下
struct semaphore sem; //信号量
wait_queue_head_t outq;
struct fasync_struct *async_quene;
};
struct xxx_dev *xxxx_dev;
xxxx_dev=kmalloc(sizeof(struct xxx_dev), GFP_KERNEL); //为设备描述结构体分配内存
//如果直接定义了该结构则可用下面函数来分配该结构:
struct cdev *cdev_alloc(void); //struct cdev分配一个结构体
if(!xxxx_dev)
{
ret=-ENOMEM;
goto fail_malloc;
}
memset(xxxx_dev,0,sizeof(struct xxx_dev)); //将分配的内存清零
void cdev_init(struct cdev *cdev, struct file_operations *fops) //初始化已分配的设备结构
cdev.owner=THIS_MODULE;
cdev.ops=&file_operations xxx
int cdev_add(struct cdev *dev, dev_t num, unsigned int count)
//注册设备,num为设备号,count是应该和该设备关联的设备编号的数量,通常取1
void cdev_del(struct cdev *dev) //卸载设备
//早期的方法:
int register_chrdev(unsigned int major, const char *name,struct file_operations *fops)
//注册字符设备
int unregister_chrdev(unsigned int major, const char *name) //卸载设备
/*********************************end********************************************/
/****************************用户空间和内核空间的数据传输**********************************/
unsigned long copy_to_user(void __user *to, const void *from, unsigned long count);
//返回值是还需要拷贝的内存数量
unsigned long copy_from_user(void *to, const void __user *from, unsigned long count);
//返回值是还需要拷贝的内存数量
//如果要复制的内存是简单类型,如 char、int、long等,则可以使用下面简单函数:
int val; //内核空间整型变量
get_user(val,(int *)arg); //用户空间到内核空间传输数据,arg是用户空间的地址
put_user(val,(int *)arg); //内核空间到用户空间传输数据,arg是用户空间的地址
/****************************end*******************************************/
【学习代码】
【驱动代码】 char_dev.c
#include <linux/init.h> // 模块头文件
#include <linux/module.h>
#include <linux/cdev.h> // 字符设备头文件
#include <linux/fs.h>
#include <linux/uaccess.h> // copy_to_user/copy_from_user
#include <linux/ioctl.h> // ioctl
#include <linux/device.h> // 导出设备信息到sysfs
#include "char_dev.h"
// 1. 创建字符设备对象(定义结构体变量)
// struct cdev char_demo; 静态定义
struct cdev *char_demo;
struct class * char_demo_class;
struct device *char_demo_device;
// 0-表示需用动态分配设备号 非0 - 表示静态分配设备号
int major = 0;
int minor = 0;
int char_open(struct inode *inode, struct file *filp)
{
printk("char open\n");
return 0;
}
ssize_t char_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
int ret = 0;
/*
* @brief 拷贝数据到应用程序空间
* @param[out] to 应用程序空间buf,要拷贝到的地方
* @param[in] from 内核空间buf,从哪个地方拷贝数据
* @param[in] n 拷贝数据长度
* @return 未成功拷贝的数据数量, 一般返回0
*/
// unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
ret = copy_to_user(buf, "char dev hello\n", 15);
if (ret) {
ret = -ENOMEM;
} else {
ret = 15;
}
return ret;
}
ssize_t char_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
int ret = 0;
char str[20];
/*
* @brief 拷贝数据从应用程序空间
* @param[out] to 内核空间buf,要拷贝到的地方
* @param[in] from 应用空间buf,从哪个地方拷贝数据
* @param[in] n 拷贝数据长度
* @return 未成功拷贝的数据数量, 一般返回0
*/
// unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
ret = copy_from_user(str, buf, size);
if (ret) {
ret = -ENOMEM;
} else {
ret = size;
printk("%s\n", str);
}
return ret;
}
long char_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
long ret = 0;
switch (cmd) {
case CMD_DEMO0:
printk("CMD_DEMO0\n");
break;
case CMD_DEMO1:
printk("CMD_DEMO1\n");
break;
default:
ret = -ENOTTY;
break;
}
return ret;
}
int char_release(struct inode *inode, struct file *filp)
{
printk("char release\n");
return 0;
}
struct file_operations fops = {
.owner = THIS_MODULE, // 当前模块
.open = char_open,
.read = char_read,
.write = char_write,
.unlocked_ioctl = char_ioctl,
.release = char_release,
};
int __init char_init(void)
{
int ret = 0;
dev_t devno;
// 给设备分配位置
if (major != 0) {
// 静态分配设备号
// 找设备号的方法:Documention/devices
devno = MKDEV(major, 0);
ret = register_chrdev_region(devno, 1, "char demo");
if (ret) {
goto register_chrdev_region_err;
}
} else {
/*
* @brief 动态分配设备号
* @param[out] dev 返回分配的设备号
* @param[in] baseminor 第一个次设备号
* @param[in] count 分配设备号的数量
* @param[in] name 主设备号的名字
* @return 0 分配成功
* < 0 错误码
*
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
*/
ret = alloc_chrdev_region(&devno, minor, 1, "char demo");
if (ret) {
goto register_chrdev_region_err;
}
major = MAJOR(devno);
}
// 1. 创建字符设备对象(动态分配)
char_demo = cdev_alloc();
if (NULL == char_demo) {
ret = -ENOMEM;
goto cdev_alloc_err;
}
// 2. 初始化字符设备对象
char_demo->owner = THIS_MODULE;
char_demo->ops = &fops;
// cdev_init(&char_demo, &fops);
// 3. 添加字符设备到内核
// ret = cdev_add(&char_demo, devno, 1);
ret = cdev_add(char_demo, devno, 1);
if (ret) {
goto cdev_add_err;
}
// 1. 创建设备类
char_demo_class = class_create(THIS_MODULE, "char_demo");
if (IS_ERR(char_demo_class)) {
ret = PTR_ERR(char_demo_class);
goto class_create_err;
}
// 2. 导出设备信息到应用空间
char_demo_device = device_create(char_demo_class, NULL, devno, NULL, "chardev");
if (IS_ERR(char_demo_device)) {
ret = PTR_ERR(char_demo_class);
goto device_create_err;
}
goto register_chrdev_region_err;
device_create_err:
class_destroy(char_demo_class);
class_create_err:
cdev_del(char_demo);
cdev_add_err:
cdev_alloc_err:
unregister_chrdev_region(devno, 1);
register_chrdev_region_err:
return ret;
}
void __exit char_exit(void)
{
dev_t devno = MKDEV(major, minor);
device_destroy(char_demo_class, devno);
class_destroy(char_demo_class);
// cdev_del(&char_demo);
cdev_del(char_demo);
unregister_chrdev_region(devno, 1);
}
module_init(char_init);
module_exit(char_exit);
MODULE_LICENSE("GPL");
- linux设备
- Linux设备
- linux设备
- linux设备
- Linux 设备
- Linux设备
- Linux设备节点、设备和设备驱动
- linux 块设备,字符设备
- Linux设备模型_platform设备
- Linux 设备 设备驱动 设备结点 (设备号) 关系
- Linux设备模型(Linux设备驱动程序)
- Linux 设备文件
- linux 设备驱动编程
- linux设备驱动
- Linux系统设备驱动程序
- Linux设备驱动程序设计
- Linux系统设备驱动程序
- Linux创建RAID设备
- linux下解压命令大全
- linux shell 学习
- oracle 数据库日常维护_1
- 一个简单的选项卡的demo
- js获取gridview的值
- linux设备
- android power managerment 之sleep and wakeup
- java中晦涩的英文
- 关于IOS开发工具XCode工具出现 OS APPle LLVM 5.02 error问题
- oracle dataguard 数据库切换 python 脚本
- strlen,strcat,strcpy,strncpy,strcmp的具体实现
- jsp链接下载的3种方式
- 用代码控制Cube的运动
- sqlserver 中日期为‘ ’时