一、总体框架
1.Linux字符设备驱动工作原理图
2.驱动使用端
3.驱动实现端
二、各部分详解
1.VFS层
1) inode结构体
在Unix/Linux操作系统中,每个文件都由一个inode(索引节点)来索引。inode是特殊的磁盘块,它们在文件系统创建时就已经生成。inode的数量限制了文件系统中可以存储的文件/目录的总数。
每个inode节点的大小,一般是128字节或256字节。inode节点的总数,在格式化时就给定,一般是每1KB或每2KB就设置一个inode。假定在一块1GB的硬盘中,每个inode节点的大小为128字节,每1KB就设置一个inode,那么inode table的大小就会达到128MB,占整块硬盘的12.8%。
inode包含以下信息:
文件的字节数
文件拥有者的User ID
文件的Group ID
文件的读、写、执行权限
文件的时间戳,包括:
ctime:inode上一次变动的时间
mtime:文件内容上一次变动的时间
atime:文件上一次打开的时间
链接数,即有多少文件名指向这个inode
文件数据block的位置
inode的存在使得文件名和inode号码分离,这种机制导致了一些Unix/Linux系统特有的现象。例如,移动文件或重命名文件,只是改变文件名,不影响inode号码。打开一个文件以后,系统就以inode号码来识别这个文件,不再考虑文件名。因此,通常来说,系统无法从inode号码得知文件名。
总的来说,inode在文件系统中起着至关重要的作用,它存储了文件的元信息,并为文件的存储和访问提供了基础。
2) struct file结构体
struct file是Linux内核中的一个重要结构体,它代表一个已经打开的文件。系统中的每个打开的文件在内核空间都有一个关联的struct file。它由内核在打开文件时创建,并传递给在文件上进行操作的任何函数。在文件的所有实例都关闭后,内核释放这个数据结构。
struct file的定义在include/linux/fs.h中。在内核创建和驱动源码中,struct file的指针通常被命名为file或filp。
struct file的一些重要成员包括:
文件描述符(fd):fd只是一个小整数,在open时产生。起到一个索引的作用,进程通过PCB中的文件描述符表找到该fd所指向的文件指针filp。
缓冲区:根据应用程序对文件的访问方式,即是否存在缓冲区,对文件的访问可以分为带缓冲区的操作和非缓冲区的文件操作。
总的来说,struct file结构体在Linux内核中起着至关重要的作用,它为文件的打开、读写等操作提供了基础。在编写设备驱动或者进行内核开发时,理解和掌握struct file结构体的使用是非常重要的。
2.字符设备的描述和操作
1) cdev结构体
在Linux内核中,cdev结构体用于描述一个字符设备。cdev结构体的定义如下:
struct cdev {
struct kobject kobj; // 内嵌的内核对象
struct module *owner; // 该字符设备所在的内核模块的对象指针
const struct file_operations *ops; // 该结构描述了字符设备所能实现的方法,是极为关键的一个结构体
struct list_head list; // 用来将已经向内核注册的所有字符设备形成链表
dev_t dev; // 字符设备的设备号,由主设备号和次设备号构成
unsigned int count; // 隶属于同一主设备号的次设备号的个数
};
在Linux字符设备驱动中,模块加载函数通过register_chrdev_region()或alloc_chrdev_region()来静态或者动态获取设备号,通过cdev_init()建立cdev与file_operations之间的连接,通过cdev_add()向系统添加一个cdev以完成注册。用户空间访问该设备的程序通过Linux系统调用,如open(),read(),write(),来“调用”file_operations来定义字符设备驱动提供给VFS的接口函数。
总的来说,cdev结构体在Linux内核中起着至关重要的作用,它为字符设备的管理提供了基础。在编写设备驱动或者进行内核开发时,理解和掌握cdev结构体的使用是非常重要的。
2) file_operations结构体
file_operations结构体在Linux内核中的定义位于linux/fs.h头文件中。它用来存储驱动内核模块提供的对设备进行各种操作的函数的指针。该结构体的每个域都对应着驱动内核模块用来处理某个被请求的事务的函数的地址。
例如,每个字符设备需要定义一个用来读取设备数据的函数。这个结构的每一个成员都对应着一个系统调用。读取file_operation中相应的函数指针,接着把控制权转交给函数,从而完成了Linux设备驱动程序的工作。
在系统内部,I/O设备的存取操作通过特定的入口点来进行,而这组特定的入口点恰恰是由设备驱动程序提供的。通常这组设备驱动程序接口是由结构file_operations结构体向系统说明的。
file_operations结构体中的成员函数是字符设备驱动程序设计的主体内容,这些函数实际会在应用程序进行Linux的open()、write()、read()、close()等系统调用时最终被内核调用。
总的来说,file_operations结构体在Linux内核中起着至关重要的作用,它为设备的各种操作提供了基础。在编写设备驱动或者进行内核开发时,理解和掌握file_operations结构体的使用是非常重要的。
三、注册字符设备
1.相关函数
1)手动注册设备号:
int register_chrdev_region(dev_t first, unsigned int count, char *name);
first
:要注册的第一个设备号。count
:要注册的设备号的数量。name
:与设备关联的名称。
一旦分配了设备号,可以从/proc/devices中读取它。
2)动态分配设备号:
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
dev
:输出参数,用于返回第一个分配的设备号。firstminor
:请求的次设备号范围的第一个数字。count
:所需的次设备号的数量。name
:与设备关联的名称。如果成功,此函数返回零,否则返回负的错误代码。
3)初始化cdev结构
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
cdev
:要初始化的cdev
结构的指针。fops
:与设备关联的文件操作的指针。
cdev_init()函数用于将cdev与一组file_operations关联起来。然后,可以调用cdev_add()函数使设备生效,这样用户就可以访问它们。
需要注意的是,cdev_init()函数用于初始化一个已经存在但未初始化的struct cdev,这个struct cdev是由你分配的。实际上,cdev_alloc()等价于以下代码:
struct cdev* p = malloc(sizeof(struct cdev));
cdev_init(p, opts);
4)将字符设备添加到系统中
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
dev
:要添加的cdev
结构的指针。num
:设备号。count
:设备号的数量。
在调用cdev_add()函数后,设备就会变为活动状态,内核可以调用其操作。
5)代码示例:
struct cdev my_cdev;
dev_t dev_no;
int major;
int minor_start = 0;
int minor_count = 1;
char *devname = "my_device";
// 初始化cdev结构
cdev_init(&my_cdev, &my_fops);
my_cdev.owner = THIS_MODULE;
// 分配设备号
alloc_chrdev_region(&dev_no, minor_start, minor_count, devname);
major = MAJOR(dev_no);
// 添加设备
cdev_add(&my_cdev, MKDEV(major, minor_start), minor_count);
在上述代码中,首先使用cdev_init()函数初始化cdev结构,然后使用alloc_chrdev_region()函数分配设备号,最后使用cdev_add()函数添加设备。
2.驱动程序
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
//自定义主设备号和次设备号
int major = 11;
int minor = 0;
int mychar_num = 1;
//定义cdev结构体:
struct cdev mydev;
//定义操作函数:
int mychar_open(struct inode *pnode, struct file *pfile)
{
printk("mychar_open is called\n");
return 0;
}
int mychar_close(struct inode *pnode, struct file *pfile)
{
printk("mychar_close is called\n");
return 0;
}
//定义操作函数集:
struct file_operations myops = {
.owner = THIS_MODULE,
.open = mychar_open,
.release = mychar_close,
};
//添加模块:
int mychar_init(void)
{
int ret = 0;
//将主设备号和次设备号转换成dev_t类型的设备编号:
dev_t devno = MKDEV(major, minor);
/* 申请设备号 */
/*若静态注册register_chrdev_region()失败,采用动态注册alloc_chrdev_region(),注册成功后用MAJOR()获取设备在设备表中的位置。*/
ret = register_chrdev_region(devno, mychar_num, "mychar");
if (ret) {
ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
if (ret) {
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno); // 容易遗漏,注意
}
/* 给struct cdev对象mydev指定操作函数集 */
cdev_init(&mydev, &myops); //绑定cdev结构体和操作函数集:
/* 将 struct cdev对象mydev添加到内核对应的数据结构里 */
/*将cdev结构体添加到内核中*/
mydev.owner = THIS_MODULE;
cdev_add(&mydev, devno, mychar_num);
return 0;
}
//卸载模块:
void __exit mychar_exit(void)
{
dev_t devno = MKDEV(major, minor);
cdev_del(&mydev);
unregister_chrdev_region(devno, mychar_num);
}
//表示支持GPL的开源协议
MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);
3.app
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdio.h>
int main(int argc,char *argv[])
{
int fd = -1;
if(argc < 2)
{
printf("The argument is too few\n");
return 1;
}
fd = open(argv[1],O_RDWR);
if(fd < 0)
{
printf("open %s failed\n",argv[1]);
return 2;
}
close(fd);
fd = -1;
return 0;
}
4.linux操作
make命令编译模块,生成对应的.ko文件:
Insmod命令将该模块插入到内核中,并通过cat命令在/proc/devices目录下查看生成的设备号:
在
/dev
目录下创建一个名为mydev
的字符设备文件,其主设备号为11,次设备号为0
四、字符设备驱动读写操作实现
1.相关函数
1) ssize_t xxx_read()
ssize_t xxx_read函数是一个通用的读取函数,用于从文件描述符fd中读取count个字节的数据到缓冲区buf中。ssize_t是一个有符号的整数类型,用于表示读取的字节数。
ssize_t xxx_read(struct file *filp, char __user *pbuf, size_t count, loff_t *ppos);
完成功能:读取设备产生的数据
参数:
filp:指向open产生的struct file类型的对象,表示本次read对应的那次open
pbuf:指向用户空间一块内存,用来保存读到的数据
count:用户期望读取的字节数
ppos:对于需要位置指示器控制的设备操作有用,用来指示读取的起始位置,读完后也需要变更位置指示器的指示位置
返回值:
本次成功读取的字节数,失败返回-1
将设备缓冲区的数据复制到用户空间缓冲区:
unsigned long copy_to_user (void __user * to, const void * from, unsigned long n)
成功为返回0,失败非0
2) ssize_t xxx_write()
ssize_t xxx_write (struct file *filp, const char __user *pbuf, size_t count, loff_t *ppos);
完成功能:向设备写入数据
参数:
filp:指向open产生的struct file类型的对象,表示本次write对应的那次open
pbuf:指向用户空间一块内存,用来保存被写的数据
count:用户期望写入的字节数
ppos:对于需要位置指示器控制的设备操作有用,用来指示写入的起始位置,写完后也需要变更位置指示器的指示位置
返回值:
本次成功写入的字节数,失败返回-1
将用户空间缓冲区复制到设备缓冲区的数据:
unsigned long copy_from_user (void * to, const void __user * from, unsigned long n)
成功为返回0,失败非0
3) container_of()
container_of是Linux内核中的一个宏,它用于获取包含特定成员的结构体的指针,宏定义如下:
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member) * __mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); })在这个宏中:
ptr
是指向结构体成员的指针。type
是结构体的类型。member
是结构体中的成员。
这个宏的工作原理是首先获取成员在结构体中的偏移量(使用offsetof宏),然后从成员的地址中减去这个偏移量,得到的就是结构体的地址。
例如,假设我们有一个如下的结构体:
struct container {
int some_other_data;
int this_data;
}
并且我们有一个指向this_data成员的指针int *my_ptr,我们可以使用container_of宏来获取指向container结构体的指针:
struct container *my_container;
my_container = container_of(my_ptr, struct container, this_data);
这样,my_container就是指向包含this_data成员的container结构体的指针。
简言之,函数定义:
container_of(成员地址,结构体类型名,成员在结构体中的名称)
在驱动程序中示例如下:
在mychar_open函数中,将设备的私有数据结构指针保存在文件结构的private_data字段中。
pfile->private_data =(void *) (container_of(pnode->i_cdev,struct mychar_dev,mydev));
- pnode->i_cdev是字符设备结构的指针,
- mydev是mychar_dev结构中的字符设备结构成员,
- container_of返回的是包含mydev的mychar_dev结构的指针。
在mychar_open函数中,我们已经将mychar_dev结构的指针保存在private_data中,现在我们可以在mychar_read和mychar_write函数中获取它。
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
2.驱动程序
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#define BUF_LEN 100 //定义设备缓冲区的长度
int major = 11;
int minor = 0;
int mychar_num = 1;
struct mychar_dev
{
struct cdev mydev; //定义字符设备结构体、设备缓冲区和当前缓冲区长度
char mydev_buf[BUF_LEN];
int curlen; //设备缓冲区当前已经存储的数据的长度
};
struct mychar_dev gmydev; //全局结构体gmydev
int mychar_open(struct inode *pnode,struct file *pfile)
{
//将设备的私有数据结构指针保存在文件结构的private_data字段中
pfile->private_data =(void *) (container_of(pnode->i_cdev,struct mychar_dev,mydev));
printk("open\n");
return 0;
}
int mychar_close(struct inode *pnode,struct file *pfile)
{
printk("close\n");
return 0;
}
ssize_t mychar_read(struct file *pfile,char __user *puser,size_t count,loff_t *p_pos)
{
int size = 0;
int ret = 0;
//从文件结构的private_data字段中获取设备的私有数据结构指针
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
//如果请求读取的字节数大于当前缓冲区的长度,那么实际读取的字节数就是当前缓冲区的长度;否则,实际读取的字节数就是请求读取的字节数。
if(count > pmydev->curlen)
{
size = pmydev->curlen;
}
else
{
size = count;
}
ret = copy_to_user(puser,pmydev->mydev_buf,size); //将当前缓冲区mydev_buf中的size大小的数据读取到用户缓冲区puser
if(ret)
{
printk("copy_to_user failed\n");
return -1;
}
//将设备缓冲区未读取的数据移到缓冲区的开始位置。
memcpy(mydev_buf,mydev_buf + size,curlen - size);
//更新当前缓冲区的长度。
curlen -= size;
return size;
}
ssize_t mychar_write(struct file *pfile,const char __user *puser,size_t count,loff_t *p_pos)
{
int size = 0;
int ret = 0;
//从文件结构的private_data字段中获取设备的私有数据结构指针
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
if(count > BUF_LEN - pmydev->curlen)
{
size = BUF_LEN - pmydev->curlen;
}
else
{
size = count;
}
//将用户缓冲区puser中的size大小的数据读取到当前缓冲区mydev_buf
ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen,puser,size);
if(ret)
{
printk("copy_from_user failed\n");
return -1;
}
pmydev->curlen += size;
return size;
}
struct file_operations myops = {
.owner = THIS_MODULE,
.open = mychar_open,
.release = mychar_close,
.read = mychar_read,
.write = mychar_write,
};
int __init mychar_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major,minor);
/*申请设备号*/
ret = register_chrdev_region(devno,mychar_num,"mychar");
if(ret)
{
ret = alloc_chrdev_region(&devno,minor,mychar_num,"mychar");
if(ret)
{
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);//容易遗漏,注意
}
/*给struct cdev对象指定操作函数集*/
cdev_init(&gmydev.mydev,&myops);
/*将struct cdev对象添加到内核对应的数据结构里*/
mydev.owner = THIS_MODULE;
cdev_add(&gmydev.mydev,devno,1);
return 0;
}
void __exit mychar_exit(void)
{
dev_t devno = MKDEV(major,minor);
cdev_del(&gmydev.mydev);
unregister_chrdev_region(devno,mychar_num);
}
MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);
3.app
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdio.h>
int main(int argc,char *argv[])
{
int fd = -1;
char buf[10] = "";
if(argc < 2)
{
printf("The argumentsent is too few\n");
return 1;
}
fd = open(argv[1],O_RDWR);
if(fd < 0)
{
printf("open %s failed\n",argv[1]);
return 2;
}
write(fd,"test txt",9);
read(fd,buf,9);
printf("buf=%s\n",buf);
close(fd);
fd = -1;
return 0;
}
4.linux操作
同 三、4.linux操作,不再赘述。
五、ioctl、printk及多个次设备支持
1.ioctl操作实现
xxx_ioctl是一个在Linux设备驱动程序中常见的函数,用于处理从用户空间发出的IOCTL系统调用。这个函数通常在设备驱动程序的file_operations结构中定义。
函数的定义如下:
long xxx_ioctl (struct file *filp, unsigned int cmd, unsigned long arg)
参数解释:
- struct file *filp:一个指向struct file的指针,表示一个打开的设备文件。
- unsigned int cmd:这是从用户空间传递过来的,表示要执行的特定命令。
- unsigned long arg:这是一个可选参数,其含义和使用方式取决于cmd参数。
这个函数的返回值是一个长整型。如果操作成功,通常会返回0。如果出现错误,会返回一个负的错误码。
注意:具体的cmd命令和arg参数的含义和使用方式,以及返回值的具体含义,都取决于你正在编写或修改的特定设备驱动程序。在编写xxx_ioctl函数时,你需要根据你的设备驱动程序的需求来实现具体的功能。
cmd组成:
-
dir(direction),ioctl 命令访问模式(属性数据传输方向),占据 2 bit,可以为 IOC_NONE、IOC_READ、IOC_WRITE、IOC_READ | _IOC_WRITE,分别指示了四种访问模式:无数据、读数据、写数据、读写数据;
-
type(device type),设备类型,占据 8 bit,在一些文献中翻译为 “幻数” 或者 “魔数”,可以为任意 char 型字符,例如 ‘a’、’b’、’c’ 等等,其主要作用是使 ioctl 命令有唯一的设备标识;
-
nr(number),命令编号/序数,占据 8 bit,可以为任意 unsigned char 型数据,取值范围 0~255,如果定义了多个 ioctl 命令,通常从 0 开始编号递增;
-
size,涉及到 ioctl 函数 第三个参数 arg ,占据 13bit 或者 14bit(体系相关,arm 架构一般为 14 位),指定了 arg 的数据类型及长度,如果在驱动的 ioctl 实现中不检查,通常可以忽略该参数;
示例:
头文件mychar.h
#ifndef MY_CHAR_H
#define MY_CHAR_H
#include <asm/ioctl.h>
#define MY_CHAR_MAGIC 'k'
#define MYCHAR_IOCTL_GET_MAXLEN _IOR(MY_CHAR_MAGIC,1,int*)
#define MYCHAR_IOCTL_GET_CURLEN _IOR(MY_CHAR_MAGIC,2,int*)
#endif
long mychar_ioctl(struct file *pfile,unsigned int cmd,unsigned long arg)
{
int __user *pret = (int *)arg; //将arg参数转换为一个指向用户空间整数的指针
int maxlen = BUF_LEN;
int ret = 0;
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
switch(cmd)
{
case MYCHAR_IOCTL_GET_MAXLEN:
ret = copy_to_user(pret,&maxlen,sizeof(int));
if(ret)
{
printk("copy_to_user MAXLEN failed\n");
return -1;
}
break;
case MYCHAR_IOCTL_GET_CURLEN:
ret = copy_to_user(pret,&pmydev->curlen,sizeof(int));
if(ret)
{
printk("copy_to_user CURLEN failed\n");
return -1;
}
break;
default:
printk("The cmd is unknow\n");
return -1;
}
return 0;
}
2.printk
3.多个次设备的支持
每一个具体设备(次设备不一样的设备),必须有一个struct cdev来代表它
cdev_init
cdev.owner赋值
cdev_add
以上三个操作对每个具体设备都要进行
4.驱动程序
六、阻塞和非阻塞
1.五种IO模型
1)阻塞: 不能操作就睡觉
2)非阻塞:不能操作就返回错误
3)多路复用:委托中介监控
4)信号驱动:让内核如果能操作时发信号,在信号处理函数中操作
5)异步IO:向内核注册操作请求,内核完成操作后发通知信号
2.阻塞与非阻塞
应用层:
open时由O_NONBLOCK指示read、write时是否阻塞
或者在open以后可以由fcntl函数来改变是否阻塞:
flags = fcntl(fd,F_GETFL,0);
flags |= O_NONBLOCK;
fcntl(fd, F_SETFL, flags);
驱动层:通过等待队列
wait_queue_head_t //等待队列头数据类型
init_waitqueue_head(wait_queue_head_t *pwq) //初始化等待队列头
wait_event_interruptible(wq,condition)
/*
功能:条件不成立则让任务进入浅度睡眠,直到条件成立醒来
wq:等待队列头
condition:C语言表达式
返回:正常唤醒返回0,信号唤醒返回非0(此时读写操作函数应返回-ERESTARTSYS)
*/
wait_event(wq,condition) //深度睡眠wake_up_interruptible(wait_queue_head_t *pwq)
wake_up(wait_queue_head_t *pwq)
/*
1. 读、写用不同的等待队列头rq、wq
2. 无数据可读、可写时调用wait_event_interruptible(rq、wq,条件)
3. 写入数据成功时唤醒rq,读出数据成功唤醒wq
*/
3.代码示例
1)驱动程序
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/wait.h>
#include <linux/sched.h>
int major = 11;
int minor = 0;
int mychar_num = 1;
#define BUF_LEN 100
#define MY_CHAR_MAGIC 'k'
#define MYCHAR_IOCTL_GET_MAXLEN _IOR(MY_CHAR_MAGIC,1,int*)
#define MYCHAR_IOCTL_GET_CURLEN _IOR(MY_CHAR_MAGIC,2,int*)
struct mychar_dev
{
struct cdev mydev;
char mydev_buf[BUF_LEN];
int curlen;
wait_queue_head_t rq;
wait_queue_head_t wq;
};
struct mychar_dev gmydev;
int mychar_open(struct inode *pnode, struct file *pfile)
{
pfile->private_data = (void *)(container_of(pnode->i_cdev,struct mychar_dev,mydev));
printk("mychar_open is called\n");
return 0;
}
int mychar_close(struct inode *pnode, struct file *pfile)
{
printk("mychar_close is called\n");
return 0;
}
ssize_t mychar_read(struct file *pfile,char __user *puser,size_t count,loff_t *p_pos)
{
int size = 0;
int ret = 0;
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
if(pmydev->curlen <= 0)
{
if(pfile->f_flags & O_NONBLOCK)
{
printk("O_NONBLOCK and No Data\n");
return -1;
}
else
{
ret = wait_event_interruptible(pmydev->rq,pmydev->curlen > 0);
if(ret)
{
printk("Wake up by signal\n");
return -ERESTARTSYS;
}
}
}
if(count > pmydev->curlen)
{
size = pmydev->curlen;
}
else
{
size = count;
}
ret = copy_to_user(puser,pmydev->mydev_buf,size);
if(ret)
{
printk("copy_to_user failed\n");
return -1;
}
memcpy(pmydev->mydev_buf,pmydev->mydev_buf + size,pmydev->curlen - size);
pmydev->curlen -= size;
wake_up_interruptible(&pmydev->wq);
return size;
}
ssize_t mychar_write(struct file *pfile,const char __user *puser,size_t count,loff_t *p_pos)
{
int size = 0;
int ret = 0;
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
if(pmydev->curlen >= BUF_LEN)
{
if(pfile->f_flags & O_NONBLOCK)
{
printk("O_NONBLOCK and Full Data\n");
return -1;
}
else
{
ret = wait_event_interruptible(pmydev->wq,pmydev->curlen < BUF_LEN);
if(ret)
{
printk("wake up by signal\n");
return -ERESTARTSYS;
}
}
}
if(count > BUF_LEN - pmydev->curlen)
{
size = BUF_LEN - pmydev->curlen;
}
else
{
size = count;
}
ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen,puser,size);
if(ret)
{
printk("copy_from_user failed\n");
return -1;
}
pmydev->curlen += size;
wake_up_interruptible(&pmydev->rq);
return size;
}
struct file_operations myops = {
.owner = THIS_MODULE,
.open = mychar_open,
.release = mychar_close,
.read = mychar_read,
.write = mychar_write,
};
int mychar_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major, minor);
/* 申请设备号 */
ret = register_chrdev_region(devno, mychar_num, "mychar");
if (ret) {
ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
if (ret) {
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);
}
/* 给structtruct cdev对象mydev指定操作函数集 */
cdev_init(&gmydev.mydev, &myops);
/* 将 struct cdev对象mydev添加到内核对应的数据结构里
* */
gmydev.mydev.owner = THIS_MODULE;
cdev_add(&gmydev.mydev, devno, 1);
init_waitqueue_head(&gmydev.wq);
init_waitqueue_head(&gmydev.rq);
return 0;
}
void __exit mychar_exit(void)
{
dev_t devno = MKDEV(major, minor);
cdev_del(&gmydev.mydev);
unregister_chrdev_region(devno, mychar_num);
}
//表示支持GPL的开源协议
MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);
2)app
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdio.h>
int main(int argc,char *argv[])
{
int fd = -1;
char buf[10] = "";
if(argc < 2)
{
printf("The argumentsent is too few\n");
return 1;
}
fd = open(argv[1],O_RDWR);
if(fd < 0)
{
printf("open %s failed\n",argv[1]);
return 2;
}
//write(fd,"test txt",9);
read(fd,buf,9);
printf("buf=%s\n",buf);
close(fd);
fd = -1;
return 0;
}
七、多路复用
1.应用层:三套接口select、poll、epoll
select:位运算实现 监控的描述符数量有限(32位机1024,64位机2048) 效率差
poll:链表实现,监控的描述符数量不限 效率差
epoll:效率最高,监控的描述符数量不限
以select为例:
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
/* 功能:监听多个描述符,阻塞等待有一个或者多个文件描述符,准备就绪。
内核将没有准备就绪的文件描述符,从集合中清掉了。
参数: nfds 最大文件描述符数 ,加1
readfds 读文件描述符集合
writefds 写文件描述符集合
exceptfds 其他异常的文件描述符集合
timeout 超时时间(NULL)
返回值:当timeout为NULL时返回0,成功:准备好的文件描述的个数 出错:-1
当timeout不为NULL时,如超时设置为0,则select为非阻塞,超时设置 > 0,则无描述符可被操作的情况下阻塞指定长度的时间
*/
void FD_CLR(int fd, fd_set *set);
//功能:将fd 从集合中清除掉int FD_ISSET(int fd, fd_set *set);
//功能:判断fd 是否存在于集合中
void FD_SET(int fd, fd_set *set);
//功能:将fd 添加到集合中void FD_ZERO(fd_set *set);
//功能:将集合清零//使用模型(套路):
while(1)
{
/*得到最大的描述符maxfd*/
/*FD_ZERO清空描述符集合*/
/*将被监控描述符加到相应集合rfds里 FD_SET*/
/*设置超时*/
ret = select(maxfd+1,&rfds,&wfds,NULL,NULL);
if(ret < 0)
{
if(errno == EINTR)//错误时信号引起的
{
continue;
}
else
{
break;
}
}
else if(ret == 0)
{//超时
//.....
}
else
{ //> 0 ret为可被操作的描述符个数
if(FD_ISSET(fd1,&rfds))
{//读数据
//....
}
if(FD_ISSET(fd2,&rfds))
{//读数据
//....
}
///.....
if(FD_ISSET(fd1,&wfds))
{//写数据
//....
}
}
}
2.驱动层:实现poll函数
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p);
/*功能:将等待队列头添加至poll_table表中
参数:struct file :设备文件
Wait_queue_head_t :等待队列头
Poll_table :poll_table表
*//*该函数与select、poll、epoll_wait函数相对应,协助这些多路监控函数判断本设备是否有数据可读写*/
unsigned int xxx_poll(struct file *filp, poll_table *wait) //函数名初始化给struct file_operations的成员.poll
{
unsigned int mask = 0;
/*
1. 将所有等待队列头加入poll_table表中
2. 判断是否可读,如可读则mask |= POLLIN | POLLRDNORM;
3. 判断是否可写,如可写则mask |= POLLOUT | POLLWRNORM;
*/
return mask;
}
3.代码示例
1)驱动程序
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/wait.h>
#include <linux/sched.h>
int major = 11;
int minor = 0;
int mychar_num = 1;
#define BUF_LEN 100
#define MY_CHAR_MAGIC 'k'
#define MYCHAR_IOCTL_GET_MAXLEN _IOR(MY_CHAR_MAGIC,1,int*)
#define MYCHAR_IOCTL_GET_CURLEN _IOR(MY_CHAR_MAGIC,2,int*)
struct mychar_dev
{
struct cdev mydev;
char mydev_buf[BUF_LEN];
int curlen;
wait_queue_head_t rq;
wait_queue_head_t wq;
};
struct mychar_dev gmydev;
int mychar_open(struct inode *pnode, struct file *pfile)
{
pfile->private_data = (void *)(container_of(pnode->i_cdev,struct mychar_dev,mydev));
printk("mychar_open is called\n");
return 0;
}
int mychar_close(struct inode *pnode, struct file *pfile)
{
printk("mychar_close is called\n");
return 0;
}
ssize_t mychar_read(struct file *pfile,char __user *puser,size_t count,loff_t *p_pos)
{
int size = 0;
int ret = 0;
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
if(pmydev->curlen <= 0)
{
if(pfile->f_flags & O_NONBLOCK)
{
printk("O_NONBLOCK and No Data\n");
return -1;
}
else
{
ret = wait_event_interruptible(pmydev->rq,pmydev->curlen > 0);
if(ret)
{
printk("Wake up by signal\n");
return -ERESTARTSYS;
}
}
}
if(count > pmydev->curlen)
{
size = pmydev->curlen;
}
else
{
size = count;
}
ret = copy_to_user(puser,pmydev->mydev_buf,size);
if(ret)
{
printk("copy_to_user failed\n");
return -1;
}
memcpy(pmydev->mydev_buf,pmydev->mydev_buf + size,pmydev->curlen - size);
pmydev->curlen -= size;
wake_up_interruptible(&pmydev->wq);
return size;
}
ssize_t mychar_write(struct file *pfile,const char __user *puser,size_t count,loff_t *p_pos)
{
int size = 0;
int ret = 0;
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
if(pmydev->curlen >= BUF_LEN)
{
if(pfile->f_flags & O_NONBLOCK)
{
printk("O_NONBLOCK and Full Data\n");
return -1;
}
else
{
ret = wait_event_interruptible(pmydev->wq,pmydev->curlen < BUF_LEN);
if(ret)
{
printk("wake up by signal\n");
return -ERESTARTSYS;
}
}
}
if(count > BUF_LEN - pmydev->curlen)
{
size = BUF_LEN - pmydev->curlen;
}
else
{
size = count;
}
ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen,puser,size);
if(ret)
{
printk("copy_from_user failed\n");
return -1;
}
pmydev->curlen += size;
wake_up_interruptible(&pmydev->rq);
return size;
}
unsigned init
struct file_operations myops = {
.owner = THIS_MODULE,
.open = mychar_open,
.release = mychar_close,
.read = mychar_read,
.write = mychar_write,
.poll = mycahr_poll,
};
int mychar_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major, minor);
/* 申请设备号 */
ret = register_chrdev_region(devno, mychar_num, "mychar");
if (ret) {
ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
if (ret) {
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);
}
/* 给structtruct cdev对象mydev指定操作函数集 */
cdev_init(&gmydev.mydev, &myops);
/* 将 struct cdev对象mydev添加到内核对应的数据结构里
* */
gmydev.mydev.owner = THIS_MODULE;
cdev_add(&gmydev.mydev, devno, 1);
init_waitqueue_head(&gmydev.wq);
init_waitqueue_head(&gmydev.rq);
return 0;
}
void __exit mychar_exit(void)
{
dev_t devno = MKDEV(major, minor);
cdev_del(&gmydev.mydev);
unregister_chrdev_region(devno, mychar_num);
}
//表示支持GPL的开源协议
MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);
2)app
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <errno.h>
#include <stdio.h>
int main(int argc,char *argv[])
{
int fd = -1;
char buf[10] = "";
int ret = 0;
fd_set rfds;
if(argc < 2)
{
printf("The argumentsent is too few\n");
return 1;
}
fd = open(argv[1],O_RDWR);
if(fd < 0)
{
printf("open %s failed\n",argv[1]);
return 2;
}
while(1)
{
FD_ZERO(&rfds);
FD_SET(fd,&rfds);
ret = select(fd+1,&rfds,NULL,NULL,NULL);
if(ret < 0)
{
if(errno == EINTR)
{
continue;
}
else
{
printf("slect error\n");
break;
}
}
if(FD_ISSET(fd,&rfds))
{
read(fd,buf,9);
printf("buf=%s\n",buf);
}
}
close(fd);
fd = -1;
return 0;
}
八、信号驱动
1.应用层:信号注册+fcntl
signal(SIGIO, input_handler); //注册信号处理函数
fcntl(fd, F_SETOWN, getpid()); //将描述符设置给对应进程,好由描述符获知PID
oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, oflags | FASYNC); //将该设备的IO模式设置成信号驱动模式void input_handler(int signum) //应用自己实现的信号处理函数,在此函数中完成读写
{
//读数据
}//应用模板
int main()
{
int fd = open("/dev/xxxx",O_RDONLY);fcntl(fd, F_SETOWN, getpid());
oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, oflags | FASYNC);signal(SIGIO,xxxx_handler);
//......
}
void xxxx_handle(int signo)
{//读写数据
}
2.驱动层:实现fasync函数
/*设备结构中添加如下成员*/
struct fasync_struct *pasync_obj;/*应用调用fcntl设置FASYNC时调用该函数产生异步通知结构对象,并将其地址设置到设备结构成员中*/
//函数名初始化给struct file_operations的成员.fasync
static int hello_fasync(int fd, struct file *filp, int mode)
{
struct hello_device *dev = filp->private_data;
return fasync_helper(fd, filp, mode, &dev->pasync_obj);
}/*写函数中有数据可读时向应用层发信号*/
if (dev->pasync_obj)
kill_fasync(&dev->pasync_obj, SIGIO, POLL_IN);
/*release函数中释放异步通知结构对象*/
if (dev->pasync_obj)
fasync_helper(-1, filp, 0, &dev->pasync_obj);int fasync_helper(int fd, struct file *filp, int mode, struct fasync_struct **pp);
/*
功能:产生或释放异步通知结构对象
参数:
返回值:成功为>=0,失败负数
*/void kill_fasync(struct fasync_struct **, int, int);
/*
功能:发信号
参数:
struct fasync_struct ** 指向保存异步通知结构地址的指针
int 信号 SIGIO/SIGKILL/SIGCHLD/SIGCONT/SIGSTOP
int 读写信息POLLIN、POLLOUT
*/
3.代码示例
1)驱动程序
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
int major = 11;
int minor = 0;
int mychar_num = 1;
#define BUF_LEN 100
#define MY_CHAR_MAGIC 'k'
#define MYCHAR_IOCTL_GET_MAXLEN _IOR(MY_CHAR_MAGIC,1,int*)
#define MYCHAR_IOCTL_GET_CURLEN _IOR(MY_CHAR_MAGIC,2,int*)
struct mychar_dev
{
struct cdev mydev;
char mydev_buf[BUF_LEN];
int curlen;
wait_queue_head_t rq;
wait_queue_head_t wq;
struct fasync_struct *pasync_obj;
};
struct mychar_dev gmydev;
int mychar_open(struct inode *pnode, struct file *pfile)
{
pfile->private_data = (void *)(container_of(pnode->i_cdev,struct mychar_dev,mydev));
return 0;
}
int mychar_close(struct inode *pnode, struct file *pfile)
{
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
if(pmydev->pasync_obj != NULL)
fasync_helper(-1,pfile,0,&pmydev->pasync_obj);
return 0;
}
ssize_t mychar_read(struct file *pfile,char __user *puser,size_t count,loff_t *p_pos)
{
int size = 0;
int ret = 0;
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
if(pmydev->curlen <= 0)
{
if(pfile->f_flags & O_NONBLOCK)
{
printk("O_NONBLOCK and No Data\n");
return -1;
}
else
{
ret = wait_event_interruptible(pmydev->rq,pmydev->curlen > 0);
if(ret)
{
printk("Wake up by signal\n");
return -ERESTARTSYS;
}
}
}
if(count > pmydev->curlen)
{
size = pmydev->curlen;
}
else
{
size = count;
}
ret = copy_to_user(puser,pmydev->mydev_buf,size);
if(ret)
{
printk("copy_to_user failed\n");
return -1;
}
memcpy(pmydev->mydev_buf,pmydev->mydev_buf + size,pmydev->curlen - size);
pmydev->curlen -= size;
wake_up_interruptible(&pmydev->wq);
return size;
}
ssize_t mychar_write(struct file *pfile,const char __user *puser,size_t count,loff_t *p_pos)
{
int size = 0;
int ret = 0;
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
if(pmydev->curlen >= BUF_LEN)
{
if(pfile->f_flags & O_NONBLOCK)
{
printk("O_NONBLOCK and Full Data\n");
return -1;
}
else
{
ret = wait_event_interruptible(pmydev->wq,pmydev->curlen < BUF_LEN);
if(ret)
{
printk("wake up by signal\n");
return -ERESTARTSYS;
}
}
}
if(count > BUF_LEN - pmydev->curlen)
{
size = BUF_LEN - pmydev->curlen;
}
else
{
size = count;
}
ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen,puser,size);
if(ret)
{
printk("copy_from_user failed\n");
return -1;
}
pmydev->curlen += size;
wake_up_interruptible(&pmydev->rq);
if(pmydev->pasync_obj != NULL)
{
kill_fasync(&pmydev->pasync_obj,SIGIO,POLL_IN);
}
return size;
}
unsigned int mychar_poll(struct file *pfile,poll_table *ptb)
{
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
unsigned int mask = 0;
poll_wait(pfile,&pmydev->rq,ptb);
poll_wait(pfile,&pmydev->wq,ptb);
if(pmydev->curlen > 0)
{
mask |= POLLIN | POLLRDNORM;
}
if(pmydev->curlen < BUF_LEN)
{
mask |= POLLOUT |POLLWRNORM;
}
return mask;
}
int mychar_fasync(int fd,struct file *pfile,int mode)
{
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
return fasync_helper(fd,pfile,mode,&pmydev->pasync_obj);
}
struct file_operations myops = {
.owner = THIS_MODULE,
.open = mychar_open,
.release = mychar_close,
.read = mychar_read,
.write = mychar_write,
.poll = mychar_poll,
.fasync = mychar_fasync,
};
int mychar_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major, minor);
/* 申请设备号 */
ret = register_chrdev_region(devno, mychar_num, "mychar");
if (ret) {
ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
if (ret) {
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);
}
/* 给structtruct cdev对象mydev指定操作函数集 */
cdev_init(&gmydev.mydev, &myops);
/* 将 struct cdev对象mydev添加到内核对应的数据结构里
* */
gmydev.mydev.owner = THIS_MODULE;
cdev_add(&gmydev.mydev, devno, 1);
init_waitqueue_head(&gmydev.wq);
init_waitqueue_head(&gmydev.rq);
return 0;
}
void __exit mychar_exit(void)
{
dev_t devno = MKDEV(major, minor);
cdev_del(&gmydev.mydev);
unregister_chrdev_region(devno, mychar_num);
}
//表示支持GPL的开源协议
MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);
2)app
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
int fd = -1;
void sigio_handler(int sigio);
int main(int argc,char *argv[])
{
int oflags = 0;
if(argc < 2)
{
printf("The argumentsent is too few\n");
return 1;
}
signal(SIGIO,sigio_handler);
fd = open(argv[1],O_RDWR);
if(fd < 0)
{
printf("open %s failed\n",argv[1]);
return 2;
}
fcntl(fd,F_SETOWN,getpid());
oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, oflags | FASYNC);
while(1)
{
}
close(fd);
fd = -1;
return 0;
}
void sigio_handler(int sigio)
{
char buf[9] = "";
read(fd,buf,9);
printf("buf=%s\n",buf);
}
九、并发控制
1.上下文和并发场合
执行流:有开始有结束总体顺序执行的一段代码 又称上下文
应用编程:任务上下文
内核编程:
1.任务上下文:五状态 可阻塞
a. 应用进程或线程运行在用户空间
b. 应用进程或线程运行在内核空间(通过调用syscall来间接使用内核空间)
c. 内核线程始终在内核空间
2.异常上下文:不可阻塞
中断上下文
竞态:多任务并行执行时,如果在一个时刻同时操作同一个资源,会引起资源的错乱,这种错乱情形被称为竞态
共享资源:可能会被多个任务同时使用的资源
临界区:操作共享资源的代码段
为了解决竞态,需要提供一种控制机制,来避免在同一时刻使用共享资源,这种机制被称为并发控制机制
并发控制机制分类:
-
原子操作类
-
忙等待类
-
阻塞类
通用并发控制机制的一般使用套路:
/*互斥问题:*/
并发控制机制初始化为可用
P操作临界区
V操作
/*同步问题:*/
//并发控制机制初始化为不可用
//先行方:
。。。。。
V操作
//后行方:
P操作
。。。。。
2.中断屏蔽
一种同步机制的辅助手段
适用场合:中断上下文与某任务共享资源时,或多个不同优先级的中断上下文间共享资源时
禁止本cpu中断 使能本cpu中断
local_irq_disable(); local_irq_enable();
local_irq_save(flags); local_irq_restore(flags); 与cpu的中断位相关
local_bh_disable(); local_bh_enable(); 与中断低半部有关,关闭、打开软中断
禁止中断
临界区 //临界区代码不能占用太长时间,需要很快完成
打开中断
3.原子变量
原子变量:存取不可被打断的特殊整型变量
适用场合:共享资源为单个整型变量的互斥场合
a.设置原子量的值
void atomic_set(atomic_t *v,int i); //设置原子量的值为i
atomic_t v = ATOMIC_INIT(0); //定义原子变量v并初始化为0
错误示范: v = 10; //错误
b.获取原子量的值
atomic_read(atomic_t *v); //返回原子量的值
c.原子变量加减
void atomic_add(int i,atomic_t *v); //原子变量增加i
void atomic_sub(int i,atomic_t *v); //原子变量减少i
d.原子变量自增自减
void atomic_inc(atomic_t *v); //原子变量增加1
void atomic_dec(atomic_t *v); //原子变量减少1
e.操作并测试:运算后结果为0则返回真,否则返回假 int
atomic_inc_and_test(atomic_t *v);
int atomic_dec_and_test(atomic_t *v);
int atomic_sub_and_test(int i,atomic_t *v);
原子位操作方法:
a.设置位
void set_bit(nr, void *addr); //设置addr的第nr位为1
b.清除位
void clear_bit(nr , void *addr); //清除addr的第nr位为0
c.改变位
void change_bit(nr , void *addr); //改变addr的第nr位为1
d.测试位
void test_bit(nr , void *addr); //测试addr的第nr位是否为1
4.自旋锁
基于忙等待的并发控制机制
适用场合:
- 异常上下文之间或异常上下文与任务上下文之间共享资源时
- 任务上下文之间且临界区执行时间很短时
- 互斥问题
a.定义自旋锁
spinlock_t lock;
b.初始化自旋锁
spin_lock_init(spinlock_t *);
c.获得自旋锁
spin_lock(spinlock_t *); //成功获得自旋锁立即返回,否则自旋在那里直到该自旋锁的保持者释放
spin_trylock(spinlock_t *); //成功获得自旋锁立即返回真,否则返回假,而不是像上一个那样"在原地打转”
d.释放自旋锁
spin_unlock(spinlock_t *);
示范:
#include <linux/spinlock.h>
定义spinlock_t类型的变量lock
spin_lock_init(&lock)后才能正常使用spinlock
spin_lock(&lock);
临界区
spin_unlock(&lock);
十、信号量和互斥锁
1.信号量:
基于阻塞的并发控制机制
适用场合:任务上下文之间且临界区执行时间较长时的互斥或同步问题
#include <linux/semaphore.h>
a.定义信号量
struct semaphore sem;
b.初始化信号量
void sema_init(struct semaphore *sem, int val);
c.获得信号量P
int down(struct semaphore *sem);//深度睡眠
int down_interruptible(struct semaphore *sem);//浅度睡眠
d.释放信号量V
void up(struct semaphore *sem);
2.互斥锁:
基于阻塞的互斥机制
适用场合:任务上下文之间且临界区执行时间较长时的互斥问题
#include <linux/mutex.h>
a.初始化
struct mutex my_mutex; mutex_init(&my_mutex);
b.获取互斥体
void mutex_lock(struct mutex *lock);
c.释放互斥体
void mutex_unlock(struct mutex *lock);
十、选择并发控制机制的原则
-
不允许睡眠的上下文需要采用忙等待类,可以睡眠的上下文可以采用阻塞类。在异常上下文中访问的竞争资源一定采用忙等待类。
-
临界区操作较长的应用建议采用阻塞类,临界区很短的操作建议采用忙等待类。
-
中断屏蔽仅在有与中断上下文共享资源时使用。
-
共享资源仅是一个简单整型量时用原子变量。