Kernel Driver API
- 0. 简介
- 1. module
- 2. drive
- 3. udev / mdev device
- 4. class
- 5. dtb -> OF API
- 6. pinctrl 子系统
- 7. GPIO 子系统
- 8. IIC
- 9. SPI
- 10. top half 顶半部/上半部 中断
- 11. bottom half 底半部/下半部 中断
- 12. 时钟
- 13.DMA
- 14. PWM
- 15. ADC
- 16. 并发和竞态
- 17 阻塞和非阻塞 IO
- 18 异步通知和异步 I/O
- 19 other
0. 简介
0.1 内核态
0.2 内核态错误输出
· 以下的编程的执行都是在内核态的,使用不建议再把用户态的接口加载进来使用(eg:printf,sanf,perror等函数都是用户态的)
#include <linux/printk.h>
pr_emerg(); //紧急级别信息
pr_alert(); //警戒级别信息
pr_crit(); //临界级别信息
pr_err(); //错误级别信息
pr_warning(); //警告级别信息
pr_notice(); //注意级别信息
pr_info(); //普通级别信息
pr_debug(); //调试级别信息
· 用法和prinf很类似
· 这些错误都会输出到系统日志中,而不会输出到终端中
· pr_emerg()级别的错误会导致程序停止运行,而其他级别的错误不会
#include <linux/errno.h>
#define EFAULT 9 /* Bad address */
#define EINTR 4 /* Interrupted system call */
#define EIO 5 /* Input/output error */
#define ENXIO 6 /* Device or resource busy */
#define E2BIG 7 /* Argument list too long */
#define ENOMEM 8 /* Out of memory */
#define EACCES 9 /* Permission denied */
EBUSY 是 Linux 内核中的一个错误码(error code),表示资源不可用。
在errno.h头文件中,除了EFAULT错误码之外,还定义了许多其他的系统错误码,包括:
· EINTR:表示系统调用被中断,通常是由于信号中断导致的。
· EIO:表示输入/输出操作出错,通常是由于磁盘I/O错误或网络I/O错误导致的。
· ENXIO:表示所请求的设备或资源不存在。
· E2BIG:表示请求的大小超过了系统限制。
· ENOMEM:表示没有足够的内存可用。
· EACCES:表示访问权限不足。
· EFAULT:与之前提到的EFAULT错误码相同,表示访问了无效的内存地址。
#define IS_ERR(ptr) ((unsigned long)(ptr) >= (unsigned long)-MAX_ERRNO)
· 该宏将指针 ptr 转换为一个无符号长整型数,并判断该数是否大于等于 -MAX_ERRNO,如果是,则表示该指针是一个错误指针,否则表示该指针不是一个错误指针。
在使用 IS_ERR 宏时,需要注意以下几点:
· 如果指针 ptr 是空指针,则 IS_ERR 宏返回的结果为 1,表示该指针是一个错误指针
· 如果指针 ptr 是一个有效的指针,并且指针所指向的内存区域已经被释放或者无效,则 IS_ERR 宏返回的结果为 1,表示该指针是一个错误指针
· 如果指针 ptr 是一个有效的指针,并且指向的内存区域仍然有效,则 IS_ERR 宏返回的结果为 0,表示该指针不是一个错误指针
#define PTR_ERR(ptr) ((unsigned long)(ptr))
· PTR_ERR 是一个宏,用于获取指针的错误码。在 Linux 内核中,当一个指针出现错误时,可以使用 PTR_ERR 宏获取指针的错误码,以便进行错误处理
1. module
1.1 模块加载函数 __init
· 模块加载函数,当通过insmod或modprobe命令加载kernel module时,module的初始化函数会被自动调用执行,完成模块的初始化
1.1.1 代码段
#include <liunx/init.h>
static __init int hello_init(void)
{
/* module init code */
return 0;
}
module_init(hello_init);
· #include <liunx/init.h> 是Linux初始化相关的头文件
· static 为静态函数,只具备文件作用域
· __init 标识,有__init标识的函数只会在kernel初始化的时候才被加载到内存调用,调用结束后即对应的代码段内存空间被释放,再次调用该标识的函数就会报错
· module_init 是一个回调函数
1.1.2 数据段
static __initdata int a = 1;
· __initdata标识,拥有该标识的数据,在kernel初始化完成后,该数据在内存占用的数据段会被释放
1.2 模块卸载函数 __exit
1.2.1 代码段
#include <liunx/init.h>
static __exit void hello_exit(void)
{
/* module exit code */
return 0;
}
module_exit(hello_exit);
· #include <liunx/init.h> 是Linux初始化相关的头文件
· static 为静态函数,只具备文件作用域
· __exit 标识,有__exit标识的函数只会在模块卸载时才被到加载到内存中,调用结束后即对应的代码段内存空间被释放,再次调用该标识的函数就会报错。但如果模块是编译到kernel中,那模块将失去热插拔的特性,卸载模块函数也失去意义
· module_init 是一个回调函数
1.2.2 数据段
static __exitdata int a = 1;
· __exitdata标识,拥有该标识的数据,在kernel模块卸载完成后,该数据在内存占用的数据段会被释放
1.3 模块许可证声明
· 许可证说明:描述kernel module的许可权限,如果不声明LICENSE(许可证),module加载时,将会收到kernel被污染(Kernel Tainted)的警告
· 在Linux module领域,可接受的LICENSE包括:''GPL''、''GPL v2''、''GPL and additional rights''、''Dual BSD/GPL''、''Dual MPL/GPL''、''Proprietary''(关于module是否可采用非GPL许可权,如''Proprietary''现在还是饱受争议的)
· 大多数情况下,kernel module都应遵循GPL兼容许可权,最常见的是使用GPL v2版本的
1.3.1声明许可证的实现
以最常见的GPL v2为例
#include <linu/module.h>
MOUDLE_LICENSE("GPL v2");
1.3.2 使用module"规避"GPL
· 只使用GPL module的运行结果,间接的使用module''规避''GPL,但这也是具有争议的
· 一般认为,保守的做法是Linux kernel 不能使用非GPL LICENSE
1.4 模块参数(可选)
1.5 模块导出符号(可选)
· /proc/kallsyms 文件对应着内核符号表,里面记录着符号以及符号所在的内存地址
module 通过下面的宏将符号导出到内核符号表中
#include <linux/export.h>
EXPORT_SYMBOL(符号名); //非GPL许可证的module使用
EXPORT_SYMBOY_GPL(符号名); // 只适用与GPL许可证的module使用
1.6 模块作者等信息(可选)
#include <linux/module.h>
MODULE_AUTHOR(author); //作者信息
MODULE_DESCRIPTION(description); //描述模块信息
MODULE_VERSION(version_string); //版本描述
MODULE_DEVICE_TABLE(table_info); //对于USB,PCI等设备驱动,通常会创建一个表明该驱动模块所支持的是设备
MODULE_ALIAS(alternate_name); //模块别名
1.7 模块使用计数(附加)
· kernel module的使用计数为0时,才允许module的卸载
· module的使用计数的增加和减少由kernel操作,操作对象为dev -> owner
· 但可能特殊情况我们也需自己管理module的使用次数
#include <linux/module>
try_module_get(dev -> owner); //module 使用次数加一
module_put(dev -> owner); //module 使用次数减一
1.8 模块的依赖性
1.8.1 模块的命名与别名
· 模块的名字一般是系统规定的,为你模块文件去掉后缀后的名字,比如DS18B20.ko的模块,模块名字一般为DS18B20
1.8.1.1 模块名字的宏
#include <linux/module.h>
.owner = THIS_MODULE; //THIS_MODULE为当前模块名的宏
1.8.1.2 模块的别名
#include <linux/module.h>
MODULE_ALIAS("my_driver_name"); //为当前模块创建一个别名
2. drive
· kernel将设备驱动分成字符设备驱动,块设备驱动,网络设备驱动
2.1 设备号的管理
· 设备号是一个32位的数据,高12位表主设备号;低20位表次设备号
· 相同的主设备号的设备,可以在驱动程序中共享代码
2.1.1 获取主次设备号
#include <linux/types.h>
MAJOR(dev_t dev); //获取主设备号
MINOR(dev_t dev); //获取次设备号
MKDEV(int major, int minor); //根据主次设备号,生成设备号
2.2.2 分配和释放设备号
2.2.2.1 设备号的申请
#include <linux/fs.h>
#include <linux/types.h>
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, const char *name);
· 该函数用于向 kernel 申请设备号,由kernel给予空闲的主设备号
· dev_t *dev:用于存储分配的起始设备号(即主设备号和第一个次设备号)的指针。
· unsigned int firstminor:起始的次设备号
· unsigned int count:申请的次设备号的数量
· const char *name:设备名称,可以为设备号分配提供更直观的警告输出
· 成功返回0,设备返回非零值
2.2.2.2 设备号的指定申请
#include <linux/types.h>
int register_chrdev_region(dev_t from, unsigned count, const char *name);
· 该函数由开发人员自行指定主设备号申请,必须要是空闲的主设备号,kernel才会接收申请的请求,否则申请失败
· dev_t from:指定申请的设备号(主设备号 和 起始次设备号)
· unsigned count:申请设备号的数量
· · const char *name:设备名称,可以为设备号分配提供更直观的警告输出
· 成功返回0,设备返回非零值
2.2.2.3 设备号的释放
#include <linux/fs.h>
#include <linux/types.h>
void unregister_chrdev_region(dev_t form, unsigned int count);
· form:要释放的设备号
· count:要释放的设备个数
· 需设备的对应的驱动结构体释放后,再释放设备号
2.2 实现对应的设备驱动
· 在 Linux 中设备和驱动是分离的,你即要向 kernel 注册 设备也要注册驱动,二者匹配后才能使用对应的设备,而且设备是搭载在总线上面的,由总线去根据设备和驱动提供的信息去匹配二者
· 对于不支持设备树的 Linux 或是不使用设备树的 Linux 来说,驱动的开发就需要编写 device module 和 drive module,然后向 kernel 加载 module,由总线进行匹配
· 对于支持设备树的 Linux 或是使用设备树的 Linux来说,设备树就是用来描述 device 的信息的,其内部是 kernel 解析 devicetree 然后注册对应的设备信息,你要做的只是修改设备树然后编写 drive module 即可
2.2.1 字符设备驱动
2.2.1.1 cdev 结构体
· 在Linux kernel中,使用cdev结构体描述一个字符设备
#include <linux/cdev.h>
struct cdev {
struct kobject kobj; //对应的kobject对象
struct module *owner; //所属的module
struct file_operations *ops; //文件操作结构体
struct list_head list; //链表节点
dev_t dev; //设备号
unsigned int count; //次设备号
};
2.2.1.2 申请cdev结构体
#include <linux/cdev.h>
struct cdev *cdev_alloc(void)
{
struct cdev*p = kzalloc(sizeof(struct cdev), GFP_KERNEL); //申请cdev的内存空间
if (p) {
INIT_LIST_HEAD(&p->list); //初始化链表
kobject_init(&p->kobject, &ktype_cdev_dynamic); //初始化kobject
}
return p;
};
· 返回一个cdev结构体指针
2.2.1.3 cdev结构体的初始化
· 初始化是必要的操作,他清空指针指向的内存空间,同时初始化内部的成员
#include <linux/cdev.h>
void cdev_init(struct cdev *, struct file_operations *)
{
memset(cdev, 0, sizeof *cdev); //将cdev指向的内存空间清零
INIT_LIST_HEAD(&cdev->list); //初始化链表
kobject_init(&cdev->kobj, &ktype_cdev_default); //初始化kobject
cdev->ops = fops; //将传入的文件操作结构体指针赋值给cdev的ops
}
2.2.1.4 向系统添加cdev
int cdev_add(struct cdev *, dev_t dev, unsigned int count);
· cdev*:cdev结构体指针
· dev:设备号
· count:次设备的数量
· 返回值:成功为0,失败为非0
2.2.1.5 删除系统的cdev
void cdev_del(struct cdev *);
· cdev*:cdev结构体指针
· 删除cdev函数一般在module的卸载函数中调用,先调用该函数删除系统对应的cdev,然后再调用设备号释放函数,释放设备号
2.2.2 块设备驱动
2.2.3 网络设备驱动
2.3 设备文件file_operations的实现
2.3.1 file_operations结构体
#include <linux/fs.h>
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iterate) (struct file *, struct dir_context *);
int (*iterate_shared) (struct file *, struct dir_context *);
__poll_t (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
unsigned long mmap_supported_flags;
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
loff_t, size_t, unsigned int);
int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,
u64);
int (*dedupe_file_range)(struct file *, loff_t, struct file *, loff_t,
u64);
int (*fadvise)(struct file *, loff_t, loff_t, int);
} __randomize_layout;
2.3.2 .open 的实现
#include <linux/fs.h>
int (*open) (struct inode *, struct file *);
· 在用户层open函数是要返回文件的 fd 文件描述符的,还要设置文件的打开方式等,很显然内核帮我们把那部分的内容做了,然后把对应的信息放在 struct inode * 和 struct file * 传给我们
· 我们需要根据 inode 和 file 结构体内的成员来实现 open 函数
2.3.3 .read 和 .write的实现
· 由于内核态的高权限,他几乎可以访问内存中的任何地址空间,但是高权限也带来高风险
· 对于外来的地址空间,内核都需要进行内存空间合法性的检查,防止恶意欺骗内核
· 所以内核操作内存需要调用函数,函数内进行操作内存对象合法性的判断,和内存操作
copy_to_user
· 复制数据到用户态
#include <linux/uaccess.h>
static inline int copy_to_user(void __user volatile *to, const void *from,
unsigned long n)
{
__chk_user_ptr(to, n); /* 内存合法性判断 */
volatile_memcpy(to, from, n); /* 内存操作 */
return 0;
}
· void __user volatile *from :用户态数据
· const void *from :内核态数据
· unsigned long n :数据长度,一般使用 sizeof 关键字获取数据长度
· 内核向用户写入,并检查操作内存的合法性
copy_form_user
· 复制来自用户态的数据
#include <linux/uaccess.h>
static inline int copy_from_user(void *to, const void __user volatile *from,
unsigned long n)
{
__chk_user_ptr(from, n); /* 内存合法性判断 */
volatile_memcpy(to, from, n); /* 内存操作 */
return 0;
}
· void *to :内核态数据
· const void __user volatile *from :用户态数据
· unsigned long n :数据长度,一般使用 sizeof 关键字获取数据长度
· 用户向内核写入,并检查操作内存的合法性
2.3.3 unlocked_ioctl 的实现
· file_operations 中有 unlocked_ioctl 和 compat_ioctl, 其中 compat_ioctl 用于处理旧版本的应用程序对设备的兼容性 I/O 控制操作(IOCTL),所以我们主要实现 unlocked_ioctl 就可以
· 其实你也可以不按照 linux 中的命令,但是容易造成命令污染
· 关于系统已使用的命令记录在 Documentation/ioctl/ioctl-number.txt 中,如果不同的设备支持相同的命令,就会造成命令码污染
设备类型 | 序列号 | 数据传输方向 | 数据尺寸 |
---|---|---|---|
8 bit | 8 bit | 2 bit | 13/14 bit |
type | nr | dir | size |
#include <linux/ioctl.h> //内核态的头文件
#include <sys/ioctl.h> //用户态的头文件
#define _IO(type, nr) //an ioctl with no parameters
#define _IOW(type, nr, size) //an ioctl with write parameters (copy_from_user)
#define _IOR(type, nr, size) //an ioctl with read parameters (copy_to_user)
#define _IOWR(type, nr, size) //an ioctl with both write and read parameters
//数据传输方向字段的宏定义
#define _IOC_NONE 0X00
#define _IOC_READ 0X01
#define _IOC_WRITE 0X10
//_IOC_READ | _IOC_WRITE 0x11
· type 设备类型字段为一个“幻数”,可以是 0x00 ~ 0xff 的数,内核中的 ioctl-number.txt 中给出了一些推荐的和已被使用的“幻数”
· nr 序列号是主要区分同一个设备中的不同命令
· dir 数据传输方向
· size 数据长度字段表示涉及的用户数据的大小,这个成员依赖于体系结构,通常是 13 / 14 bit
2.4 设备和驱动的匹配
· 设备和驱动的匹配是通过对应的总线去完成的
· 无设备树的设备和驱动通过 name 属性去匹配彼此
· 有设备树的设备和驱动通过设备树节点的 compatible 属性去匹配彼此
2.4.1 无设备树匹配
· 无设备树需要编写 device module 加载到 kernel 中
· 无设备树也需要编写 drive module 加载到 kernel 中
· device 和 drive 通过自定义的 name 属性进行匹配,device 和 drive 允许单独存在
2.4.2 有设备树匹配
· 有设备树只需要编写 drive module 即可,通过设备树节点的 compatible 属性进行匹配
· 但是需要修改设备树节
2.4.3 device module
2.4.3.1 platform device API
2.4.3.1.1 平台设备结构体
struct platform_device {
const char *name;
int id;
struct device dev;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
bool id_auto;
};
const char *name:平台设备的名称。
int id:设备的 ID。
struct device dev:设备对象。
u32 num_resources:设备资源的数量。
struct resource *resource:指向设备资源的指针数组。
const struct platform_device_id *id_entry:平台设备 ID 表项。
bool id_auto:ID 自动分配标志。
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj;
const char *init_name;
struct device_type *type;
struct mutex mutex;
struct bus_type *bus;
struct device_driver *driver;
void *platform_data;
struct dev_pm_info power;
struct dev_pm_domain *pm_domain;
bool is_registered;
bool uevent_suppress;
struct completion *uevent_done;
struct dev_links_info links;
struct list_head deferred_probe;
struct device_node *of_node;
struct acpi_dev_node *acpi_node;
};
struct device *parent:指向父设备的指针。
struct device_private *p:指向设备私有数据的指针。
struct kobject kobj:设备关联的内核对象。
const char *init_name:设备的初始名称。
struct device_type *type:设备类型。
struct mutex mutex:设备的互斥锁。
struct bus_type *bus:设备所属的总线类型。
struct device_driver *driver:设备关联的驱动程序。
void *platform_data:指向平台数据的指针。
struct dev_pm_info power:设备的电源管理信息。
struct dev_pm_domain *pm_domain:设备的电源管理域。
bool is_registered:设备是否已注册。
bool uevent_suppress:是否抑制设备的 uevent。
struct completion *uevent_done:指向 uevent 完成的指针。
struct dev_links_info links:设备的链接信息。
struct list_head deferred_probe:延迟探测的设备链表。
struct device_node *of_node:设备的设备树节点。
struct acpi_dev_node *acpi_node:设备的 ACPI 节点。
2.4.3.1.2 注册平台设备
#include <linux/platform_device.h>
int platform_device_register(struct platform_device *pdev);
· int: 执行成功返回 0,执行失败返回负数
· struct platform_device *pdev: 平台设备结构体
2.4.3.1.3 注销平台设备
#include <linux/platform_device.h>
void platform_device_unregister(struct platform_device *pdev)
· struct platform_device *pdev: 平台设备结构体
2.4.4 driver module
2.4.4.1 驱动结构体
#include <linux/device.h>
struct device_driver {
const char *name; //用于无设备树的设备驱动匹配
struct bus_type *bus;
struct module *owner;
const char *mod_name;
const char *mod_ver;
struct device_driver *next;
int (*probe)(struct device_driver *drv);
int (*remove)(struct device_driver *drv);
void (*shutdown)(struct device_driver *drv);
int (*suspend)(struct device_driver *drv, pm_message_t state);
int (*resume)(struct device_driver *drv);
const struct attribute_group **groups;
const struct attribute_group *groups_autoprobe;
const struct driver_attribute **attrs;
const struct driver_attribute *driver_attrs;
const struct device_attribute **dev_attrs;
const struct device_attribute *dev_attrs_groups[2];
bool (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe_type)(struct device *dev, const char *type);
int (*suspend_late)(struct device *dev, pm_message_t state);
int (*resume_early)(struct device *dev);
const struct of_device_id *of_match_table; //acpi_device_id有设备树的设备驱动的匹配
const struct acpi_device_id *acpi_match_table; //用于高级电源设备的匹配
};
#include <linux/of_device.h>
struct of_device_id {
const char *compatible; /* 设备的兼容字符串 */
const char *data; /* 设备数据 */
struct device_node *node; /* 设备节点 */
struct fwnode_handle *fwnode; /* 设备的 firmware node */
};
2.4.4.1 platform driver API
2.4.4.1.1 平台设备驱动结构体
#include <linux/platform_driver.h>
struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
kernel_ulong_t driver_data;
};
struct platform_driver {
int (*probe)(struct platform_device *); //总线匹配后,执行的回调函数
int (*remove)(struct platform_device *); //移除设备后。执行的回调函数
struct device_driver driver; //继承的驱动结构体
const struct platform_device_id *id_table; //驱动兼容的设备类型
};
2.4.4.1.2 注册平台驱动
#include <linux/platform_device.h>
int platform_driver_register(struct platform_driver *drv);
· int: 执行成功返回 0,执行失败返回负数
· struct platform_driver *drv: 平台驱动结构体
2.4.4.1.3 注销平台驱动
#include <linux/platform_device.h>
void platform_driver_unregister(struct platform_driver *drv);
· struct platform_driver *drv: 平台驱动结构体
2.4.4.2 IIC driver API
2.4.4.2.1 IIC 设备驱动结构体
#include <linux/i2c.h>
struct i2c_driver {
unsigned int class;
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
//...
struct device_driver driver;
const struct i2c_device_id *id_table;
};
2.4.4.2.2 添加IIC 驱动
#include <linux/i2c.h>
int i2c_add_driver(struct i2c_driver *driver);
· int : 执行成功返回 0,执行失败返回负值
· struct i2c_driver *driver : 要添加的 i2c 驱结构体对象
2.4.4.2.3 删除 IIC 驱动
#include <linux/i2c.h>
void i2c_del_driver(struct i2c_driver *driver);
· struct i2c_driver *driver : 要删除的 i2c 驱结构体对象
2.4.1 设备属性匹配设备
· 设备与驱动的匹配,通过识别设备树节点的 compatible(兼容性)属性,来匹配设备树节点和驱动程序
// 设备节点属性与驱动程序属性的匹配函数,用于驱动程序与设备匹配
static const struct of_device_id my_i2c_of_match[] = {
{ .compatible = "myi2c" }, // 设备名称
{ }, //为空,指明结束处
};
// 声明一个设备属性表,用于将设备名称和设备ID绑定
MODULE_DEVICE_TABLE(of, my_i2c_of_match);
2.4.2 驱动绑定设备
· 一个驱动程序可以支持多个设备,每个设备具有唯一的设备ID(在实践中可能就是一个数字)。当系统在探测设备时,它将会检查设备ID并与驱动程序的ID表的设备ID进行匹配,如果匹配成功,则使用驱动程序。
// 声明一个设备驱动的ID表,用于将设备ID和设备名称关联起来
static const struct i2c_device_id my_i2c_idtable[] = {
{ "myi2c", 0 }, // 设备名称和设备ID
{ }, //为空,指明结束处
};
// 声明一个i2c模块的设备表,用于将设备ID与驱动程序关联
MODULE_DEVICE_TABLE(i2c, my_i2c_idtable);
2.4.3 定义驱动程序
· 对于不同的驱动,使用不同的总线来进行声明驱动和匹配设备属性
// 定义一个i2c驱动程序,包含驱动名称、设备匹配表和探测函数等信息
static struct i2c_driver my_i2c_driver = {
.driver = {
.name = "my_i2c", // 无设备树匹配驱动名称
.of_match_table = my_i2c_of_match, // 有设备树匹配设备属性表
},
.probe = my_i2c_probe, // 设备探测函数
.remove = my_i2c_remove, // 设备移除函数
.id_table = my_i2c_idtable, // 设备驱动ID表
};
// 将i2c驱动程序注册到系统内核
// 有注册和有释放
module_i2c_driver(my_i2c_driver);
· .probe成员函数,是需要开发者自己编写的,然后由kernel加载驱动时进行初始化时调用
· .probe成员函数的工作,1.检查设备ID和版本信息,以确定是否支持这个设备;2. 为设备申请和初始化必要的资源,例如寄存器配置、中断分配和I/O端口申请等
· .remove成员函数,也是需要开发者自己编写实现,然后在驱动卸载时由kernek执行该成员函数
· .remove成员函数的工作,1.首先从i2c客户端实例中获取器件数据;2.停止设备的使用;3.释放占用的系统内存资源等
· .id_table成员,指明当前驱动的ID表
3. udev / mdev device
· udev是用户层的一个守护进程,用户创建设备文件(kobject)
3.1 struct device
#include <linux/device.h>
struct device {
struct kobject kobj;
struct device *parent;
struct device_private *p;
struct device_driver *driver;
const struct dev_pm_ops *pm;
int numa_node;
u64 *dma_mask;
u64 coherent_dma_mask;
struct device_dma_parameters *dma_parms;
struct list_head dma_pools;
struct dma_coherent_mem **dma_mem;
void *iommu_group;
const char *bus_id;
const char *init_name;
struct lock_class_key lock_key;
struct class *class;
struct device_type *type;
char name[64];
struct dev_links_info links;
struct dev_links_data *links_data;
struct attribute_group *groups;
void (*release)(struct device *dev);
/* ... (其他成员变量) ... */
};
struct kobject kobj:用于设备对象的嵌套名字空间。
struct device *parent:指向父设备的指针。
struct device_private *p:指向私有数据(一般不需要直接访问)。
struct device_driver *driver:指向设备驱动程序的指针。
const struct dev_pm_ops *pm:指向设备的电源管理操作。
int numa_node:设备所在的 NUMA 节点。
u64 *dma_mask:设备的 DMA (直接内存访问)掩码。
u64 coherent_dma_mask:设备的一致性 DMA 访问掩码。
struct device_dma_parameters *dma_parms:DMA 参数设置。
struct list_head dma_pools:管理 DMA 内存池的链表。
struct dma_coherent_mem **dma_mem:DMA 一致性内存。
void *iommu_group:指向设备所属的 IOMMU 组的指针。
const char *bus_id:设备所在总线的 ID。
const char *init_name:设备初始名称。
struct lock_class_key lock_key:用于设备锁的关联键。
struct class *class:指向设备所属的类的指针。
struct device_type *type:指向设备类型的指针。
char name[64]:设备的名称。
struct dev_links_info links:设备链接信息。
struct dev_links_data *links_data:指向设备链接数据。
struct attribute_group *groups:指向设备的属性组。
void (*release)(struct device dev):设备释放函数指针,用于在设备被释放时调用。
/ … (其他成员变量) … */:其他成员变量,未在注释中给出具体解释。
3.2 device_create 注册设备
#include <linux/device.h>
struct device *device_create(struct class *class,
struct device *parent,
dev_t devt,
void *drvdata,
const char *fmt, ...);
· struct device * :执行成功返回创建的软件层面的设备结构体指针,执行失败返回 NULL
· struct class *class:设备对应的类结构体指针
· struct device *parent :父设备
· dev_t devt :设备号
· void *drvdatat :
· const char *fmt :
3.3 device_destroy 摧毁设备
#include <linux/device.h>
void device_destroy(struct class *class, dev_t devt);
· struct class *class:struct class
· dev_t devt :设备号
· 由于这里用到class,所以要先摧毁device,然后在摧毁class
4. class
· 设备文件的管理是class在操作的,需要把驱动的file_operation实现绑定到对应的设备文件中去
4.1 struct class
4.2 create class 创建类
#include <linux/device.h>
struct class *class_create(struct module *owner, const char *name);
· struct module *owner:所属的module
· const char *name:class name
· 成功返回struct class * ,失败返回NULL
· 一般在 module 的 init 中实现
4.3 destroy class 摧毁类
#include <linux/device.h>
void class_destroy(struct class *cls);
· struct class *cls:要释放的class
· 一般在 module 的 exit 函数中实现
· 要先摧毁device然后再摧毁class
5. dtb -> OF API
· OF API(Open Firmware API)的源文件路径在/include/linux/of.h中
· 是驱动程序用来识别和操作设备树节点的接口
5.1 设备节点信息
· kernel把设备树的节点信息封装在device_node结构体中,但需要使用对应的节点时,kernel会在内存上加载对应的节点
· 设备树节点的计数,kernel是通过设备树节点的调用次数来管理设备树节点的。每次调用获取设备树节点的API时,对应的设备树节点计数就会加一;每次of_node_put函数时,对应的设备树节点计数减一。当设备树节点的计数为0时,kernel会自动释放对应的节点内存空间,使用每次在使用完设备树节点后,都需要调用of_node_put函数使得始末的设备树计数一致,避免内存泄漏的发生。
· 系统启动后,可以在根文件系统里面看到设备树节点的信息。在/proc/device-tree/目录下存放着设备树信息,根下的子节点的信息都存放在这个目录下。属性是通过文件的形式存在的,节点是通过目录的形式存在的
· kernel启动时会解析设备树,然后在/proc/device-tree目录下呈现出来
device_node设备树节点结构体
#include <linux/of.h>
struct device_node {
const char *name; //节点的名称
const char *type; //设备的类型(现在已经很少用到了)
phandle phandle;
const char *full_name; //完整的节点名称
struct fwnode_handle fwnode;
struct property *properties; //属性
struct property *deadprops; /* removed properties */
struct device_node *parent; //父节点
struct device_node *child; //子节点
struct device_node *sibling;
#if defined(CONFIG_OF_KOBJ)
struct kobject kobj;
#endif
unsigned long _flags;
void *data;
#if defined(CONFIG_SPARC)
const char *path_component_name;
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif
};
5.2 查询设备树节点
5.2.1 根据节点对应的树路径查询
#include <linux/of.h>
static inline struct device_node *of_find_node_by_path(const char *path)
{
return of_find_node_opts_by_path(path, NULL);
}
· path :节点对应的树的路径
· 成功返回device_node的指针,失败返回NULL指针
5.2.2 获取子节点
#include <linux/of.h>
struct device_node *of_get_next_child(const struct device_node *node,
struct device_node *prev);
· struct device_node :执行成功返回子节点的 device_node 设备树节点结构体,执行失败返回 NULL
· const struct device_node *parent :父节点的 device_node 设备树节点结构体
· cstruct device_node *prev :指向前一个节点的指针。如果该参数为 NULL,则表示查找第一个子节点。
5.2.2 根据节点的compatible(兼容性)属性查询
#include <linux/of.h>
static device_node *of_find_compation_node(
struct device_node *from,
const chat *type,
const char *compat);
· from: 从哪个节点开始找,NULL表从根节点开始找
· name:
· type:
· compat:
· 成功返回device_node的指针
· 失败返回NULL指针
5.2.3 减少节点计数
#include <linux/of.h>
void of_node_put(struct device_node *node);
· *node: 设备树节点指针
· 在你不需要使用设备树节点时,根据你前面调用了几次查询设备树节点的函数,你就要再调用几次减少设备树节点的函数,以此达到不改变始末设备树节点计数的目的
5.3 节点的属性结构体
struct property {
char *name; //属性的名字
int length; //属性的长度
void *value; //属性值
struct property *next; //下一个属性,有点像链表的结构
unsigned long _flags;
unsigned int unique_id;
struct bin_attribute attr;
}
5.4 获取节点的属性信息
6. pinctrl 子系统
· pinctrl 子系统的本质也是驱动的一种,只不过他向上提供了API接口,他是由官方提供的,但如果你是BSP驱动工程师,这个部分就需要你自行编写
· pinctrl 子系统是用来控制引脚的复用和电气属性的
7. GPIO 子系统
· GPIO 子系统的本质也是驱动的一种,只不过他向上提供了API接口,他是由官方提供的,但如果你是BSP驱动工程师,这个部分就需要你自行编写
· 当引脚为 gpio 工作模式时,才可以使用 gpio 子系统来控制和管理gpio
· 需要获取 gpio 的引脚索引编号,才能通过 gpio 控制器,控制引脚
7.1 gpio 引脚操作
7.1.1 获取 gpio 引脚编号
#include <linux/of.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
static inline int of_get_named_gpio(struct device_node *np, const char *propname, int index);
· int : 执行成功返回 gpio 引脚编号,执行失败返回负数
· struct device_node *np : 根据设备树节点信息获取 gpio 引脚编号
· const char *propname : gpio 的属性名,与设备树中定义的属性名一致
· int index : 引脚的索引值,在设备树中一条引脚属性可以包含多个引脚,该参数指定获取那个引脚
7.1.2 申请引脚别名
#include <linux/of.h>
#include <linux/gpio.h>
static inline int gpio_request(unsigned gpio, const char *label);
· int :执行成功返回 0,执行失败返回负数
· unsigned gpio :要申请别名的 gpio 引脚的编号
· const char *label :引脚的别名
7.1.3 释放 gpio 引脚编号
#include <linux/of.h>
#include <linux/gpio.h>
static inline void gpio_free(unsigned gpio);
· unsigned gpio :要释放的 gpio 引脚编号
7.2 设置 gpio 引脚
7.2.1 初始化 gpio 引脚
7.2.1.1 输入设置
· 将引脚设置为输入模式
#include <linux/of.h>
#include <linux/gpio.h>
static inline int gpio_direction_input(unsigned gpio);
· int :执行成功返回 0,执行失败返回负值
· unsigned gpio :要设置的 gpio 引脚编号
7.2.1.2 输出设置
· 将引脚设置为输出模式
#include <linux/of.h>
#include <linux/gpio.h>
static inline int gpio_direction_output(unsigned gpio, int value);
· int :执行成功返回 0,执行失败返回负值
· unsigned gpio :要设置的 gpio 引脚编号
· int value :输出值, 1 -> high;0 -> low
7.3 获取 / 修改引脚的状态
7.3.1 获取引脚的状态
#include <linux/of.h>
#include <linux/gpio.h>
static inline int gpio_get_value(unsigned gpio);
· int : 执行成功返回引脚的状态,执行成功返回负数
· unsigned gpio :要获取的 gpio 引脚编号
7.3.2 修改引脚的状态
#include <linux/of.h>
#include <linux/gpio.h>
static inline int gpio_set_value(unsigned gpio, int value);
· int :执行成功返回 0,执行失败返回负数
· unsigned gpio :要修改的 gpio 引脚编号
· int value :设置输出值,1 -> high;0 -> low
8. IIC
9. SPI
9.1 SPI 相关结构体
9.1.1 struct spi_device spi 设备结构体
#include <linux/spi.h>
struct spi_device {
struct device dev; /* 继承的 device 结构体 */
/* 当前 spi 设备挂载的 spi 控制器 */
struct spi_controller *controller;
/* 指定 spi 设备挂载的 spi 总线 */
struct spi_controller *master; /* compatibility layer */
u32 max_speed_hz; /* spi 的最大传输速率 */
u8 chip_select; /* spi 总线用于区分不同 spi 设备的标号 */
u8 bits_per_word; /* spi 通讯时一个字节多少个位 */
u16 mode; /* spi 工作模式 */
/*
下列的宏定义是用来设置 mode 成员的
mode 的每个位都代表不同的模式功能
如 mode 的第 0 位为 1 时,SPI_CPHA 使能,为 0 时 SPI_CPHA 失能
*/
/* 工作模式如以上代码中的宏定义。包括时钟极性、位宽等等,这些宏定义可以使用或运算 | 进行组合 */
#define SPI_CPHA 0x01 /* clock phase */
#define SPI_CPOL 0x02 /* clock polarity */
#define SPI_MODE_0 (0|0) /* (original MicroWire)␣
*/
/* SPI_CPOL 和 SPI_CPHA的值同时也指定了全双工模式/半双工等工作模式 */
#define SPI_MODE_1 (0|SPI_CPHA)
#define SPI_MODE_2 (SPI_CPOL|0)
#define SPI_MODE_3 (SPI_CPOL|SPI_CPHA)
#define SPI_CS_HIGH 0x04 /* chipselect active, high? */
#define SPI_LSB_FIRST 0x08 /* per-word bits-on-, wire */
#define SPI_3WIRE 0x10 /* SI/SO signals shared */
#define SPI_LOOP 0x20 /* loopback mode */
#define SPI_NO_CS 0x40 /* 1 dev/bus, no,chipselect */
#define SPI_READY 0x80 /* slave pulls low to,pause */
#define SPI_TX_DUAL 0x100 /* transmit with 2, wires */
#define SPI_TX_QUAD 0x200 /* transmit with 4, wires */
#define SPI_RX_DUAL 0x400 /* receive with 2 wires, */
#define SPI_RX_QUAD 0x800 /* receive with 4 wires,*/
int irq; /* 如果使用了中断,用于指定中断号 */
void *controller_state;
void *controller_data;
char modalias[SPI_NAME_SIZE];
int cs_gpio; /* chip select gpio */
/* 片选引脚 */
/* 设备树中设置了片选引脚,驱动和设别树节点匹配成功后自动获取片选引脚,我们也可以在驱动总通过设置该参数自定义片选引脚 */
/* the statistics */
struct spi_statistics statistics; /* 记录 spi 的名字,用于和 spi_driver 进行匹配 */
};
9.1.2 spi_controller spi 总线控制器结构体
#include <linux/spi/spi.h>
struct spi_controller {
struct device dev;
...
struct list_head list; /* 链表节点,IC 可能有多个 spi 控制器 */
s16 bus_num; /* spi 控制器的编号 */
u16 num_chipselect; /* spi 片选信号的个数 */
...
struct spi_message *cur_msg; /* spi_message 结构体,发送消息时都会把信息封装在这个结构体中 */
...
int (*setup)(struct spi_device *spi);
/* 用于将数据加入 spi 控制器的消息队列中 */
int (*transfer)(struct spi_device *spi,
struct spi_message *mesg);
/* 当 spi_message 被释放时,执行清理工作 */
void (*cleanup)(struct spi_device *spi);
/* 内核线程工人,spi 可以使用异步传输的方式发送数据 */
struct kthread_worker kworker;
struct task_struct *kworker_task;
struct kthread_work pump_messages; /* 具体传输工作 */
struct list_head queue; /* 所有等待传输消息队列挂载的头部链表节点 */
struct spi_message *cur_msg;
...
int (*transfer_one)(struct spi_controller *ctlr, struct spi_device *spi,struct spi_transfer *transfer);
int (*prepare_transfer_hardware)(struct spi_controller *ctlr);
/* 产生 spi 时序的底层接口 */
int (*transfer_one_message)(struct spi_controller *ctlr,struct spi_message *mesg);
void (*set_cs)(struct spi_device *spi, bool enable);
...
int *cs_gpios; /* 记录 spi 上具体的片选信号 */
}
9.1.3 struct spi_transfer 数据传输结构体
#include <linux/spi/spi.h>
struct spi_transfer {
/* it's ok if tx_buf == rx_buf (right?)
* for MicroWire, one buffer must be null
* buffers must work with dma_*map_single() calls, unless
* spi_message.is_dma_mapped reports a pre-existing mapping
*/
const void *tx_buf; /* 发送缓冲区 */
void *rx_buf; /* 接收缓冲区 */
unsigned len; /* 传输长度,根据 spi 的特性发送和接收的长度相等 */
dma_addr_t tx_dma; /* 如果使用了 DMA,用于指定 DMA 传输的发送地址 */
dma_addr_t rx_dma; /* 如果使用了 DMA,用于指定 DMA 传输的接收地址 */
struct sg_table tx_sg;
struct sg_table rx_sg;
unsigned cs_change:1;
unsigned tx_nbits:3;
unsigned rx_nbits:3;
#define SPI_NBITS_SINGLE 0x01 /* 1bit transfer */
#define SPI_NBITS_DUAL 0x02 /* 2bits transfer */
#define SPI_NBITS_QUAD 0x04 /* 4bits transfer */
u8 bits_per_word; /* 传输单个字节的位数 */
u16 delay_usecs;
u32 speed_hz; /* spi 的发送频率 */
struct list_head transfer_list;
};
9.1.4 struct spi_message spi 消息结构体
· 总体来说 spi 设备驱动中的数据是以消息队列的形式发送的
· 发送和接收数据都是封装在 spi_message 消息结构体中的
#inlude <linux/spi/spi.h>
struct spi_message {
struct list_head transfers; /* spi 数据传输结构体链表头部节点 */
struct spi_device *spi; /* 对应的 spi 设备结构体对象 */
unsigned is_dma_mapped:1;
/* REVISIT: we might want a flag affecting the behavior of the
* last transfer ... allowing things like "read 16 bit length L"
* immediately followed by "read L bytes". Basically imposing
* a specific message scheduling algorithm.
*
* Some controller drivers (message-at-a-time queue processing)
* could provide that as their default scheduling algorithm. But
* others (with multi-message pipelines) could need a flag to
* tell them about such special cases.
*/
/* 下面和是异步传输回调函数相关的成员 */
/* completion is reported through a callback */
void (*complete)(void *context);
void *context;
unsigned frame_length;
unsigned actual_length;
int status;
/* for optional use by whatever driver currently owns the
* spi_message ... between calls to spi_async and then later
* complete(), that's the spi_master controller driver.
*/
struct list_head queue;
void *state;
};
9.1.5 struct spi_driver spi 驱动结构体
#include <linux/spi/spi.h>
struct spi_driver {
const char *name; // 驱动程序名称
const char *modalias; // 驱动程序的别名,可选
unsigned int bus_type; // 总线类型,如 SPI_BUS_TYPE_SPI
int max_speed_hz; // 最大传输速率,单位为 Hz
int max_queue_len; // 最大队列长度
struct device_driver *drv; // 继承的驱动程序实例
struct bus_type *bus; // SPI 总线实例
struct device_node *of_node; // 设备树节点
const struct fwnode_handle *fwnode; // Firmware 节点
const char *modalias_of_node; // 设备树节点的驱动程序别名
void (*probe)(struct spi_device *); // 设备探测回调函数
void (*remove)(struct spi_device *); // 设备删除回调函数
void (*shutdown)(struct spi_device *); // 设备关闭回调函数
void (*suspend)(struct spi_device *); // 设备挂起回调函数
void (*resume)(struct spi_device *); // 设备恢复回调函数
void (*init)(struct spi_device *); // 设备初始化回调函数
struct list_head entry; // 设备列表
unsigned int num_devices; // 设备数量
};
9.1.n struct spi_ioc_transfer 应用层 spi 通讯结构体
#include <linux/spi/spidev.h>
struct spi_ioc_transfer {
__u64 tx_buf; //发送数据缓存
__u64 rx_buf; //接收数据缓存
__u32 len; //数据长度(以字节为单位的)
__u32 speed_hz; //通讯速率
__u16 delay_usecs; //两个spi_ioc_transfer之间的延时,微秒
__u8 bits_per_word; //数据长度
__u8 cs_change; //取消选中片选
__u8 tx_nbits; //单次数据宽度(多数据线模式)
__u8 rx_nbits; //单次数据宽度(多数据线模式)
__u16 pad;
};
9.2 spi_register_driver 注册 SPI 驱动
#include <linux/spi.h>
int spi_register_driver(sturct spi_driver *sdrv);
· int :执行成功返回 0,执行失败返回负数
· sturct spi_driver *sdrv :申请注册的 SPI 驱动结构体对象
9.3 spi_unregister_driver 注销 SPI 驱动
#include <linux/spi.h>
static inline void spi_unregister_driver(sturct spi_driver *sdrv);
· sturct spi_driver *sdrv :申请注册的 SPI 驱动结构体对象
9.4 spi_setup 设置 spi
· spi_setup 函数用于设置 spi 的传输速率,带宽和片选信号等
#include <linux/spi.h>
int spi_setup(struct spi_device *spi)
· int : 执行成功返回 0,执行失败返回负值
· struct spi_device *spi : spi spi_device spi设备结构体
9.5 spi_message_init 消息结构体初始化
#include <linux/spi.h>
static inline void spi_message_init(struct spi_message *m)
{
memset(m, 0, sizeof *m); /* 清空内存操作 */
spi_message_init_no_memset(m); /* 初始化结构体的接口 */
}
· struct spi_message *m : 要初始化的消息结构体指针对象
9.6 spi_message_add_tail 添加传输结构体到消息结构体对象中
· 将 spi_transfer 结构体添加到 spi_message 队列的末尾。
#include <linux/spi.h>
static inline void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
{
list_add_tail(&t->transfer_list, &m->transfers);
}
· struct spi_transfer *t : 要添加的数据传输结构体的指针对象
· struct spi_message *m : 操作的消息结构体指针指针对象
9.7 spi_sync spi 同步传输
· 阻塞当前进程,直至将 message 中的数据传输完成才结束阻塞
#include <linux/spi.h>
int spi_sync(struct spi_device *spi, struct spi_message *message)
{
int ret;
mutex_lock(&spi->controller->bus_lock_mutex);
ret = __spi_sync(spi, message);
mutex_unlock(&spi->controller->bus_lock_mutex);
return ret;
}
static int __spi_sync(struct spi_device *spi, struct spi_message *message)
{
int status;
struct spi_controller *ctlr = spi->controller;
unsigned long flags;
status = __spi_validate(spi, message);
if (status != 0)
return status;
message->complete = spi_complete;
message->context = &done;
message->spi = spi;
...
if (ctlr->transfer == spi_queued_transfer) {
spin_lock_irqsave(&ctlr->bus_lock_spinlock, flags);
trace_spi_message_submit(message);
status = __spi_queued_transfer(spi, message, false);
spin_unlock_irqrestore(&ctlr->bus_lock_spinlock, flags);
} else {
status = spi_async_locked(spi, message);
}
if (status == 0) {
...
wait_for_completion(&done);
status = message->status;
}
message->context = NULL;
return status;
}
· struct spi_message *m : 操作的消息结构体指针指针对象
· struct spi_device *spi : spi spi_device spi设备结构体
9.8 spi_async spi 异步传输
· 异步传输不会阻塞当前进程,但内核态又要怎么在 spi 异步通讯完成后去通知用户空间呢?
· struct spi_message 在 spi 的消息结构体中你可以看到下面这俩个成员
· void (*complete)(void *context);
· void *context;
· 当 spi 异步传输完成后,内核会执行 complete 这个回调函数,而 context 是 complete 回调函数的参数
· 所以只需要自定义 complete 类型的回调函数供内核调用,在回调函数中编写通知用户态的内容即可
#include <linux/spi.h>
int spi_async(struct spi_device *spi, struct spi_message *message)
{
...
ret = __spi_async(spi, message);
...
}
static int __spi_async(struct spi_device *spi, struct spi_message *message)
{
struct spi_controller *ctlr = spi->controller;
...
return ctlr->transfer(spi, message);
}
· struct spi_message *m : 操作的消息结构体指针指针对象
· struct spi_device *spi : spi spi_device spi设备结构体
10. top half 顶半部/上半部 中断
· 如果要处理的内容不希望被其他中断打断,那么可以放到上半部
· 如果要处理的任务对时间敏感,可以放到上半部
· 如果要处理的任务与硬件有关,可以放到上半部
· 上半部就是中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可以放在上半部完成
10.1 request_irq 请求中断
· request_irq 可能会引起睡眠
#include <linux/interrupt.h>
int request_irq( unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev);
· unsigned int irq : 申请的中断号
· irq_handler_t handler : 中断处理函数
· unsigned long flags : 中断标志
· 在 includ/linux/interrupt.h 中,可选多个标志
· IRQF_SHARED 多设备共享一个中断线,共享的所有中断都必须指定此标志。如果使用共享中断的话,request_irq 函数的 dev 参数是唯一区分的标志
· IRQF_ONESHOT 单次中断,中断执行一次就结束
· IRQF_TRIGGER_NONE 无触发
· IRQF_TRIGGER_RISING 上升沿触发
· IRQF_TRIGGER_FALLING 下降沿触发
· IRQF_TRIGGER_HIGH 高电平触发
· IRQF_TRIGGER_LOW 低电平触发
·
·
· const char *name : 中断名字,在 /proc/interrupts 中即可看见相关描述
· void *dev : 如果将 flags 设置为 IRQF_SHARED 的话,dev 用来区分不同的中断,一般情况下将
dev 设置为设备结构体,dev 会传递给中断处理函数 irq_handler_t 的第二个参数。
10.1.1 interrupt handler 中断服务函数
#include <linux/interrupt.h>
irq_handler_t (*irq_handler_t)(int, void *);
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
return IRQ_RETVAL(IRQ_HANDLED);
· irq_handler_t : 返回值是枚举类型的
· IRQ_NONE:中断处理程序没有处理该中断,并且该中断不是由该处理程序注册的源 产生的。
·IRQ_HANDLED:中断处理程序已经处理了该中断,并且该中断确实是由该处理程序注册的源产生的。
·IRQ_WAKE_THREAD:中断处理程序处理了该中断,并且希望唤醒一个等待该中断的线程
· int : 中断号
· void * : 接收 void *dev 参数,当有中断共享时,用来区分中断的设备
· IRQ_RETVAL 是一个宏定义,它的作用是将一个整数值转换为一个 enum irqreturn 枚举类型的值。这个宏定义通常在内核中使用,例如在处理中断时,中断处理程序通常需要返回一个 enum irqreturn 枚举类型的值来表示该中断是否已被成功处理。
10.2 free_irq 释放中断
#include <linux/interrupt.h>
void free_irq(unsigned int irq, void *dev);
· unsigned int irq : 要释放的中断号
· void *dev : 如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断
只有在释放最后中断处理函数的时候才会被禁止掉。
10.3 中断控制
void enable_irq(unsigned int irq);
void disable_irq(unsigned int irq);
void disable_irq_nosync(unsigned int irq);
· 指定要禁用的中断号
· disable_irq 要等到当前正在执行的中断处理函数执行完成后才返回
· disable_irq_nosync 不会等待当前中断处理程序执行完毕,而是立即返回
local_irq_enable();
local_irq_disable();
· 关闭当前处理器的整个中断系统
local_irq_save(flags);
local_irq_restore(flags);
· local_irq_save 用于禁用中断,并把中断状态保存在 flags 中
· local_irq_restore 用于恢复中断,将中断恢复到 flags 状态
11. bottom half 底半部/下半部 中断
· 并不是中断都有底半部的,有些中断仅顶半部就能实现功能
· 如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部
11.1 软中断
· 软中断不允许睡眠或重新调度
11.2 tasklet 任务片
· tasklet 不允许睡眠或重新调度
11.2.1 struct tasklet_struct
#include <linux/sched/types.h>
struct tasklet_struct
{
struct tasklet_struct *next; /* 下一个 tasklet */
unsigned long state; /* tasklet 状态 */
atomic_t count; /* 计数器,记录对 tasklet 的引用数 */
void (*func)(unsigned long); /* tasklet 中断下半部处理函数 */
unsigned long data; /* 函数 func 的参数 */
};
11.2.2 tasklet 初始化
11.2.2.1 tasklet_init 函数初始化
#include <linux/sched/types.h>
void tasklet_init( struct tasklet_struct *t,
void (*func)(unsigned long),
unsigned long data);
· struct tasklet_struct *t : tasklet_struct 结构体
· void (*func)(unsigned long) : 下半部处理函数
· unsigned long data : 传递给下半部处理函数的参数
11.2.2.2 DECLARE_TASKLET 宏定义初始化
#include <linux/interrupt.h>
DECLARE_TASKLET(tasklet_name, tasklet_function, data)
· tasklet_name : tasklet 的名字
· tasklet_function : 下半部处理函数
· data : 传递给下半部处理函数的参数
11.2.3 tasklet_schedule 上半部调用函数
· 上半部中断处理函数需要调用 tasklet_schedule 函数,才能使 tasklet 在合适的时间运行
#include <linux/sched/types.h>
void tasklet_schedule(struct tasklet_struct *t);
· struct tasklet_struct *t : tasklet_struct 结构体
11.3 工作队列
· 工作队列是另外一种下半部执行方式,工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度。因此如果你要推后的工作可以睡眠那么就可以选择工作队列,否则的话就只能选择软中断或 tasklet
11.4 内核线程化
12. 时钟
12.1 jiffies 系统运行节拍率
· jiffies 和 errno 变量一样是全局的变量,只不过是 errno 被用来记录系统上一次发送错误的错误码,而 jiffies 是用来记录系统从开机起的运行节拍率,节拍率是基于内部的时钟的,用来扫描中断,如:系统的节拍率为 100 HZ,即 jiffies 的单位是 1 / 100s,也就是 10 毫秒
12.1.1 jiffies 的数据类型
· jiffies 是存储在 64 位的数据类型的,jiffies 也存在溢出问题
· 对于64 位的系统,jiffies 溢出的问题几乎不用考虑,大概率是机器都不能比 jiffies 溢出的寿命长
· 对于 32 位的系统,他只访问 jiffies 的低 32 位的数据,溢出问题尤为重要
12.1.2 jiffies 比较 API
· unkown 通常为 jiffies,known 通常是需要对比的值
#include <linux/sched.h> /* jiffies include */
#include <linux/time.h>
time_after(unkown, known);
time_before(unkown, known);
time_after_eq(unkown, known);
time_before_eq(unkown, known);
time_after
· 如果 unkown 超过 known 的,返回 true,否则返回 false
time_before
· 如果 unkown 没有超过 known 的,返回 true,否则返回 false。
·time_after_eq 函数和 time_after 函数类似,只是多了判断等于这个条件。同理,time_before_eq 函数和 time_before 函数也类似
12.1.3 jiffies 转换
#include <linux/sched.h> /* jiffies include */
#include <linux/types.h>
int jiffies_to_msecs(const unsigned long j); /* jiffies -> ms */
int jiffies_to_usecs(const unsigned long j); /* jiffies -> us */
u64 jiffies_to_nsecs(const unsigned long j); /* jiffies -> ns */
long msecs_to_jiffies(const unsigned int m); /* ms -> jiffies */
long usecs_to_jiffies(const unsigned int u); /* us -> jiffies */
unsigned long nsecs_to_jiffies(u64 n); /* ns -> jiffies */
· jiffies 的精度是在 kernel 编译时设置的,如:HZ = 100 (即 jiffies 的精度为 1 / HZ 也就是 10ms)
· 虽然有对应的 API 可以将 jiffies 与 us ns 单位更小的时间进行相互转换,但这并不会提高 jiffies 的精度
12.2 内核定时器
· 软件意义上的定时器最终是依赖硬件定时器来实现的
· 实际上, 时钟中断处理的程序会唤醒 TIMER_SOFTIRQ 软中断,运行当前处理器上到期的所有定时器
12.2.1 timer_list
· timer_list 是将 expires 与 jiffies 进行比较实现的,但 jiffies 大于或等于 expires 时,即定时器到期
#include <linux/timer.h>
/* 老版 Linux 中的定义 */
struct timer_list {
/*
* All fields that change during normal runtime grouped to the
* same cacheline
*/
struct hlist_node entry; /* 哈希表,kernel 用于管理 timer_list */
unsigned long expires; /* 定时器到时时间,单位是节拍数 */
/* 当定时器满期后,function 函数将被执行 */
void (*function)(struct timer_list *);
u32 flags; /* 时钟标志位 */
/* 用于在内核锁的情况下跟踪计时器的使用情况 */
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
/* 当今主流的 Linux 中的定义 */
struct timer_list {
struct hlist_node entry; /* 哈希表,kernel 用于管理 timer_list */
unsigned long expires; /* 定时器到时时间,单位是节拍数 */
/* 当定时器满期后,function 函数将被执行 */
void (*function)(unsigned long);
unsigned long data; /* 回调函数的参数 */
u32 flags; /* 时钟标志位 */
int slack;
};
· u32 flags (也定义在 linux/timer.h 中)
· TIMER_CPUMASK:表示定时器可以在哪些 CPU 上运行
· TIMER_MIGRATING:表示定时器正在迁移中
· TIMER_BASEMASK:是 TIMER_CPUMASK 和 TIMER_MIGRATING 的位或运算结果
· TIMER_DEFERRABLE:表示该定时器是 deferrable 的,即不会阻塞 CPU,而是在 CPU 空闲时执行
· TIMER_PINNED:表示该定时器是固定在 CPU 上的,不能移动到其他 CPU 上。
· TIMER_IRQSAFE:表示该定时器是 irqsafe 的,即在执行回调函数时不会影响其他中断的处理
· TIMER_ARRAYSHIFT:表示 TIMER_ONESHOT、TIMER_IRQSAFE 和 TIMER_PINNED 这三个位的位置
· TIMER_ARRAYMASK:是 TIMER_CPUMASK、TIMER_ARRAYSHIFT 和 TIMER_BASEMASK 的按位与运算结果
· TIMER_TRACE_FLAGMASK:是 TIMER_MIGRATING、TIMER_DEFERRABLE、TIMER_PINNED 和 TIMER_IRQSAFE 的按位或运算结果,用于标记定时器的一些属性
12.2.2 timer_list init
12.3 hrtimer 高精度定时器
12.3.1 struct hrtimer
#include <linux/hrtimer.h>
#include <linux/timerqueue.h>
/**
* struct hrtimer - the basic hrtimer structure
* @node: timerqueue node, which also manages node.expires,
* the absolute expiry time in the hrtimers internal
* representation. The time is related to the clock on
* which the timer is based. Is setup by adding
* slack to the _softexpires value. For non range timers
* identical to _softexpires.
* @_softexpires: the absolute earliest expiry time of the hrtimer.
* The time which was given as expiry time when the timer
* was armed.
* @function: timer expiry callback function
* @base: pointer to the timer base (per cpu and per clock)
* @state: state information (See bit values above)
* @is_rel: Set if the timer was armed relative
* @is_soft: Set if hrtimer will be expired in soft interrupt context.
*
* The hrtimer structure must be initialized by hrtimer_init()
*/
struct timerqueue_node {
struct rb_node node;
/* Nanosecond scalar representation for kernel time values */
/* typedef s64 ktime_t; */
ktime_t expires;
};
struct hrtimer {
struct timerqueue_node node;
ktime_t _softexpires;
enum hrtimer_restart (*function)(struct hrtimer *);
struct hrtimer_clock_base *base;
u8 state;
u8 is_rel;
u8 is_soft;
};
12.3 delayed_work 内核延迟工作
12.4 内核延时
12.4.1 短延迟
12.4.2 长延迟
12.4.3 睡着延迟
12.5 获取时间
12.3.1 jiffies 获取时间
12.3.2 do_gettimeofday 获取时间戳
#include <sys/time.h>
struct timeval {
/*
微秒的取值范围在 0 ~ 1 秒的区间内
当微秒到 1s 的时间时,微秒清零,tv_sec++
*/
long tv_sec; /* 秒 */
long tv_usec; /* 微秒 */
};
* do_gettimeofday - Returns the time of day in a timeval
* @tv: pointer to the timeval to be set
*
* NOTE: Users should be converted to using getnstimeofday()
*/
void do_gettimeofday(struct timeval *tv)
{
struct timespec64 now;
getnstimeofday64(&now);
tv->tv_sec = now.tv_sec;
tv->tv_usec = now.tv_nsec/1000;
}
13.DMA
14. PWM
14.1 struct pwm_struct
#include <linux/pwm.h>
struct pwm_device {
const char *label; /* PWM 设备的名称 */
unsigned int flags; /* PWM 设备的标志位 */
unsigned int hwpwm; /* PWM 设备是否支持硬件 PWM */
unsigned int pwm; /* PWM 设备的 PWM 通道号 */
struct pwm_chip *chip; /* 用于管理该设备的硬件资源 */
void *chip_data; /* PWM 设备的私有数据 */
struct pwm_args args; /* PWM 设备的参数 */
struct pwm_state state; /* PWM 设备的状态 */
};
struct pwm_state {
unsigned int period; /* PWM 的周期 */
unsigned int duty_cycle; /* PWM 占空比 */
enum pwm_polarity polarity; /* 是否反相输出 */
bool enabled; /* 使能标志位 */
};
enum pwm_polarity {
PWM_POLARITY_NUORMAL, /* 正相输出 */
PWM_POLARITY_INVERSED, /* 反相输出 */
};
14.2 devm_of_pwm_get 生成 pwm_device 对象
#include <linux/of.h>
#include <linux/pwm.h>
struct pwm_device *devm_of_pwm_get( struct device *dev,
struct device_node *np,
const char *con_id);
· struct pwm_device : 执行成功返回 pwm_device 结构体,执行失败返回 NULL
· struct device *dev : 使用 PWM 的设备结构体
· struct device_node *np : PWM 设备树节点结构体
· const char *con_id : 设备树节点匹配名, 指向 PWM 控制器的 ID 的指针。该参数通常是由设备树中的属性提供的
14.3 pwm_free 释放 pwm
#include <linux/pwm.h>
void pwm_free(struct pwm_device *pwm);
· struct pwm_device : pwm 设备的 pwm_device 结构体
14.4 pwm_config 设置 PWM
#include <linux/pwm.h>
static inline int pwm_config( struct pwm_device *pwm,
int duty_ns,
int period_ns);
· int : 执行成功返回 0,执行失败返回负数
· struct pwm_device *pwm : 设置的 pwm_device 结构体对象
· int duty_ns : 高电平的持续时间(单位是 ns),设置 pwm 的占空比
· int period_ns : PWM 的周期时间(单位是 ns),设置 pwm 的频率
14.5 pwm_set_polarity 设置 pwm 极性
#include <linux/pwm.h>
static inline int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity);
enum pwm_polarity {
PWM_POLARITY_NUORMAL, /* 正相输出 */
PWM_POLARITY_INVERSED, /* 反相输出 */
};
· int : 执行成功返回 0,执行失败返回负数
· struct pwm_device *pwm : 设置的 pwm_device 结构体对象
· enum pwm_polarity polarity : pwm 的极性枚举常量
14.6 pwm_enable/disable 使能/失能 pwm
#include <linux/pwm.h>
static inline int pwm_enable(struct pwm_device *pwm);
static inline void pwm_disable(struct pwm_device *pwm);
· int : 执行成功返回 0,执行失败返回负数
· struct pwm_device *pwm : 操作的 pwm_device 结构体对象
15. ADC
16. 并发和竞态
· 操作系统是一个庞大的架构,不仅进程之间要抢占系统资源,线程间,中断间,线程与中断间都存在系统资源的抢占问题
· 我们希望每次执行一段程序都能正确的执行,不会出现数据被其他程序修改而发生的错误,所以这需要设置对数据的保护机制,注意是保护数据而不是保护代码
· 以下针对的是内核态的进程的同步,线程的同步是用户态的
· 保护数据的方式一般有,原子操作,互斥锁,自旋锁等等
16.1 原子操作
15.2 自旋锁
· 使用 spin_lock 获取自旋锁时,当锁已被占用时,当前进程将自旋,直到获取到自旋锁(轮询获取锁)
· 一个进程只允许同一时间内持有一个锁,需要其他资源时,需要先释放当前的锁,再去申请获取其他资源的锁,也不允许在获得锁后申请再获得一次当前的锁,这些情况都要避免,以免出现死锁的现象
16.2.1 自旋锁
#include <linux/spinlock.h>
spinlock_t lock;
void spin_lock_init(spinlock_t *lock);
void spin_lock(spinlock_t *lock);
int spin_trylock(spinlock_t *lock);
void spin_unlock(spinlock_t *lock);
16.2.2 读写锁
16.2.3 顺序锁
16.3 互斥锁
16.4 mutex 互斥体
16.5 Semaphore 信号量
· 信号量是典型的用于同步和互斥的手段,不过新的 Linux kernel 更倾向于用 mutex 作为互斥的手段,信号量互斥不再被推荐使用
· 对于同步问题,典型的关心具体数值的生产者/消费者问题,使用信号量则更合适
16.5 完成量 Completion
17 阻塞和非阻塞 IO
17.1 等待阻塞
· 用户进程休眠等待内核资源
17.1.1 wait_queue 等待队列
17.1.1.1 结构体
17.1.1.1.1 struct wait_queue_head 等待队列头部结构体
#include <linux/wait.h>
struct wait_queue_head {
spinlock_t lock; //自旋锁
struct list_head head; //保存队列元素的链表节点
};
typedef struct wait_queue_head wait_queue_head_t;
17.1.1.1.1 struct wait_queue_entry 等待队列元素结构体
#include <linux/wait.h>
struct wait_queue_entry {
unsigned int flags;
void *private;
wait_queue_fun_t func;
struct list_head entry; //连接到等待队列头部的链表节点
};
typedef struct wait_queue_entry wait_queue_entry_t;
17.1.1.2 初始化等待队列头部
17.1.1.2.1 init_waitqueue_head 初始化
#include <linux/wait.h>
#define init_waitqueue_head(wa_head)
\
do { \
static struct lock_class_key __key; \
\
__init_waitqueue_head(wq_head), #wa_head, &__key); \
} while(0);
· wa_head : 要初始化的 wait_queue_head 的指针对象
17.1.1.2.2 定义并初始化
#include <linux/wait.h>
#define DECLARE_WAIT_QUEUE_HEAD(name) \
struct wait_queue_head name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
· 返回初始化后的 wait_queue_head 的指针对象
· name : 定义的 wait_queue_head 的名称
17.1.1.3 定义并初始化等待队列元素
#include <linux/wait.h>
#define DECLARE_WAITQUEUE(name, tsk) \
struct wait_queue_entry name = __WAITQUEUE_INITALIZER(name, tsk)
17.2 轮询非阻塞
· open 设备文件时,使用 O_NONBLOCK 宏,以非阻塞的模式打开文件
17.2.1 select
17.2.1 poll
18 异步通知和异步 I/O
· 驱动端需要实现 file_operations 中的 .fasync 成员,供用户层修改文件描述符的执行模式(使得设备文件支持 FASYNC 模式,即异步通知模式)
· 在关闭文件时还需要把 mode 设置为 0,将文件从异步通知列表中删除
· 驱动端还需要在设备资源可用时调用 kill_fasync 函数释放信号供上层用户态进程捕获(用户态进程需要调用 fcntl 函数去设置设备文件拥有者为指定的进程,才能成功捕获到驱动端释放的信号)
· 释放的信号还可携带数据,这样减少了用户层再去获取数据的流程,只需要捕获信号,然后解析信号即可,这样做更加高效
18.1 异步通知
· 在设备驱动和应用程序的异步通知交互中,仅仅是在应用程序中捕获信号是不够的,信号的源头在设备驱动端。因此在合适的时机让设备驱动释放信号是必要的
· 驱动端在获取到设备资源时,调用 kill_fasync 函数激发相应的信号
18.1.1 fasync_struct
struct fasync_struct {
int magic;
int fa_fd;
struct file *fa_filp;
struct task_struct *fa_task;
struct list_head fa_list;
int fa_opcode;
struct fasync_struct *fa_next;
};
· magic:一个用于验证该结构体是否有效的整数
· fa_fd:一个指向 file 结构体的指针,表示正在进行异步 I/O 操作的文件描述符
· fa_filp:一个指向 struct file 结构体的指针,表示正在进行异步 I/O 操作的文件对象
· fa_task:一个指向 struct task_struct 结构体的指针,表示正在进行异步 I/O 操作的进程
· fa_list:一个链表,用于存储相同文件描述符的所有异步 I/O 操作
· fa_opcode:一个用于标识异步 I/O 操作类型的整数
· fa_next:一个指向下一个 fasync_struct 结构体的指针
18.1.2 fasync_helper 处理 FASYNC 标志变更函数
· 驱动程序在实现 fcntl 函数中的 fasync 函数的功能时,需要调用
#include <linux/fs.h>
int fasync_helper(int fd, struct file *filp, int mode, struct fasync_struct **fa);
· int fd :操作的文件描述符
· struct file *filp:file 结构体
· int mode:要修改的权限
· struct fasync_struct **fa:fasync_struct 结构体
18.1.3 kill_fasync 释放信号
#include <linux/fs.h>
#include <linux/sched/signal.h>
struct fasync_struct {
rwlock_t fa_lock;
int magic;
int fa_fd;
struct fasync_struct *fa_next; /* singly linked list */
struct file *fa_file;
struct rcu_head fa_rcu;
};
int kill_fasync(struct fasync_struct **fa, int sig, int band);
· struct fasync_struct **fa :fasync_struct 结构体
· int sig :指定要释放的信号函数
· int band :在资源可读时,设置为 POLL_IN
在资源可写时,设置为 POLL_OUT
18.2 非实时信号释放
18.2.1 kill_pid 释放非实时信号
#include <linux/signal.h>
int kill_pid(struct pid *pid, int sig, int priv);
· int :执行成功返回 0, 执行失败返回错误码
· pid:指定要接收信号的进程
· sig:将要释放的非实时信号
· priv:指定发送信号的劝降
· SIG_KILL :强制终止进程
· SIG_DFL :使用默认处理程序
· SIG_IGN :忽略信号
· 0 :使用处理程序
18.3 实时信号释放
kill_fasync和send_sig_info都是用于进程间通信或信号处理的函数,但它们在功能和使用方式上存在明显的差别。
kill_fasync函数用于向特定进程发送异步信号。它接受三个参数:一个进程ID(pid),一个用户ID(uid),以及一个数据指针(data)。然而,它并不支持发送带参数的信号,这种情况下,可能需要用户端使用read来读取数据。
send_sig_info函数是Linux内核的一部分,具体定义在<linux/signal.h>头文件中。它被用于向特定进程发送信号,并允许携带额外的信息。具体来说,siginfo结构可以包含有关信号的更多详细信息,例如信号的来源、发送的时间等。这种信号通常用于更复杂的进程间通信或异常处理。
总的来说,kill_fasync和send_sig_info的主要区别在于,kill_fasync仅能发送简单的信号给进程,而send_sig_info则可以发送更复杂的信号,并且允许携带额外的信息。
18.3.1 struct siginfo
#include <linux/signal.h>
/* 伴随数据为一个数据类型或用户层的指针 */
typedef union sigval {
int sival_int;
void __user *sival_ptr; /* 需要先从用户层获取指针或是在内核态中申请用户态可直接访问地址 */
} sigval_t;
typedef struct siginfo {
int si_signo; /* Signal number */
#ifndef __ARCH_HAS_SWAPPED_SIGINFO
int si_errno; /* An errno value */
int si_code; /* Signal code 触发信号的原因,有具体的宏定义触发信号的原因*/
#else
int si_code;
int si_errno;
#endif
/*
伴随数据联合体
对于不同的信号有不同的伴随数据结构
*/
union {
int _pad[SI_PAD_SIZE];
/* kill() */
struct {
__kernel_pid_t _pid; /* sender's pid */
__kernel_uid32_t _uid; /* sender's uid */
} _kill;
/* POSIX.1b timers */
struct {
__kernel_timer_t _tid; /* timer id */
int _overrun; /* overrun count */
sigval_t _sigval; /* same as below */
int _sys_private; /* not to be passed to user */
} _timer;
/* POSIX.1b signals */
struct {
__kernel_pid_t _pid; /* sender's pid */
__kernel_uid32_t _uid; /* sender's uid */
sigval_t _sigval;
} _rt; /* rt表示实时信号 */
/* SIGCHLD */
struct {
__kernel_pid_t _pid; /* which child */
__kernel_uid32_t _uid; /* sender's uid */
int _status; /* exit code */
__ARCH_SI_CLOCK_T _utime;
__ARCH_SI_CLOCK_T _stime;
} _sigchld;
/* SIGILL, SIGFPE, SIGSEGV, SIGBUS, SIGTRAP, SIGEMT */
struct {
void __user *_addr; /* faulting insn/memory ref. */
#ifdef __ARCH_SI_TRAPNO
int _trapno; /* TRAP # which caused the signal */
#endif
#ifdef __ia64__
int _imm; /* immediate value for "break" */
unsigned int _flags; /* see ia64 si_flags */
unsigned long _isr; /* isr */
#endif
#define __ADDR_BND_PKEY_PAD (__alignof__(void *) < sizeof(short) ? \
sizeof(short) : __alignof__(void *))
union {
/*
* used when si_code=BUS_MCEERR_AR or
* used when si_code=BUS_MCEERR_AO
*/
short _addr_lsb; /* LSB of the reported address */
/* used when si_code=SEGV_BNDERR */
struct {
char _dummy_bnd[__ADDR_BND_PKEY_PAD];
void __user *_lower;
void __user *_upper;
} _addr_bnd;
/* used when si_code=SEGV_PKUERR */
struct {
char _dummy_pkey[__ADDR_BND_PKEY_PAD];
__u32 _pkey;
} _addr_pkey;
};
} _sigfault;
/* SIGPOLL */
struct {
__ARCH_SI_BAND_T _band; /* POLL_IN, POLL_OUT, POLL_MSG */
int _fd;
} _sigpoll;
/* SIGSYS */
struct {
void __user *_call_addr; /* calling user insn */
int _syscall; /* triggering system call number */
unsigned int _arch; /* AUDIT_ARCH_* of syscall */
} _sigsys;
} _sifields;
} __ARCH_SI_ATTRIBUTES siginfo_t;
18.3.2 send_sig_info 释放实时信号
#include <linux/signal.h>
int send_sig_info(int, struct siginfo *, struct task_struct *);
· int :指定要发送的实时信号