一、内核和用户态数据的传输
1. copy_from_user 从用户态获取数据,传送到内核态
static __always_inline unsigned long __must_check
copy_from_user(void *to, const void __user *from, unsigned long n)
{
if (likely(check_copy_size(to, n, false)))
n = _copy_from_user(to, from, n);
return n;
}
static inline __must_check unsigned long
_copy_from_user(void *to, const void __user *from, unsigned long n)
{
unsigned long res = n;
might_fault();
if (!should_fail_usercopy() && likely(access_ok(from, n))) {
instrument_copy_from_user(to, from, n);
res = raw_copy_from_user(to, from, n);
}
if (unlikely(res))
memset(to + (n - res), 0, res);
return res;
}
函数的三个参数:*to是内核空间的指针,*from是用户空间指针,n表示从用户空间想内核空间拷贝数据的字节数。如果成功执行拷贝操作,则返回0,否则返回还没有完成拷贝的字节数。
access_ok用来对用户空间的地址指针from作某种有效性检验
2. copy_to_user 从内核态获取数据,传送到用户态
static __always_inline unsigned long __must_check
copy_to_user(void __user *to, const void *from, unsigned long n)
{
if (likely(check_copy_size(from, n, true)))
n = _copy_to_user(to, from, n);
return n;
}
static inline __must_check unsigned long
_copy_to_user(void __user *to, const void *from, unsigned long n)
{
might_fault();
if (should_fail_usercopy())
return n;
if (access_ok(to, n)) {
instrument_copy_to_user(to, from, n);
n = raw_copy_to_user(to, from, n);
}
return n;
}
函数的三个参数:*to是用户空间的指针,*from是内核空间指针,n表示从内核空间想用户空间拷贝数据的字节数。如果成功执行拷贝操作,则返回0,否则返回还没有完成拷贝的字节数。
3. put_user 数据传送到用户态
put_user ( x, ptr);
x
Value to copy to user space.
ptr
Destination address, in user space.
4. get_user 从用户态获取数据
get_user ( x, ptr);
x
Variable to store result.
ptr
Source address, in user space.
二、创建字符型设备
1.MKDEV将主设备号和次设备号转换成dev_t类型的一个内核函数。
#define MKDEV(major,minor) (((major) << MINORBITS) | (minor))
宏:MKDEV(MAJOR, MINOR);
说明:获取设备在设备表中的位置。
MAJOR 主设备号
MINOR 次设备号
成功执行返回dev_t类型的设备编号
2. register_chrdev_region
/*指定设备编号来静态注册一个字符设备*/
int register_chrdev_region(dev_t from, unsigned count, const char *name);
from: 注册的指定起始设备编号,比如:MKDEV(100, 0),表示起始主设备号100, 起始次设备号为0
count:需要连续注册的次设备编号个数,比如: 起始次设备号为0,count=100,表示0~99的次设备号都要绑定在同一个file_operations操作方法结构体上
*name:字符设备名称
当返回值小于0,表示注册失败
3. alloc_chrdev_region
/*动态分配一个字符设备,注册成功并将分配到的主次设备号放入*dev里*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);
*dev: 存放起始设备编号的指针,当注册成功, *dev就会等于分配到的起始设备编号,可以通过MAJOR()和MINNOR()函数来提取主次设备号
baseminor:次设备号基地址,也就是起始次设备号
count:需要连续注册的次设备编号个数,比如: 起始次设备号(baseminor)为0,baseminor=2,表示0~1的此设备号都要绑定在同一个file_operations操作方法结构体上
*name:字符设备名称
当返回值小于0,表示注册失败
4. unregister_chrdev_region
/*注销字符设备*/
void unregister_chrdev_region(dev_t from, unsigned count);
from: 注销的指定起始设备编号,比如:MKDEV(100, 0),表示起始主设备号100, 起始次设备号为0
count:需要连续注销的次设备编号个数,比如: 起始次设备号为0,baseminor=100,表示注销掉0~99的次设备号
5. cdev_init
/*初始化cdev结构体,并将file_operations结构体放入cdev-> ops 里*/
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
其中cdev结构体的成员,如下所示:
struct cdev {
struct kobject kobj; // 内嵌的kobject对象
struct module *owner; //所属模块
const struct file_operations *ops; //操作方法结构体
struct list_head list; //与 cdev 对应的字符设备文件inode->i_devices 的链表头
dev_t dev; //起始设备编号,可以通过MAJOR(),MINOR()来提取主次设备号
unsigned int count; //连续注册的次设备号个数
};
6. cdev_add
/*将cdev结构体添加到系统中,并将dev(注册好的设备编号)放入cdev-> dev里, count(次设备编号个数)放入cdev->count里*/
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
7. cdev_del
/*将系统中的cdev结构体删除掉*/
void cdev_del(struct cdev *p);
8.class_create,device_create
内核中定义了struct class结构体,顾名思义,一个struct class结构体类型变量对应一个类,内核同时提供了class_create(…)函数,可以用它来创建一个类,这个类存放于sysfs下面,一旦创建好了这个类,再调用device_create(…)函数来在/dev目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev会自动响应device_create(…)函数,去/sysfs下寻找对应的类从而创建设备节点。
参考博客:
Linux驱动开发16之再论register_chrdev_region_wangdapao12138的博客-CSDN博客
注册字符设备时的 class_create() 与 device_create()_sir_zeng的专栏-CSDN博客