【10】
字符设备驱动:
user:
/dev/led
fd=open("/dev/led",O_RDWR);
read=(fd,buf,sizeof(buf));
write(fd,buf,sizeof(buf));
close(fd);
open-->inode号-->c /dev/led (设备号32=主设备号12+次设备号20)
//操作方法结构体
struct file_operations fops{ <==> struct file_operations fops;
.open = led_open, <==> fops.open =led_open;
.read = led_read,
.write = led_write,
.release = led_close,
};//在定义的时候直接赋值,内核常用这种方式
************************************************
【11】 字符设备驱动的API
函数原型:int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
功能: 向内核申请注册设备号
参数: @major :主设备号。如果你填写的major>0,它认为这个大于0的值就是主设备号
如果major=0,系统就会自动分配主设备号。
@name :字符设备驱动的名字
cat /proc/devices
@fops : 操作方法结构体
返回值:major>0,成功返回0,失败返回错误码
major=0,成功返回主设备号,失败返回错误码
函数原型:void unregister_chrdev(unsigned int major, const char *name)
功能:注销字符设备驱动
参数:
@major :主设备号。
@name :字符设备驱动的名字
追寻源码
make tags 或者 ctags -R *//创建tags
:set tags=/home/hsw/HQ/kernel-3.4.39/tags
【12】创建设备文件
手动创建:(/dev 没有mycdev设备文件)
先加载驱动模块,然后查看
[root@iTOP-4418]# cat /proc/devices
243 my_cdev
sudo mknod /dev/mycdev{设备文件的名字} c/b 主设备号 次设备号
sudo mknod /dev/mycdev c 243 0
编写应用层程序:
if((fd = open("/dev/mycdev",O_RDWR))<0)
{
perror("open");
return -1;
}
read(fd,buf,sizeof(buf));
write(fd,buf,sizeof(buf));
close(fd);
查看现象
sudo ./test
sudo /chmod 777 /dev/mycdev
./test
[ 2556.987000] /home/cdev/my_cdev.c:mydev_open:19
[ 2556.988000] /home/cdev/my_cdev.c:mydev_read:24
[ 2556.994000] /home/cdev/my_cdev.c:mydev_write:29
[ 2557.000000] /home/cdev/my_cdev.c:mydev_close:35
【13】用户空间和内核空间数据传递
//“__”是告诉编译器对地址进行检查
unsigned long __must_check copy_from_user(void *to,
const void __user *from,unsigned long n)
功能:将用户空间数据拷贝到内核空间
参数:
@to :内核空间内存的首地址
@form:用户空间内存的首地址
@n :拷贝的字节的个数
返回值: 成功返回0,失败返回未拷贝的字节个数
unsigned long __must_check copy_to_user(void __user *to,
const void *from, unsigned long n);
功能:将内核空间数据拷贝到用户空间
参数:
@to :用户空间内存的首地址
@form:内核空间内存的首地址
@n :拷贝的字节的个数
返回值: 成功返回0,失败返回未拷贝的字节个数
【14】内存映射的函数
void *ioremap(phys_addr_t offset, unsigned long size)
功能:将物联地址映射成虚拟地址
参数:
@off:物理地址
@size:映射的字节的
返回值:成功返回虚拟地址,失败返回NULL
void iounmap(void *addr)
功能:取消映射
参数:
@addr:虚拟地址
返回值:成功返回虚拟地址,失败返回NULL
#define LED_GPIOO (PAD_GPIO_C + 1)
Base Address: C001_C000h (GPIOC)
【15】ioctl函数
user: #include <sys/ioctl.h> int ioctl(int fd, unsigned long request, ...);
功能:调用驱动的ioctl
参数:
@fd :文件描述符
@request:通过_IO _IOR _IOW _IOWR来封装的命令码
@... :可变参数,可以传递也可以不传递
kernel:
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
参数:@cmd : 命令码
@args :传递的数据或数组,结构体,字符串的首地址
32位命令码的组成如下:
bits meaning
31-30 00 - no parameters: uses _IO macro
10 - read: _IOR
01 - write: _IOW
11 - read/write: _IOWR
29-16 size of arguments
15-8 ascii character supposedly
unique to each driver
7-0 function #
31-30 29-16 15-8 7-0
方向 大小 类型 序号
2位 14位 8位 8位
命令码宏的内核封装格式如下:
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size))
#define _IOC(dir,type,nr,size) \
(((dir) << _IOC_DIRSHIFT) | \
((type) << _IOC_TYPESHIFT) | \
((nr) << _IOC_NRSHIFT) | \
((size) << _IOC_SIZESHIFT))
如何使用内核的宏封装去封装命令码
#define type 'b'
#define LED_RED_ON _IO(type,0)
#define LED_RED_OFF _IO(type,1)
如果不想和内核中已有的ioctl的命令码冲突,请查看
kernel/Documentation/ioctl$ vi ioctl-number.txt 文档
【16】自动创建设备节点
user:
hotplug---------->udev/mdev /sys/class/目录/文件 |---->/dev/设备节点
kernel:
1.提交目录(目录名)
2.提交信息(设备号,文件名)
#define class_create(owner, name)
功能:提交目录
参数:
@owner: THIS MODULE
@name: 目录名
返回值:成功返回struct class *结构体指针
失败返回错误码指针
注:如何判断失败,内核预留4k错误码信息
struct class *cls = class_create(THIS MODULE, "hello");
判断错误:
IS_ERR(const void *ptr) 如果是错误返回真,否则返回假
ERR_PTR(long error) 将错误码转化为错误指针码
PTR_ERR(const void *ptr) 将错误码指针转化为错误码
反向操作函数:void class_destroy(struct class *cls
struct device *device_create(struct class *cls, struct device *parent,
dev_t devt, void *drvdata,
const char *fmt, ...);
反向操作函数:void device_destroy(struct class *class, dev_t devt)
功能:提交信息(设备名,文件名)
参数:
@cls :class结构体指针
@parent : NULL
@devt : 设备号
MKDEV(major,minor) ==>根据主设备号和次设备号合成设备号
MAJOP(dev) ==>根据设备号的到主设备号
MINOR(dev) ==>根据设备号的到此设备号
@drvdata: NULL
@fmt : 文件名字
返回值:成功返回device的结构体指针
失败返回错误码指针
【17】字符设备驱动的框架
open("/dev/myadc",O_RDWR) read write close
文件系统 ----ls -i ------->inode号
描述文件的结构体
struct inode {
umode_t i_mode; //文件的权限
unsigned short i_opflags; //
uid_t i_uid; //用户id
gid_t i_gid; //组的id
unsigned int i_flags; //文件系统标志
1.字符设备的结构体
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;//操作方法结构体
struct list_head list; //在内核中构成链表
dev_t dev; //设备号
unsigned int count; //设备的个数
};
2.字符设备驱动创建的流程
1>分配对象
struct cdev cdev;
struct cdev *cdev = cdev_alloc();
2>结构体成员的初始化
void cdev_init(struct cdev *cdev,
const struct file_operations *fops)
功能:只初始化了fops成员
3>申请设备号
主设备号:哪一类设备
次设备号:同类中的那个设备
静态指定:
int register_chrdev_region(dev_t from,
unsigned count, const char *name)
功能:静态指定
参数:
@from :设备号的起始的值
@count: 个数
@name : 名字 /cat proc/devices
返回值:成功为0,失败返回错误码
自动分配:
int alloc_chrdev_region(dev_t *dev,
unsigned baseminor, unsigned count,
const char *name)
功能:自动分配主设备号
参数:
@dev :申请到设备号
@baseminor: 个数
@count:次设备号的起始的值
@name : 名字 /cat proc/devices
返回值:成功为0,失败返回错误码
4>字符设备驱动的注册
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
功能:字符设备驱动的注册
参数:
@p :cdev指针
@dev: 设备号
@count:个数
返回值:成功为0,失败返回错误码
-----------------------------------------------------------------
注销函数:
void cdev_del(struct cdev *p)
功能:字符设备驱动的注销
void unregister_chrdev_region(dev_t from, unsigned count)
功能:释放设备号
void kfree(void *p)
功能:释放kzalloc分配的内存
---------------公司一般都这样做,带有下划线的一般是给内核使用,
---------------这种方法可以设置次设备号大于256,而内核只申请这
---------------么多,若设备号要大于256往往用这种方式
面试注意:#define COUNT 3 与 const int count = 3;
宏定义编译器只做替换不做检查,而const修
饰一个只读的变量,会对格式进行检查,const
可不是修饰一个常量
常量和变量有本质的区别
【18】fd如何找到驱动的操作方法结构体中的函数
read(fd,buf,sizeof(buf));
fd:文件描述符,它是大于0的整数
open,read,write...函数是在进程中执行,fd是咋执行这个进程的时候产生的,
在进程结构体中绝对会有fd的描述
进程的结构体
struct task_struct {
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
void *stack;
int prio, static_prio, normal_prio; //进程优先级
pid_t pid; //进程号
struct task_struct *parent; //父进程
struct list_head children; //子进程
struct list_head sibling; //兄弟进程
struct files_struct *files; /* open file information */
};
struct files_struct {
struct file * fd_array[NR_OPEN_DEFAULT];
};
fd文件描述符就是fd_arrary数组的下标
fd--fd_array[fd]-->struct file *
struct file:只要在进程中打开一次,就会产生一个file结构体,这个结构体用来
记录你打开的文件时候的各种信息,比如打开文件的方式,读,写等
struct file {
struct path f_path; //路径
const struct file_operations *f_op; //操作方法结构体
//它是从inode结构体中获取到的
unsigned int f_flags; //记录打开文件的方式
void *private_data; //私有数据,驱动的函数间参数
//传递,只能进程值传递
};
【19】设备号如何存储的,如何分配的?
在linux内核中通过哈希表来管理设备号
数字下标 = 主设备号%255(取余)
0 ==> -------0----------255......>
1
2
.
.
.
254
所以通过以上的的表我们能解释为什么500 600 在中间的位置,反而255在开头位置
设备号动态分配的方法?
动态申请的设备号只能是254-1之间的值,其他的申请不到(只能自己指定)。
从后向前申请。
【20】并发和竞态的解决方法
有多个程序访问同一个驱动程序中的临界资源的时候,竞态就产生了
竞态产生的根本原因:
1.对于单核CPU,内核支持抢占
2.对于多核CPU,核与核之间会产生竞态
3.中断和进程也会产生竞态
4.中断和中断间产生竞态(arm架构不支持嵌套)
解决方法:
1.中断屏蔽(了解)
只适合在单核CPU屏蔽,中断屏蔽保护的临界资源的时间尽可能的短,
如果屏蔽的时间较长,可能导致内核的崩溃,或者用户数据的丢失。
local_irq_disable();
//临界资源
local_irq_enable();
2.自旋锁(重点)
针对多核CPU设计的,在一个进程中获得到这把锁之后,另一个进程也想获取
这把锁,此时第二进程就处于自旋状态。
1>并不是休眠,消耗CPU资源,不断询问
2>自旋锁保护的临界资源尽可能的短,在上锁区间不能做延时,耗时,
copy_from_user/copy_to_user,不能够让进程状态切换。
3>自旋锁会导致死锁(在同一个进程内想多次获取同一把未解锁的锁)
4>自旋锁在上锁的时候会关闭抢占
5>自旋锁工作在中断上下文
API:
定义自旋锁 spinlock_t lock
初始化自旋锁 spin_lock_init(&lock)
上锁 spin_lock(&lock)
解锁 spin_unlock(&lock)
全局:spinlock_t lock;
flags = 0;
static int __init my_cdev_init(void)
{
spin_lock_init(&lock);
}
int mycdev_open (struct inode * node, struct file * filp)
{
spin_lock(&lock);
if(flags == 1)
{
spin_unlock(&lock)
return -EBUSY;
}
flags = 0;
return 0;
}
int mycdev_close (struct inode * node, struct file * filp)
{
spin_lock(&lock);
flags = 0;
spin_unlock(&lock);
return 0;
}
3.信号量(重点)
信号量:当一个进程获取到锁之后,另一个进程也想获取这个锁,此时第二个
进程处于休眠状态。
1> 休眠状态的进程不消耗CPU
2> 信号量保护的临界区,可以有延时,耗时,休眠,甚至进程状态切换代码
3> 信号量工作在进程上下文,(不能在中断上下文中使用)
4> 信号量的开销比较大(指进程的状态切换)
API:
定义信号量:
struct semaphore sem;
初始化信号量:
void sema_init(struct semaphore *sem, int val);
@val:设置的是信号量可以被获取的次数,但一般在驱动中都是设置为1,实现互斥效果
val;0表示同步,在调用down的时候就休眠了,必须通过up之后才能获取信号量
上锁:
void down(struct semaphore *sem);
int down_trylock(struct semaphore *sem); //成功返回0,失败返回1
解锁:
void up(struct semaphore *sem);
struct semaphore sem;
static int __init my_cdev_init(void)
{
sema_init(&sem);
}
int mycdev_open (struct inode * node, struct file * filp)
{
//down(&sem);
if(down_trylock(&sem))
{
printk("get sema error\n");
return -EBUSY;
}
return 0;
}
int mycdev_close (struct inode * node, struct file * filp)
{
spin_lock(&lock);
flags = 0;
spin_unlock(&lock);
return 0;
}
4.互斥体(会用)
定义互斥体:
struct mutex lock;
初始化:
mutex_init(&lock);
上锁:
void mutex_lock(&lock);
void mutex_trylock(&lock);//成功返回1,失败返回0
解锁:
void mutex_unlock(&lock);
5.原子操作(会用)(将整个操作看为一个整体)
typedef struct {
int counter;
} atomic_t;
使用内联汇编
定义及初始化
atomic_t lock = ATOMIC_INIT(1);
//上锁
atomic_dec_and_test(v)
//减去1之后和0比较,如果结果为0,表示获取锁成功,否则失败
//解锁
atomic_inc(v)
定义及初始化
atomic_t lock = ATOMIC_INIT(-1);
//上锁
atomic_inc_and_test(v);
//加1之后和0比较,如果结果为0,表示获取锁成功,否则失败
//解锁
atomic_dec(v);
Linux驱动基础、内核模块(二)
最新推荐文章于 2024-05-13 01:46:35 发布