打印等级:
#define KERN_EMERG "<0>" /* system is unusable */
#define KERN_ALERT "<1>" /* action must be taken immediately */
#define KERN_CRIT "<2>" /* critical conditions */
#define KERN_ERR "<3>" /* error conditions */
#define KERN_WARNING"<4>" /* warning conditions */
#define KERN_NOTICE "<5>" /* normal but significant condition */
#define KERN_INFO "<6>" /* informational */
#define KERN_DEBUG "<7>" /* debug-level messages */
控制台相关的打印级别:
#define console_loglevel (console_printk[0])
#define default_message_loglevel (console_printk[1])
#define minimum_console_loglevel (console_printk[2])
#define default_console_loglevel (console_printk[3])
cat /proc/sys/kernel/printk 查看当前打印级别
/lib/modules ls 查看当前加载的是哪个内核 并找到makefile 的当前路径
如何去运行相关模块:
sudo insmod xxx.ko :自动执行入口函数
查看内核打印信息:dmesg
如何删除当前模块:
sudo rmmod xxx :自动执行出口函数
清除内核打印信息:
sudo dmesg -c:清除打印信息的同时,将打印信息,显示在屏幕上
sudo dmesg -C:清除打印信息的同时,不显示打印信息在屏幕上
查看当前内核中存在的模块:
lsmod modinfo xxx.ko 可以查看到很多info
关于type的赋值:
type:bool,invbool,charp,int,long,short,uint,ulong,ushort;
module_param_named(named_out,name_in,type,perm)
参数:name_out:加载模块时,参数的名字
name_in:模块内部变量的名字
type:参数的类型
perm:访问权限
传递字符串到全局字符数组:
加载模块时传递字符串到一个全局字符数组module_param_string(name,string,len,perm)
参数:name:加载模块时,参数的名字
string:模块内部字符数组的名字
len:模块内部字符数组
perm:访问权限
传递数组型参数 :
加载模块时传递参数到模块的数组中module_param_array(name,type,num,perm)
参数:name:加载模块时,参数的名字
type:模块数组的数据类型
num:传递数组成员的个数,NULL不关心用户传递的参数个数
perm:访问权限
参数描述信息:MODULE_PARM_DESC( 参数名称,"参数描述信息");
sudo insmod module.ko num=1 module_out=8 str="hello" my_array=9,8,7,6,0
modinfo xxx.ko 查看依赖的模块
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
-C :获取编译该模块的内核的makefile 路径, M:获取待编译模块所在的路径
EXPORT_SYMBOL(待导出的符号);
生成:Module.symvers 该文件中记录了 add的函数名 , 函数的指针
export.h-->/usr/src/linux-headers-3.5.0-23-generic/Module.symvers: addr + symbol + module + EXPORT_SYMBOL
/proc/kallsyms
typedef void (*lpFunction)();
lpFunction lpReset = (lpFunction)0xF000FFF0;
lpReset();
1.获取到 module.symvers insmode xxx.ko
2.完成对应的使用 extern int add(int a, int b); extern int sub(int a, int b);
3.完成, 编译, 插入到内核
dev_t :设备号的数据类型
dev_t from ;
int major ,minor;
获取主设备号major: major = MAJOR(from);
获取次设备号minor: minor = MINOR(from);
利用主次设备号获取设备号: from = MKDEV(major , minor);
struct inode {------>某一个具体的文件--->设备节点--->/dev/xxx
const struct inode_operations *i_op;//对文件、设备节点操作时的相关操作方法 的集合
dev_t i_rdev; //设备号
const struct file_operations *i_fop; //操作方法
union {
struct pipe_inode_info *i_pipe;//网络相关的
struct block_device *i_bdev; //块设备的抽象结构体
struct cdev *i_cdev; //字符设备的抽象结构体
};
};
struct cdev {//字符设备对象
//struct kobject kobj;//linux设备模型的底层组成
struct module *owner;//表征其所归属的模块对象
const struct file_operations *ops;//对于字符设备对象相关操作的方法集合,open,close,read,write,llseek
dev_t dev;//该字符设备对象对应的设备号 -------------》 对应 inode 中的 i_rdev container_of(inode->i_rdev, xxxxx);
};
[1]设备号的申请
[1.1]自动申请设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
功能:自动申请设备号
参数:dev:对应的设备号
baseminor:起始次设备号
count:申请次设备的个数
name:名称
返回值:成功:0 失败:errno
[1.2]手动申请(可能存在设备号的冲突)
int register_chrdev_region(dev_t from, unsigned count, const char *name)
功能:手动申请设备号
参数:from:对应的设备号
count:申请次设备的个数
name:名称
返回值:成功:0 失败:errno
[2]申请得到对应的字符设备对象
struct cdev *cdev_alloc(void)
功能:申请cdev对象
参数:无
返回值:成功:struct cdev 的指针 失败:NULL
[3]填充字符设备对象的相关属性成员
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
功能:初始化cdev[完成cdev与之相关联的操作方法集合的绑定]
参数:cdev:字符设备的指针对象
fops:操作方法集合
返回值:无
[4]字符设备对象的注册
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
功能:注册字符设备对象
参数:p:字符设备的指针对象
dev:该字符设备对象的设备号
count:注册的字符设备的个数
返回值:成功 : 0 失败:errno
---------------------------exit------------------------------
[1]设备号的注销
void unregister_chrdev_region(dev_t from, unsigned count)
功能:注销设备号
参数:from:对应的设备号
count:注销次设备号的个数
返回值:无
[2]void cdev_del(struct cdev *p)
功能:注销字符设备对象
参数:p:对应的字符设备对象
返回值:无
(0)字符设备驱动另一种方式
[1]字符设备驱动的简化版
static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
功能:完成字符设备框架的初始化编写
参数:major:主设备号 (major= 0 完成设备号部分的自动申请 )
name:设备对应的名称 cat /proc/devices
fops:该设备的对应的操作方法集合
返回值:成功:0 /major 失败:errno
static inline void unregister_chrdev(unsigned int major, const char *name)
功能:注销字符设备框架(的退出部分编写)
参数:major:主设备号 (major= 0 完成设备号部分的自动申请 )
name:设备对应的名称 cat /proc/devices
返回值:无
(1)读写
用户空间------->内核空间的访问
(1)系统调用【read,write】
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
头文件:include <linux/uaccess.h> //read write是针对用户来讲的 copy_to_user copy_from_user是针对内核来讲的
read:将底层数据传输给用户层 copy_to_user
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
功能:传输数据到用户空间
参数:to:用户空间的数据存储地址
from:内核空间的地址
n:传输数据的大小
返回值:成功:0 失败:传输数据的个数
write:将用户空间数据传输给底层 copy_from_user
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
功能:将用户空间的数据写入内核空间
参数:to:内核空间的地址
from:用户空间的地址
n:传输数据的大小
返回值:成功:0 失败:传输数据的个数
(2)控制(32bits被分为四部分)
#include <sys/ioctl.h>
int ioctl(int d, int request, ...);
功能:根据不同的命令码执行不同的操作
参数:d:文件描述符
request:命令码
...:视情况再填充 ,[地址]
返回值:成功:0 失败:-1
头文件:#include <asm-generic/ioctl.h>
ioctl:
底层实现的机制:
(1)完成unlocked_ioctl的底层实现:switch case
(2)完成不同命令码的封装
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
功能:封装命令码
参数:type:命令码的一个类型 【幻数】,一般使用相关字符来标志 eg: #define type 'a'
nr: 命令代码参数 eg:0 , 1 , 2...
/*
#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)))
linux-3.14/Documentation/ioctl/ioctl-number.txt记录了内核中可能被占用的一些命令码
*/
ioctl命令码组成: 32bit
_________________________________________
| 方 向 | 数据尺寸 | 设备类型 | 序列号 |
|----------|----------|----------|--------|
| 2 bit | 8~14 bit | 8 bit | 8 bit |
幻数 一般使用字符来表示 eg:# define type ‘a’
Nr:命令代码参数
#define TYPE 'A'
#define ONE _IO(TYPE, 0)
#define TWO _IO(TYPE, 1)
(3)自动创建设备节点[/proc/sys/kernel/hotplug]--->udev
头文件:#include <linux/device.h>
结构体:
struct class {
const char *name;//类名 /sys/class/xxx
struct module *owner;//所归属的模块
//struct kobject *dev_kobj;//linux设备模型相关-->生成文件夹
};
struct device {
struct device *parent;//该设备的parent
struct device_private *p;//存放该设备的私有数据
//struct kobject kobj;//linux设备模型相关-->生成文件夹
const char *init_name; /* 设备名称 */
//const struct device_type *type;//设备类型
//struct bus_type *bus; /* 该设备挂接的总线*/
//struct device_driver *driver; /*支持当前设备的驱动对象*/
dev_t devt; /* 设备号*/
struct class *class;//当前设备归属于哪一个类
//void (*release)(struct device *dev);//当设备移除时,会被调用
};
(1)struct class *class_create(struct module *owner,const char *name)
功能:创建class
参数:owner:THIS_MODULE
name:对应的设备类/*/sys/class/xxx*/
返回值:成功:struct class指针对象 失败:errno指针
IS_ERR(errno):判断其是否申请得到了对应的指针,如果说errno该指针是正确的话,那么IS_ERR会返回0,反之非0
PTR_ERR(errno):将错误指针,转换成具体的错误码
/*
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
struct class *__class_create(struct module *owner, const char *name, struct lock_class_key *key)
*/
(2)void class_destroy(struct class *cls)
功能:销毁对应的class
参数:cls:struct class的指针对象
返回值:无
(3)struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...)
功能:创建对应的device
参数:class:struct class的指针对象
parent:设备父节点 如果没有 NULL
devt:设备号
drvdata:私有数据 没有的话 NULL
fmt:对于待生成的设备节点的命名格式 /dev/demo0 /dev/demo1 "%s%d"
...:"demo" i
返回值:成功:struct device指针对象 失败:errno指针
(4)void device_destroy(struct class *class, dev_t devt)
功能:销毁对应的device
参数:class:struct class的指针对象
devt:设备号
返回值:无
内存管理模型:
uma:一致性内存模型
numa:非一致性内存管理模型
速度的一致非一致
/*数据结构struct pglist_data*/
给予页面管理之上的slab管理:gfp_t flags
linux系统在物理页分配的基础上实现了对更小内存空间进行管理的slab分配器。
slab思想:将一个内存页或者若干内存页分配成若干相等的小内存,供程序员使用。
slab分配器限制:只能在低端内存区进行分配;
典型:kmalloc
static __always_inline void *kmalloc(size_t size, gfp_t flags)
功能:分配对应的虚拟内存
参数:size:分配内存区的大小
flags:内存分配标志
返回值:对应虚拟地址
特点:最大128k , 分配虚拟地址,其虚拟地址空间连续,物理地址空间也是连续
类似函数:kzalloc:分配虚拟内存区并清零
void kfree(const void *x)
功能:释放对应的虚拟内存
参数:x:虚拟内存的起始地址
返回值:无
页面分配器函数的核心:
alloc_pages && __get_free_pages ---------->alloc_pages_node
alloc_pages 与 __get_free_pages的不同:前者较后者能分配高端内存以及低端内存; 后者只能获取低端内存区的内容;
[3.1]__alloc_pages/alloc_page<-------->__free_pages
[3.2]__get_free_pages/__get_free_page<-------->free_pages
[3.3]get_zeroed_page:分配内存页的同时,将其对应的物理内存区清零
unsigned long get_zeroed_page(gfp_t gfp_mask)
功能:分配并清零对应的内存页
参数:gfp_mask:表征当前分配内存的标志 GFP_KERNEL,GFP_ATOMIC
返回值:对应的页地址
#define GFP_KERNEL (__GFP_WAIT | __GFP_IO | __GFP_FS)
__GFP_WAIT:当前正在向内核申请页分配的进程可以被阻塞,意味着调器可以在此请求期间调度另一个进程执行。
__GFP_IO:内核在查找空闲页的操作中可以进行IO操作,如此内核可以将换出的页写到硬盘中。
__GFP_FS:查找空闲页的过程中允许执行文件系统相关的操作__GFP_DMA
#define GFP_ATOMIC (__GFP_HIGH)
__GFP_HIGH:内核允许使用紧急分配链表中的保留内存页,该请求必须以原子方式完成,意味着请求过程不允许被中断。
[1]内存管理部件内部使用的标志
__GFP_WAIT,__GFP_IO,__GFP_FS,__GFP_HIGH
[2]外部使用的内存分配标志
GFP_KERNEL,GFP_ATOMIC
#define GFP_ATOMIC (__GFP_HIGH|__GFP_ATOMIC|__GFP_KSWAPD_RECLAIM)
#define GFP_KERNEL (__GFP_RECLAIM | __GFP_IO | __GFP_FS)
#define GFP_KERNEL_ACCOUNT (GFP_KERNEL | __GFP_ACCOUNT)
#define GFP_NOWAIT (__GFP_KSWAPD_RECLAIM)
#define GFP_NOIO (__GFP_RECLAIM)
#define GFP_NOFS (__GFP_RECLAIM | __GFP_IO)
#define GFP_USER (__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL)
#define GFP_DMA __GFP_DMA
#define GFP_DMA32 __GFP_DMA32
#define GFP_HIGHUSER (GFP_USER | __GFP_HIGHMEM)
#define GFP_HIGHUSER_MOVABLE (GFP_HIGHUSER | __GFP_MOVABLE)
#define GFP_TRANSHUGE_LIGHT ((GFP_HIGHUSER_MOVABLE | __GFP_COMP | \
__GFP_NOMEMALLOC | __GFP_NOWARN) & ~__GFP_RECLAIM)
#define GFP_TRANSHUGE (GFP_TRANSHUGE_LIGHT | __GFP_DIRECT_RECLAIM)
/* Convert GFP flags to their corresponding migrate type */
#define GFP_MOVABLE_MASK (__GFP_RECLAIMABLE|__GFP_MOVABLE)
#define GFP_MOVABLE_SHIFT 3
{2}给予页面管理之上的slab管理:gfp_t flags
linux系统在物理页分配的基础上实现了对更小内存空间进行管理的slab分配器。
slab思想:将一个内存页或者若干内存页分配成若干相等的小内存,供程序员使用。
slab分配器限制:只能在低端内存区进行分配;
典型:kmalloc
static __always_inline void *kmalloc(size_t size, gfp_t flags)
功能:分配对应的虚拟内存
参数:size:分配内存区的大小
flags:内存分配标志
返回值:对应虚拟地址
特点:最大128k , 分配虚拟地址,其虚拟地址空间连续,物理地址空间也是连续
类似函数:kzalloc:分配虚拟内存区并清零
void kfree(const void *x)
功能:释放对应的虚拟内存
参数:x:虚拟内存的起始地址
返回值:无
虚拟内存管理
转换过程:
[1]vmalloc / vfree
void *vmalloc(unsigned long size) // 没有标志 是在 固有的一个局域分配的
功能:分配对应的虚拟内存
参数:size:分配内存区的大小
返回值:对应虚拟地址
特点:分配虚拟地址,其虚拟地址空间连续,但是物理地址空间不一定连续
void vfree(const void *addr)
功能:释放对应的虚拟内存
参数:addr:虚拟内存区的首地址
返回值:无
[2]linux内核虚拟地址空间构成
/*
4g------------------------------------
4k
-----------------------------------
固定内存映射区 4m-4k
-----------------------------------
高端内存映射区 4m
------------------------------------
NULL 8K(保护)
------------------------------------vmalloc end
vmalloc内存区120m-8m-8k //就是这个固有的区域
------------------------------------vmalloc start
vmalloc offset 8m
------------------------------------
物理内存映射区896M
3g------------------------------------ 物理内存 3g 偏移 4k + 3g
用户空间
0g------------------------------------
*/
各个地址的概念
(1)物理地址:在datasheet中能够查到的地址称之为物理地址,实际设备的操作地址;
(2)逻辑地址:将程序进行反汇编之后,其中能够看到的地址称之为逻辑地址;
(3)虚拟地址:在操作系统程序员能够操作的地址称之为虚拟地址;
如果我们想操作实际硬件的话:将物理地址-- mmu -->虚拟地址
static inline void __iomem *ioremap(phys_addr_t offset, unsigned long size)
功能:完成物理地址到虚拟地址的转换
参数:offset:设备的物理地址
size:映射的大小
返回值:成功:映射成功的虚拟地址 失败:NULL
static inline void iounmap(void __iomem *addr)
功能:解除地址映射
参数:addr:虚拟地址
返回值:无
static inline u32 readl(const volatile void __iomem *addr)
功能:完成对虚拟地址的数据读取
参数:addr:虚拟地址
返回值:读取虚拟地址得到的数据
static inline void writel(u32 b, volatile void __iomem *addr)
功能:完成对虚拟地址的数据写入
参数:b:待写入虚拟地址中的数据
addr:虚拟地址
返回值:无
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/io.h>
#define CLASS_NAME "demo_class"
#define DEVICE_NAME "demo"
#define MAX 64
#define GPF3_CON 0X114001E0
#define GPF3_DAT 0X114001E4
void __iomem *gpf3_con;
void __iomem *gpf3_dat;
char ker_buf[64] = {0};
int major = 0;
int count = 2;
int demo_open(struct inode *inode, struct file *file)
{
writel((readl(gpf3_dat)|(1<<5)),gpf3_dat);//在打开操作时将gpf3_5这个灯点亮
printk("%s,%d\n",__func__,__LINE__);
return 0;
}
int demo_release(struct inode *inode, struct file *file)
{
writel((readl(gpf3_dat)&~(1<<5)),gpf3_dat);//在关闭操作时,将led关闭
printk("%s,%d\n",__func__,__LINE__);
return 0;
}
struct file_operations f_ops = {
.owner = THIS_MODULE,
.open = demo_open,
.release = demo_release,
};
struct class *cls = NULL;
struct device * dev = NULL;
static int __init demo_init(void)
{
int ret = 0,i= 0;
major = register_chrdev(major , DEVICE_NAME, &f_ops);
if(major < 0){
printk("register_chrdev ... %s,%d\n",__func__,__LINE__);
return -EINVAL;
}
cls = class_create(THIS_MODULE,CLASS_NAME);
if(IS_ERR(cls)){
printk("class_create fail...\n");
ret = PTR_ERR(cls);
goto ERR_STEP1;
}
for(i = 0; i < count ; i++){
dev = device_create(cls,NULL,MKDEV(major,i),NULL,"%s%d",DEVICE_NAME,i);
if(IS_ERR(dev)){
printk("device_create fail...\n");
ret = PTR_ERR(dev);
goto ERR_STEP2;
}
}
/*********************************************/
gpf3_con = ioremap(GPF3_CON,0x4);
if(gpf3_con == NULL){
printk("ioremap FAIL... \n");
goto ERR_STEP3;
}
gpf3_dat = ioremap(GPF3_DAT,0x4);
if(gpf3_dat == NULL){
printk("ioremap FAIL... \n");
goto ERR_STEP4;
}
writel((readl(gpf3_con)&~(0xf<<20))|(1<<20),gpf3_con);//gpf3_5输出功能的配置
writel((readl(gpf3_dat)&~(1<<5)),gpf3_dat);//在初始化之后先令gpf3_5处于关闭的状态
printk("%s,%d major :%d \n",__func__,__LINE__,major);
return 0;
ERR_STEP4:
iounmap(gpf3_con);
ERR_STEP3:
device_destroy(cls,MKDEV(major,1));
ERR_STEP2:
for(i-- ; i > 0 ; i--)
device_destroy(cls,MKDEV(major,i));
class_destroy(cls);
ERR_STEP1:
unregister_chrdev(major,DEVICE_NAME);
return ret;
}
static void __exit demo_exit(void)
{
int i = 0;
writel((readl(gpf3_dat)&~(1<<5)),gpf3_dat);//在初始化之后先令gpf3_5处于关闭的状态
iounmap(gpf3_con);
iounmap(gpf3_dat);
for(i = 0; i < count ; i++)
device_destroy(cls,MKDEV(major,i));
class_destroy(cls);
unregister_chrdev(major,DEVICE_NAME);
printk("%s,%d\n",__func__,__LINE__);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
Linux设备驱动中的并发与竟态 https://blog.csdn.net/zmjames2000/article/details/87743122
概念:linux系统允许多进程,多cpu的一个操作系统, 并发执行的程序或cpu对共享资源的访问,会造成一种竟态。
竟态:就是并发单元同时对同一块资源进行访问操作,造成该资源数据混乱的一种状态;
为了确保共享资源的访问安全的措施:
互斥:排他性
同步:按照顺序访问
补充概念:
临界区:就是访问共享资源的代码段
临界资源:被共享的硬件或软件资源;
并发源:多进程,多cpu等;
为了防止产生竟态,将访问共享资源的代码段进行排他性访问或者顺序性访问;
单cpu
/*中断屏蔽开始*/ --->local_irq_disable();
/*中断屏蔽进行*/ --->执行对共享资源的访问
/*中断解除屏蔽*/ --->local_irq_enable();
应用注意事项:被中断屏蔽的时间应该尽量短,否则的话会造成当前系统响应进程调度以及中断反应缓慢;
自旋锁概念及使用场景:主要适用于smp,进程与进程间抢占, /*进程与中断间抢占(自旋锁的扩展功能:自旋锁的衍生锁)*/
目的:保护共享资源 //ps. 中断是不能休眠的,
自旋锁: 如果获取不到资源的话,会一直在测试,会造成死锁 (忙等锁时间过长就会造成死锁)
所以占有锁的时间不能够过长。
所以不能在持有锁期间能够执行睡眠的函数,也不能执行被调度的函数。
因为时间过长会导致死锁。 其实自选锁就是忙等锁
信号量:跟自旋锁类似,其只能在进程上下文使用,不能在中断中用。
自旋锁有衍生锁,是能在中断上下文用的。
信号量只会测试一次,条件不满足就会去睡眠。(获取不到资源的时候)
这个是最大的区别之一。 两个区别 ,只能在进程中用,只测试一次,不满足就去睡眠 ,不是忙等锁
typedef struct {
struct lock_impl internal_lock;
} spinlock_t;
struct lock_impl {
bool locked;
};
struct lock_impl {
pthread_mutex_t mutex;
};
函数API(初始化,上锁,解锁):
(1)初始化
void spin_lock_init(spinlock_t *lock)
功能:初始化自旋锁
参数:lock:自旋锁的指针对象
返回值:无
#define spin_lock_init(_lock) \
do { \
spinlock_check(_lock); \
raw_spin_lock_init(&(_lock)->rlock); \
} while (0)
(2)上锁
void spin_lock(spinlock_t *lock)
功能:自旋锁上锁
参数:lock:自旋锁的指针对象
返回值:无
(3) void spin_unlock(spinlock_t *lock)
功能:自旋锁解锁
参数:lock:自旋锁的指针对象
返回值:无
——————————————————————————————————————————————————————————————————————————
static __always_inline void spin_lock(spinlock_t *lock)
{
raw_spin_lock(&lock->rlock);
}
#define raw_spin_lock(lock) _raw_spin_lock(lock)
#define _raw_spin_lock(lock) __LOCK(lock)
#define _raw_spin_lock_nested(lock, subclass) __LOCK(lock)
#define _raw_read_lock(lock) __LOCK(lock)
#define _raw_write_lock(lock) __LOCK(lock)
#define _raw_spin_lock_bh(lock) __LOCK_BH(lock)
#define _raw_read_lock_bh(lock) __LOCK_BH(lock)
#define _raw_write_lock_bh(lock) __LOCK_BH(lock)
#define _raw_spin_lock_irq(lock) __LOCK_IRQ(lock)
#define _raw_read_lock_irq(lock) __LOCK_IRQ(lock)
#define _raw_write_lock_irq(lock) __LOCK_IRQ(lock)
#define _raw_spin_lock_irqsave(lock, flags) __LOCK_IRQSAVE(lock, flags)
#define _raw_read_lock_irqsave(lock, flags) __LOCK_IRQSAVE(lock, flags)
#define _raw_write_lock_irqsave(lock, flags) __LOCK_IRQSAVE(lock, flags)
#define _raw_spin_trylock(lock) ({ __LOCK(lock); 1; })
#define _raw_read_trylock(lock) ({ __LOCK(lock); 1; })
#define _raw_write_trylock(lock) ({ __LOCK(lock); 1; })
#define _raw_spin_trylock_bh(lock) ({ __LOCK_BH(lock); 1; })
#define _raw_spin_unlock(lock) __UNLOCK(lock)
#define _raw_read_unlock(lock) __UNLOCK(lock)
#define _raw_write_unlock(lock) __UNLOCK(lock)
#define _raw_spin_unlock_bh(lock) __UNLOCK_BH(lock)
#define _raw_write_unlock_bh(lock) __UNLOCK_BH(lock)
#define _raw_read_unlock_bh(lock) __UNLOCK_BH(lock)
#define _raw_spin_unlock_irq(lock) __UNLOCK_IRQ(lock)
#define _raw_read_unlock_irq(lock) __UNLOCK_IRQ(lock)
#define _raw_write_unlock_irq(lock) __UNLOCK_IRQ(lock)
#define _raw_spin_unlock_irqrestore(lock, flags) \
__UNLOCK_IRQRESTORE(lock, flags)
#define _raw_read_unlock_irqrestore(lock, flags) \
__UNLOCK_IRQRESTORE(lock, flags)
#define _raw_write_unlock_irqrestore(lock, flags) \
__UNLOCK_IRQRESTORE(lock, flags)
信号量:跟自旋锁类似,其只能使用在进程上下文,其当资源获取不到时,直接休眠等待;
应用注意事项:只能在进程上下文,不是忙等锁;
信号量:跟自旋锁类似,其只能在进程上下文使用,不能在中断中用。
自旋锁有衍生锁,是能在中断上下文用的。
信号量只会测试一次,条件不满足就会去睡眠。(获取不到资源的时候)
这个是最大的区别之一。 两个区别 ,只能在进程中用,只测试一次,不满足就去睡眠 ,不是忙等锁
信号量 同时两个进程对同一个资源惊进行读写,去读是可以的,但是不能同时写
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};
函数(初始化,获取,释放):
void sema_init(struct semaphore *sem, int val)
功能:初始化信号
参数:sem:信号量的结构体对象
val:给信号量的count的赋值
返回值:无
int down_trylock(struct semaphore *sem)
功能:获取信号量
参数:sem:信号量的结构体对象
返回值:成功:0 失败:1
void up(struct semaphore *sem)
功能:释放信号量
参数:sem:信号量的结构体对象
返回值:无
-----------------------------------------------------------------
int down_trylock(struct semaphore *sem)
{
unsigned long flags;
int count;
raw_spin_lock_irqsave(&sem->lock, flags);
count = sem->count - 1;
if (likely(count >= 0)) //内核编译的时候把 likely 往前放, unlikely 往后放
sem->count = count;
raw_spin_unlock_irqrestore(&sem->lock, flags);
return (count < 0);
}
EXPORT_SYMBOL(down_trylock);
void up(struct semaphore *sem)
{
unsigned long flags;
raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(list_empty(&sem->wait_list)))
sem->count++;
else
__up(sem);
raw_spin_unlock_irqrestore(&sem->lock, flags);
}
EXPORT_SYMBOL(up);
static noinline void __sched __up(struct semaphore *sem)
{
struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list,
struct semaphore_waiter, list);
list_del(&waiter->list);
waiter->up = true;
wake_up_process(waiter->task);
}
int wake_up_process(struct task_struct *p)
{
return try_to_wake_up(p, TASK_NORMAL, 0);
}
EXPORT_SYMBOL(wake_up_process);
PS:互斥锁mutex,是信号量的count成员为1的特殊用法,以实现互斥
struct mutex {
atomic_long_t owner;
spinlock_t wait_lock;
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
struct optimistic_spin_queue osq; /* Spinner MCS lock */
#endif
struct list_head wait_list;
#ifdef CONFIG_DEBUG_MUTEXES
void *magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
};
(1)void mutex_init(struct mutex *lock)
功能:完成互斥体的初始化
参数:lock:互斥体的结构体指针
返回值:无
# define mutex_init(mutex) \
do { \
static struct lock_class_key __key; \
\
__mutex_init((mutex), #mutex, &__key); \
} while (0)
void
__mutex_init(struct mutex *lock, const char *name, struct lock_class_key *key)
{
atomic_long_set(&lock->owner, 0); // 初始化是0
spin_lock_init(&lock->wait_lock);
INIT_LIST_HEAD(&lock->wait_list);
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
osq_lock_init(&lock->osq);
#endif
debug_mutex_init(lock, name, key);
}
EXPORT_SYMBOL(__mutex_init);
(2) int mutex_trylock(struct mutex *lock)
功能:获取互斥体
参数:lock:互斥体的结构体指针
返回值:Returns 1 if the mutex has been acquired successfully, and 0 on contention.
(3)void mutex_unlock(struct mutex *lock)
功能:释放互斥体
参数:lock:互斥体的结构体指针
返回值:无
________________________________________________________________
int __sched mutex_trylock(struct mutex *lock)
{
bool locked = __mutex_trylock(lock);
if (locked)
mutex_acquire(&lock->dep_map, 0, 1, _RET_IP_);
return locked;
}
EXPORT_SYMBOL(mutex_trylock);
自旋锁是不带标志位的。 所以需要手动添加一个标志位。 信号量和互斥锁 里面是自带标志位的。 所以是自动去加锁 的。
原子变量:将若干步操作在cpu上执行时变为一步 ,肯定不能被打断
函数API(初始化,获取,释放):
(1)初始化
#define ATOMIC_INIT(i) { (i) }
功能:给原子变量初始化
参数:i:给原子变量赋值 ,一般 i = 1
(2)int atomic_dec_and_test( atomic_t *v)
功能:减去并测试当前原子变量
参数:v:原子变量的指针对象
返回值:如果v最开始被赋值为1 的话 ,那么此时 返回true反之则返回false
(3)void atomic_inc( atomic_t *v)
功能:增加当前原子变量
参数:v:原子变量的指针对象
返回值:无
/*
#define atomic_inc(v) atomic_add(1, v)
static inline void atomic_add(int i, atomic_t *v)
*/
Linux设备驱动中的阻塞与非阻塞I/O
1、字符设备IO模型
同步阻塞io模型:
当系统调用read/write,底层并没有准备好相关的资源的情况下,该系统调用所代表的进程被挂起(睡眠),当底层准备好相关资源后 去唤醒被阻塞的进程;
写操作: ///相关读操作也是一样的
内核有一个 kernerbuf[],需要有空间才能写,只有当底层的 kerbuf,有空间,才能被写。如果满的话,写 buf 的进
程就被移走,去睡眠,等有空间了,才能被唤醒,来才做写。
等待队列:以队列为基础,结合进程组成的这样一种数据结构。
等待队列:负责管理链接系统种不满足条件的系统调用所代表的进程挂界项。
同步阻塞 IO 流程
操作等待队列的流程
1、等待队列头的定义
2、完成等待队列头的初始化
3、当系统调用 read/write 底层条件不满足,则将其设置为对应的睡眠态,并挂起对应的进程,实现阻塞
Wait_event_interruptible
4、实现对应的满足条件,并唤醒该进程
int flag = 0;
wait_queue_head_t wq;
ssize_t demo_read(struct file *file, char __user *user, size_t size, loff_t *loff)
{
if(size > MAX) size =MAX;
if(!flag){ //所以这里必须是真 !sizeof(ker_buf)
if(wait_event_interruptible( wq, flag)){ //flag=0表示假就return -ERESTARTSYS
return -ERESTARTSYS;
}
}
if(copy_to_user(user,ker_buf,size)){
printk("copy_to_user fail\n");
return -EAGAIN;
}
flag = 0;
printk("%s,%d\n",__func__,__LINE__);
return size;
}
ssize_t demo_write(struct file *file, const char __user *user, size_t size, loff_t *loff)
{
if(size > MAX) size = MAX;
if(copy_from_user(ker_buf,user,size)){ //如果是先写,必须等写完了flag=1 才能读
printk("copy_from_user fail\n"); //如果是先读,也必须等flag=1 才能读,其实就是判断sizeof(ker_buf)
return -EAGAIN;
}
flag = 1;
wake_up(&wq);
return size;
}
同步非阻塞io模型:
当系统调用read/write,底层并没有准备好相关的资源的情况下, 直接报错返回;
需要应用层 设置 flag fd = open(PATH, O_RDWR | O_NONBLOCK) ;
驱动底层 判断 if ( file->f_flags & O_NONBLOCK) 是不是设置, 设置了就直接返回报错。
没有设置,就 向下走呗。
异步阻塞io模型: https://blog.csdn.net/zmjames2000/article/details/88104464
https://blog.csdn.net/zmjames2000/article/details/87736983
https://blog.csdn.net/zmjames2000/article/details/87994817
其阻塞的并非是系统调用,而是将系统调用所对应的文件描述符,放到一个文件描述符数组中, 当检测到该文件描述符数组中的成员如果其阻塞的调节被解除,就会执行对应的操作;
struct file_operations -> poll
应用层:
#include <poll.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#define PATH "/dev/demo"
/*
struct pollfd {
int fd; //需要监听的文件描述符
short events; // 需要监听的事件 POLLIN POLLOUT
short revents;// 已发生的事件
}
*/
char usr_rx[64] = {0};
char usr_tx[64] = {0};
int main()
{
int fd,ret;
fd = open(PATH,O_RDWR);
if(fd < 0){
perror("open");
}
struct pollfd fds[] = {
[0]= {
.fd = fd,
.events = POLLIN,
},
[1] = {
.fd = fd,
.events = POLLOUT,
}
};
while(1){
ret = poll(fds,2,1000);
if(ret > 0){
if(fds[0].revents & POLLIN){
if(read(fds[0].fd ,usr_rx, sizeof(usr_rx)) < 0){
perror("read");
}
printf("usr_rx:%s\n",usr_rx);
}else if(fds[1].revents & POLLOUT){
printf("please input:\n");
fgets(usr_tx,sizeof(usr_tx),stdin);
/*char *fgets(char *s, int size, FILE *stream);*/
usr_tx[strlen(usr_tx) - 1] = '\0';
if(write(fds[1].fd,usr_tx,sizeof(usr_tx)) < 0){
perror("write");
}
}
}else if(ret == 0){
printf("you time out\n");
}else{
printf("you error\n");
}
}
close(fd);
return 0;
}
____________________________________________________
wait_queue_head_t wq;
unsigned int demo_poll(struct file *file, struct poll_table_struct *table)
{
int mask = 0;
poll_wait(file,&wq,table);
if(flag == 1)//此时有数据写入
return mask |= POLLIN;
else if(flag == 0)
return mask |=POLLOUT;
printk("%s,%d \n ",__func__,__LINE__);
return 0;
}
异步非阻塞io模型:https://blog.csdn.net/zmjames2000/article/details/88106833
异步信号驱动io(异步通知):
1、应用层 signal 绑定信号 sigio 和 read_handler,有人 发 sigio 信号过来的时候,就会触发 read_handler
2、设置异步通知模式,FASYNC 会触发底层的 fasync 驱动,该驱动的作用就是将其加入队列中
3、应用层就开始 while(1)
4、当有 echo 111 >dev/demo 的时候就会触发底层 的写操作,当底层的写操作完成的时候,就会发送一 个 kill_fasync 信号,
5、应用层收到信号就回去,执行相应的函数。
6、 向 IO 写的时候的,才能触发一个 IO 信号,写进入, 让 app 读,(echo 111 > /dev/demo App(收到信号,就会去处理绑定的函数)开始读。 相当与写入 IO ,让 app 去读
[1]同步阻塞io概念:
等待队列:以队列为基础,结合进程组成的这样一种数据结构;
等待队列头:负责管理连接系统中不满足条件的系统调用所代表的进程的挂接项;
底层驱动实现对应的同步阻塞io的相关参数及API(初始化,睡眠,唤醒):
/*
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
*/
struct __wait_queue_head wait_queue_head_t;
(1)定义等待队列头: wait_queue_head_t wq;
(2)等待队列头得初始化
void init_waitqueue_head(wait_queue_head_t *q)
功能:实现等待队列头的初始化
参数:q:等待队列头的指针对象
返回值:无
/*
#define init_waitqueue_head(q) \
do { \
static struct lock_class_key __key; \
\
__init_waitqueue_head((q), #q, &__key); \
} while (0)
*/
(3)实现阻塞:
int wait_event_interruptible(wait_queue_head_t q ,bool condition)
功能:实现对应系统调用的睡眠直到条件为真或被信号唤醒
参数:q:等待队列头
condition:唤醒该睡眠进程的条件
返回值:如果condition为真 则返回0 反之 直接报错返回-ERESTARTSYS
* The function will return -ERESTARTSYS if it was interrupted by a signal and 0 if @condition evaluated to true.
/*
#define wait_event_interruptible(wq, condition) \
({ \
int __ret = 0; \
if (!(condition)) \
__ret = __wait_event_interruptible(wq, condition); \
__ret; \
})
*/
(3)void wake_up(wait_queue_head_t *q)
功能:唤醒对应的等待队列
参数:q:等待队列头的指针对象
返回值:无
/*#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)*/
[2]同步非阻塞io概念:
相关参数或标志:O_NONBLOCK--->fd
fd = open(PATH, O_RDWR|O_NONBLOCK);
fd ---->struct file->unsigned int f_flags;
【*】程序编写:同步非阻塞IO
(1)app:在open时, ‘|O_NONBLOCK’
(2)driver: 前提是read的条件不满足 ,read 检测struct file->f_flags是否有被设置过非阻塞标志,如果有的话,直接报错返回
[3]异步阻塞io概念:
底层实现:
struct file_operations ->poll
(1)poll_wait();
(2)返回满足条件的文件描述符的可读POLLIN 可写的掩码 POLLOUT
static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
功能:将阻塞项存放到对应的一个table
参数:struct file*指针
wait_address:等待队列头的指针对象
p:poll表
返回值:void
app实现:[select/poll]
[4]异步非阻塞io概念:
异步通知:
底层实现: int (*fasync) (int, struct file *, int);
(1)触发对应的信号
如果是要在app中read ,那么就在底层的write中 触发信号
如果是要在app中write ,那么就在底层的read中 触发信号
void kill_fasync(struct fasync_struct **fp, int sig, int band)
功能:触发信号,驱使应用层的信号处理函数执行
参数:fp:异步通知结构体的二级指针
nfds:对应的信号 eg :SIGIO
band:触发执行操作的方向 :POLL_IN/POLL_OUT
返回值:无
(2)将顶层的请求放入(移除)对应的队列中
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
功能:将顶层的请求放入(移除)对应的队列中
参数:fd:文件描述符
filp:file指针对象
on:表征此时是删除还是增加对应的请求 如果说on=0 移除 反之就是add
fapp:struct fasync_struct二级指针
返回值:It returns negative on error, 0 if it did no changes and positive if it added/deleted the entry.
(3)在release函数中完成对所有请求的移除
FASYNC:表征可以使用异步信号驱动IO
应用层实现:
(1)信号与其处理函数绑定
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
(2)设置标志
int fcntl(int fd, int cmd, ... /* arg */ );
功能:根据不同的命令码cmd去对fd执行不同的操作
参数:fd:文件描述符
cmd:命令码 //eg:F_SETFL设置对应的fd的flag
...:其余的参数
返回值:根据不同的命令码其返回值是不同的
/*F_GETFL Value of file status flags.
F_GETOWN Value of descriptor owner.*/
[2.1] 首先获取到该fd的对应flags
flags = fcntl(fd,F_GETFL)
fcntl(fd,F_SETFL,flags|FASYNC);
(3)设置当前进程id: F_SETOWN
fcntl(fd,F_SETOWN,getpid());
https://blog.csdn.net/zmjames2000/article/details/87879339
https://blog.csdn.net/zmjames2000/article/details/87868885
PIC:program interrupt controller
----------- --------
外设一-------->| P | INT | C |
外设二-------->| I |----- | P |
外设三-------->| C | 软件 | U |
... 硬件中断号----------- --------
PIC的作用:完成中断硬件中断号到软件中断号的映射,中断屏蔽,中断触发电平的设置等;
中断实现里硬件设备按需获得处理器关注的机制,与查询方式相比大大节省里cpu时间。
对于每一根中断线--->(硬件中断号)
struct irq_desc {
struct irq_data irq_data; //保存软件中断号irq与chip信息
irq_flow_handler_t handle_irq; //与中断触发电信号相关的函数
struct irqaction *action; //与具体设备的中断处理的抽象 设备的中断处理函数
struct module *owner; //中断所属的模块
...
} ____cacheline_internodealigned_in_smp;
irq_data结构体中的成员irq_data保存软件中断号irq与chip信息
struct irq_data {
unsigned int irq; //软件中断号
struct irq_chip *chip; //可编程中断控制器pic
...
};
struct irqaction {
irq_handler_t handler; // 指向中断服务程序
unsigned long flags; // 中断标志 :设定对应的中断触发标志
unsigned long mask; // 中断掩码
const char *name; // I/O设备名 cat /proc/interrupts
void *dev_id; // 用于共享中断时识别设备
struct irqaction *next; // 用于共享中断时指向下一个描述符
int irq; // IRQ线
struct proc_dir_entry *dir; // 指向IRQn相关的/proc/irq/n目录的描述符
};
linux中由若干中断号,将所有的中断的结构体,组织成一个中断数组
irq_desc [irq_max];
系统中的中断数组名 系统中中断的数量
irq = 10 ;
irq_desc[10] --->描述该中断的结构体对象
cat /proc/interrupts
函数API:
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
功能:申请对应的中断(绑定irq 与 中断处理函数 , 中断触发电平 , 中断名称)
参数:irq:中断号(软件)
handler:中断处理函数
flags:中断触发电平 IRQF_TRIGGER_FALLING IRQF_TRIGGER_RISING
name:中断名称 /proc/interrupts下可以看到
dev:中断设备的专属数据 ,一般非共享中断可以写为NULL ,反之共享中断必填
返回值:成功:等于0 失败:errno
void free_irq(unsigned int irq, void *dev_id)
功能:释放对应的中断
参数:irq:中断号
dev_id:中断设备的专属数据
返回值:无
#define IRQF_TRIGGER_NONE 0x00000000
#define IRQF_TRIGGER_RISING 0x00000001
#define IRQF_TRIGGER_FALLING 0x00000002
#define IRQF_TRIGGER_HIGH 0x00000004
#define IRQF_TRIGGER_LOW 0x00000008
#define IRQF_TRIGGER_MASK (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
#define IRQF_TRIGGER_PROBE 0x00000010
gic: interrupt-controller@10490000 {
compatible = "arm, cortex-a9-gic";
#interrupt-celss = <3>; //描述中断参数个数 是三个
interrupt-controller; //中断控制器 可以被其他节点引用
cpu-offset = <0x4000>;
reg <0x10490000 0x10000>, <0x10480000 0x1000> ....
};
pinctrl@11000000 {
gpx1: gpx1{
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
interrupt-parent = <&gic>;
interrupts = <0 16 0 >,<0 17 0>,<0 18 0>,
<0 19 0>,<0 20 0>,....
#interrupt-cells =<2>;
};
};
gpx1-1-key{
interrupt-parent = <&gpx1>
interrupt = <1 2> //1 表示< 0 17 0> 2表示 触发方式
};
也可以
xxxx-leds-gpio{
compatible = " xxxx, leds";
led2 = <&gpx2 7 0>
led3 = <&gpx1 0 0>
led4 = <&gpx3 4 0>
led5 = <&gpx5 5 0>
};
//中断域 中断 触发方式
interrupts = <0 16 0>
中断域: 0 spi shared
1 ppi private
2 sgi
触发方式: 1.上升沿 2.下降沿 3.高电压 4.低电平
int irq = 0;
struct device_node *np;
np = of_find_node_by_path("/key@0x12345678");
irq = irq_of_parse_and_map(np,0);
ret = request_irq(irq,key_irq_handler,IRQF_TRIGGER_FALLING,NAME,NULL);
free_irq(irq,NULL);
1. 设备树的作用? 提供对应的设备信息
2.用设备树是为了获取具体的设备信息
3. 具体的信息是在设备树种被写成对应的节点
4. 具体的设备信息是被作为该节点的属性填充的
https://blog.csdn.net/zmjames2000/article/category/8689114
make dtbs
中断底半部:
为了解决中断既能快速响应,又能执行大量的任务。
中断顶半部:快速响应 ------> 中断状态的获取 中断标志的清除
中断底半部:执行大量的任
上半部能打断底部的执行。必须上半部执行完成才能执行下半部。
1.tasklet 中断上下文,不允许睡眠
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
static inline void tasklet_schedule(struct tasklet_struct *t)
功能:实现tasklet的调度
参数:t:struct tasklet_struct指针对象
返回值:无
1.
struct tasklet_struct task = {
.func = task_func,
.data = 999,
};
2.
int irq = 0;
struct device_node *np;
np = of_find_node_by_path("/key@0x12345678");
ret = request_irq(irq,key_irq_handler,IRQF_TRIGGER_FALLING,NAME,NULL);
irqreturn_t key_irq_handler(int irq, void *data)
{
tasklet_schedule(&task);
return IRQ_HANDLED;//表征中断处理成功
}
void task_func(unsigned long data)//底半部处理函数
{
unsigned long i = 100000;
while(i--);
printk("%s,data:%ld\n",__func__,data);
}
2.workqueue:进程上下文,,允许睡眠
struct work_struct {
work_func_t func;//工作队列的底半部处理函数
};
typedef void (*work_func_t)(struct work_struct *work);
函数API:
INIT_WORK(struct work_struct *work,work_func_t func);
完成工作队列的初始化(绑定工作队列底半部处理函数)
/*
#define INIT_WORK(_work, _func) \
do { \
__INIT_WORK((_work), (_func), 0); \
} while (0)
*/
static inline bool schedule_work(struct work_struct *work)
功能:实现work_struct的调度
参数:work:struct work_struct指针对象
返回值: * Returns %false if @work was already on the kernel-global workqueue and %true otherwise.
1.
struct work_struct wk;
INIT_WORK(&wk,work_func);
2.
int irq = 0;
struct device_node *np;
np = of_find_node_by_path("/key@0x12345678");
ret = request_irq(irq,key_irq_handler,IRQF_TRIGGER_FALLING,NAME,NULL);
irqreturn_t key_irq_handler(int irq, void *data)
{
//tasklet_schedule(&task);
schedule_work(&wk);
return IRQ_HANDLED;//表征中断处理成功
}
void work_func(struct work_struct *work)
{
unsigned long i = 100000;
while(i--);
printk("%s\n",__func__);
}
1、内核中gpio的函数API--->gpiolib.c
[1]申请、释放gpio管脚
int gpio_request(unsigned gpio, const char *label)
功能:申请gpio
参数:gpio:对应的gpio的管脚号
label:gpio标签//eg:“gpx1_1”
返回值:成功:0 失败:errno
void gpio_free(unsigned gpio)
功能:释放gpio
参数:gpio:对应的gpio的管脚号
返回值:无
[2]标记gpio的使用方向包括输入还是输出/*成功返回零失败返回负的错误值*/
static inline int gpio_direction_input(unsigned gpio)
功能:完成相应的gpio管脚的输入功能设置
参数:gpio:对应的gpio的管脚号
返回值:成功:0 失败:errno
static inline int gpio_direction_output(unsigned gpio, int value)
功能:完成相应的gpio管脚的输出功能设置
参数:gpio:对应的gpio的管脚号
value:设置输出的值
返回值:成功:0 失败:errno
[3]获得gpio引脚的值和设置gpio引脚的值(对于输出)
static inline void gpio_set_value(unsigned int gpio, int value)
功能:完成对应GPIO管脚的输出数据的设置
参数:gpio:对应的gpio的管脚号
value:设置输出的电平
返回值:成功:0 失败:errno
static inline int gpio_get_value(unsigned int gpio)
功能:获取对应的GPIO管脚的数据
参数:gpio:对应的gpio的管脚号
返回值:返回对应管脚的电平状态
[4]gpio当作中断口使用
static inline int gpio_to_irq(unsigned int gpio)
功能:根据GPIO管脚号来得到该管脚对应的中断号
参数:gpio:对应的gpio的管脚号
返回值:返回对应管脚的电平状态
static inline int of_get_named_gpio(struct device_node *np,const char *propname, int index)
功能:根据节点中GPIO管脚对应的“键名”来获取gpio管脚序号
参数:np:当前gpio管脚所在的节点
propname:“键名”
index:序号
返回值:成功: GPIO管脚序号 失败:errno
1.
添加led的设备节点信息
led{
compatible = "xxxxx,led";
led=<&gpf3 4 0>,<&gpf3 5 0>;
0 1
};
2.
struct device_node *np;
np = of_find_node_by_path("/led");
led4_gpio = of_get_named_gpio(np,"led",0);
ret = gpio_request(led4_gpio,"GPF3_4");
gpio_direction_output(led4_gpio,1);
gpio_set_value(led4_gpio,0);
gpio_free(led4_gpio);
3、用户空间gpio的调用
内核配置 : /sys/class/gpio的交互界面的
Device Drivers --->
--- GPIO Support
[*] /sys/class/gpio/... (sysfs interface)
配置+编译+拷贝+挂载
(1)/sys/class/gpio
echo 220 > export
出现:/sys/class/gpio/gpio220
echo 220 > unexport
(2)cd /sys/class/gpio/gpio220
设置输入、输出方向
echo in/out > direction
(3)给管脚电平赋值
echo 1/0 > value
https://blog.csdn.net/zmjames2000/article/details/87812384
2.定时器
[1]定时器
[1.1]结构体
struct timer_list {
unsigned long expires;//定时时间 jiffies + 2
void (*function)(unsigned long);//定时器时间达到后的处理函数
unsigned long data;//给func传参
};
[2.2]函数
void init_timer(struct timer_list *timer)
功能:初始化定时器
参数:timer:定时器指针对象
返回值:无
/*
#define init_timer(timer) \
__init_timer((timer), 0)
*/
void add_timer(struct timer_list *timer)
功能:注册定时器
参数:timer:定时器指针对象
返回值:无
int del_timer(struct timer_list *timer)
功能:注销定时器
参数:timer:定时器指针对象
返回值: del_timer() of an inactive timer returns 0, del_timer() of an active timer returns 1
int mod_timer(struct timer_list *timer, unsigned long expires)
功能:修改定时器的定时时间
参数:timer:定时器指针对象
expires:定时到达时间
返回值:mod_timer() of an inactive timer returns 0, mod_timer() of an active timer returns 1
[3]
HZ: 1s = 1hz
t = 1/f;
CONFIG_HZ=200
1s = 200 jiffies
[4]jiffies时间操作
unsigned int jiffies_to_msecs(const unsigned long j);
unsigned int jiffies_to_usecs(const unsigned long j);
unsigned long msecs_to_jiffies(const unsigned int m);
unsigned long usecs_to_jiffies(const unsigned int u);
unsigned long timespec_to_jiffies(const struct timespec *value);
void jiffies_to_timespec(const unsigned long jiffies, struct timespec *value);
unsigned long timeval_to_jiffies(const struct timeval *value);
void jiffies_to_timeval(const unsigned long jiffies,struct timeval *value);
1.
struct timer_list tm;
tm.expires = jiffies + msecs_to_jiffies(10);
tm.function = tm_function;
tm.data = 12233;
init_timer(&tm);
add_timer(&tm);
2.
struct device_node *np;
np = of_find_node_by_path("/key2_gpio");
ey2_gpio = of_get_named_gpio(np,"key2",0);
io_request(key2_gpio,"key2_gpio");
irq = gpio_to_irq(key2_gpio);
ret = request_irq(irq,key2_irq_handler,IRQF_TRIGGER_FALLING,"key2_gpio",NULL);
irqreturn_t key2_irq_handler(int irq, void *data)
{
mod_timer(&tm,jiffies + msecs_to_jiffies(10));
return IRQ_HANDLED;
}
void tm_function(unsigned long data)
{
int value = 0;
value = gpio_get_value(key2_gpio);
if(!value){
while(!gpio_get_value(key2_gpio));
printk("key2 enter...\n");
}
}
内核中: 设备=====总线=====驱动
引入总线设备驱动:可以将设备信息 与 驱动信息 分离开来
[1]总线
[1.1]总线结构体
struct bus_type {
const char *name;//总线名称 platform ,i2c
int (*match)(struct device *dev, struct device_driver *drv);//指定总线上 device device_driver 的匹配规则
// 只有match成功后,才能执行probe
};
[1.2]相关API
int bus_register(struct bus_type *bus); //注册总线对象 /sys/bus/i2c /sys/bus/platform
void bus_unregister(struct bus_type *bus);//注销总线对象
[1.3]注册成功现象
[2]设备
[1.1]设备结构体
struct device {
//struct kobject kobj;//linux设备对象
const char *init_name; /*设备名称*/
struct bus_type *bus; /* 该设备被挂接的总线 */
struct device_driver *driver; /* 支持该设备的驱动对象*/
dev_t devt; /* 设备号*/
void (*release)(struct device *dev);/*设备移除时会被执行*/
};
[1.2]相关API
int device_register(struct device *dev)//注册device /sys/bus/platform/devices
void device_del(struct device *dev)//注销
[1.3]注册成功现象
[3]驱动
[1.1]驱动结构体
struct device_driver {
const char *name; //名称
struct bus_type *bus;//依附的宗霞
const struct of_device_id *of_match_table;//设备驱动的一种匹配方式 --->设备树匹配
int (*probe) (struct device *dev);//总线上的设备与驱动匹配成功后自动执行
int (*remove) (struct device *dev);//总线上的设备与驱动任意一方移除的时会被调用
};
[1.2]相关API
int driver_register(struct device_driver *drv) //驱动注册 /sys/bus/platform/drivers
void driver_unregister(struct device_driver *drv)//驱动注销
[1.3]注册成功现象
[1]platform总线
[1.1]总线结构体
struct bus_type platform_bus_type = {
.name = "platform",
.match = platform_match,
};
[2]platform设备 + 资源
[1.1]设备结构体
struct platform_device {
const char *name;//名称
int id;// -1
struct device dev;//设备模型的参数
struct resource *resource;//填充对应的设备硬件信息
u32 num_resources;//设备信息的数量
const struct platform_device_id *id_entry;//兼容设备
};
struct resource {
unsigned long flags;//用于区分不同的资源 IORESOURCE_MEM IORESOURCE_IRQ
resource_size_t start;
resource_size_t end;
};
struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
};
[1.2]相关API
int platform_device_register(struct platform_device *pdev)
功能:注册platform_device
参数:pdev:struct platform_device的指针对象
返回值:成功: 0 错误 :errno
void platform_device_unregister(struct platform_device *pdev)
功能:注销platform_device
参数:pdev:struct platform_device的指针对象
返回值:无
[1.3]注册成功现象
[3]platform驱动
[1.1]驱动结构体
struct platform_driver {
int (*probe)(struct platform_device *);//platform_device与platform_driver匹配成功后自动执行
int (*remove)(struct platform_device *);//platform_device与platform_driver任意一方移除时自动执行
struct device_driver driver;
const struct platform_device_id *id_table;
};
struct device_driver {
const char *name; //名称
struct module *owner;//指向该模块
const struct of_device_id *of_match_table;//设备驱动的一种匹配方式 --->设备树匹配
};
platform_device与platform——driver匹配成功的规则:
device driver
[1.2]相关API
int platform_driver_register(struct platform_driver *drv)
功能:注册platform_driver
参数:pdev:struct platform_driver的指针对象
返回值:成功: 0 错误 :errno
/*#define platform_driver_register(drv) \
__platform_driver_register(drv, THIS_MODULE)*/
void platform_driver_unregister(struct platform_driver *);
功能:注销platform_driver
参数:pdev:struct platform_driver的指针对象
返回值:无
[1.3]注册成功现象
为什么platform_device/platform_driver注册进内核,匹配成功的话会自动执行probe?
思路:找注册函数?(了解即可)
(1)__platform_driver_register
driver_register
ret = bus_add_driver(drv);
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
if (drv->bus->p->drivers_autoprobe) {
error = driver_attach(drv);
if (error)
goto out_unregister;
}
(2)int driver_attach(struct device_driver *drv)
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
if (!driver_match_device(drv, dev))//执行总线中的match
if (!dev->driver)
driver_probe_device(drv, dev);
ret = really_probe(dev, drv);//probe函数
总线 设备 驱动模型中 对于设备及驱动的匹配 : bus_type->match
xxx_driver->probe
所有的 总线 设备 驱动 模型中,对于设备及驱动的匹配:bus_type->match 先执行这个函数。
如果匹配成功就会执行 xxx_driver->probe 这个函数
adc
adc{
compatible = "xxxx,adc";
reg = <0x126c0000 0x20>;//根据adc的寄存器的地址进行填充
interrupt-parent = <&combiner>;//根据第九章 第十章 中断控制器 combiner控制器进行相关的填充
interrupts = <10 3>; //第10组的第三个序号
};
combiner:interrupt-controller@10440000 {
compatible = "samsung,exynos4210-combiner";
interrupt-controller;//中断控制器
#interrupt-cells = <2>;//如果说继承该“combiner”,使用该“combiner”中的一个具体的中断,
//那么对于该中断的描述应该是两个成员
reg = <0x10440000 0x1000>;
0 1 2 3
interrupts = <0 0 0>, <0 1 0>, <0 2 0>, <0 3 0>,
<0 4 0>, <0 5 0>, <0 6 0>, <0 7 0>,
<0 8 0>, <0 9 0>, <0 10 0>, <0 11 0>,
<0 12 0>, <0 13 0>, <0 14 0>, <0 15 0>;
};
intG10_7 DMC1_PPC_PEREV_M DMC1
10 42 intG10_6 DMC1_PPC_PEREV_A DMC1
intG10_5 DMC0_PPC_PEREV_M DMC0
intG10_4 DMC0_PPC_PEREV_A DMC0
intG10_3 ADC Geberal ADC
2.pwm
pwm占空比就是一个脉冲周期内高电平的所整个周期占的比例。例如1秒高电平1秒低电平的PWM波占空比是50%。
pwm就是脉冲宽度调制,应用在从测量、通信到功率控制与变换的许多领域中。
https://blog.csdn.net/zmjames2000/article/details/87969719