北京网站搭建多少钱,权威发布是什么意思,wordpress 仪表盘裁剪图片,张槎网站开发在上一章节的实验中#xff0c;对并发与竞争进行了实验#xff0c;两个app应用程序之间对共享资源的竞争访问引起了数据传输错误#xff0c;而在Linux内核中#xff0c;提供了四种处理并发与竞争的常见方法#xff0c;分别是原子操作、自旋锁、信号量、互斥体#xff0c;…在上一章节的实验中对并发与竞争进行了实验两个app应用程序之间对共享资源的竞争访问引起了数据传输错误而在Linux内核中提供了四种处理并发与竞争的常见方法分别是原子操作、自旋锁、信号量、互斥体在之后的几个章节中会依次对上述四种方法进行讲解。
本章首先对四种常见方法中的原子操作进行讲解。
20.1 原子操作
“原子”是化学世界中不可再分的最小微粒一切物质都由原子组成。在Linux内核中的原子操作可以理解为“不可被拆分的操作”就是不能被更高等级中断抢夺优先的操作。在C语言中可以使用以下代码对一个整形变量赋值。
int v;//定义一个int类型的变量v
v 1;//将int类型的变量v赋值为1而上述代码仍然不是“不可拆分的操作”C语言程序仍然需要翻译成汇编指令在汇编指令的执行过程中仍可能会有竞争的产生。而原子操作会将整形变量的操作当成一个整体不可再进行分割。而原子操作又可以进一步细分为“整型原子操作”和“位原子操作”这里首先对****整型原子操作****进行讲解。
在Linux内核中使用 atomic_t和atomic64_t结构体分别来完成32位系统和64位系统的整形数据原子操作两个结构体定义在“内核源码/include/linux/types.h”文件中具体定义如下 typedef struct {int counter;} atomic_t;#ifdef CONFIG_64BITtypedef struct {long counter;
} atomic64_t;#endif例如可以使用以下代码定义一个64位系统的原子整形变量
atomic64_t v;在成功定义原子变量之后必然要对原子变量进行读取、加减等动作原子操作的部分常用API函数如下所示定义在“内核源码/include/linux/atomic.h”文件中所以在接下来的实验中需要加入该头文件的引用。
函数描述ATOMIC_INIT(int i)定义原子变量的时候对其初始化赋值为iint atomic_read(atomic_t *v)读取v的值并且返回。void atomic_set(atomic_t *v, int i)向原子变量v写入i值。void atomic_add(int i, atomic_t *v)原子变量v加上i值。void atomic_sub(int i, atomic_t *v)原子变量v减去i值。void atomic_inc(atomic_t *v)原子变量v加1void atomic_dec(atomic_t *v)原子变量v减1int atomic_dec_return(atomic_t *v)原子变量v减1并返回v的值。int atomic_inc_return(atomic_t *v)原子变量v加 1并返回v的值。int atomic_sub_and_test(int i, atomic_t *v)原子变量v减 i如果结果为0就返回真否则返回假int atomic_dec_and_test(atomic_t *v)原子变量v减 1如果结果为0就返回真否则返回假int atomic_inc_and_test(atomic_t *v)原子变量v加 1如果结果为0就返回真否则返回假int atomic_add_negative(int i, atomic_t *v)原子变量v加 i如果结果为负就返回真否则返回假
图表20- 1
至此对于整型原子操作的相关API函数就讲解完成了会在下一小节中使用上述原子整形操作API进行相应的实验。
下面对原子位操作进行讲解和原子整形变量不同原子位操作没有 atomic_t 的数据结构原子位操作是直接对内存进行操作原子位操作相关API函数如下图表20-2所示
函数描述void set_bit(int nr, void *p)将 p 地址的第 nr 位置 1。void clear_bit(int nr,void *p)将 p 地址的第 nr 位清零。void change_bit(int nr, void *p)将 p 地址的第 nr 位进行翻转。int test_bit(int nr, void *p)获取 p 地址的第 nr 位的值。int test_and_set_bit(int nr, void *p)将 p 地址的第 nr 位置 1并且返回 nr 位原来的值。int test_and_clear_bit(int nr, void *p)将 p 地址的第 nr 位清零并且返回 nr 位原来的值。int test_and_change_bit(int nr, void *p)将 p 地址的第 nr 位翻转并且返回 nr 位原来的值。
图表20- 2
对于原子位操作的知识就不再深入讲解和实验感兴趣的同学可以到相关网站上进行自主学习。
在下一小节中将会使用原子整形操作对19章的并发与竞争实验进行改进。
20.2 实验程序的编写
20.2.1 驱动程序编写
本实验对应的网盘路径为iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\15\module。
为了解决第19章实验中并发与竞争的问题本章节实验将加入原子整形操作相关实验代码在open()函数和release()函数中加入原子整形变量v的赋值代码并且在open()函数中加入原子整形变量v的判断代码从而实现同一时间内只允许一个应用打开该设备节点以此来防止共享资源竞争的产生。
编写完成的atomic.c代码如下所示
#include linux/init.h
#include linux/module.h
#include linux/fs.h
#include linux/cdev.h
#include linux/kdev_t.h
#include linux/uaccess.h
#include linux/delay.h
#include linux/atomic.h
#include linux/errno.hstatic atomic64_t v ATOMIC_INIT(1);//初始化原子类型变量v,并设置为1
static int open_test(struct inode *inode,struct file *file)
{if(atomic64_read(v) ! 1){//读取原子类型变量v的值并判断是否等于1return -EBUSY;}atomic64_set(v,0);//将原子类型变量v的值设置为0//printk(\nthis is open_test \n);return 0;
}static ssize_t read_test(struct file *file,char __user *ubuf,size_t len,loff_t *off)
{int ret;char kbuf[10] topeet;//定义char类型字符串变量kbufprintk(\nthis is read_test \n);ret copy_to_user(ubuf,kbuf,strlen(kbuf));//使用copy_to_user接收用户空间传递的数据if (ret ! 0){printk(copy_to_user is error \n);}printk(copy_to_user is ok \n);return 0;
}
static char kbuf[10] {0};//定义char类型字符串全局变量kbuf
static ssize_t write_test(struct file *file,const char __user *ubuf,size_t len,loff_t *off)
{int ret;ret copy_from_user(kbuf,ubuf,len);//使用copy_from_user接收用户空间传递的数据if (ret ! 0){printk(copy_from_user is error\n);}if(strcmp(kbuf,topeet) 0 ){//如果传递的kbuf是topeet就睡眠四秒钟ssleep(4);}else if(strcmp(kbuf,itop) 0){//如果传递的kbuf是itop就睡眠两秒钟ssleep(2);}printk(copy_from_user buf is %s \n,kbuf);return 0;
}
static int release_test(struct inode *inode,struct file *file)
{//printk(\nthis is release_test \n);atomic64_set(v,1);//将原子类型变量v的值赋1return 0;
}struct chrdev_test {dev_t dev_num;//定义dev_t类型变量dev_num来表示设备号int major,minor;//定义int类型的主设备号major和次设备号minorstruct cdev cdev_test;//定义struct cdev 类型结构体变量cdev_test表示要注册的字符设备struct class *class_test;//定于struct class *类型结构体变量class_test表示要创建的类
};
struct chrdev_test dev1;//创建chrdev_test类型的
struct file_operations fops_test {.owner THIS_MODULE,//将owner字段指向本模块可以避免在模块的操作正在被使用时卸载该模块.open open_test,//将open字段指向open_test(...)函数.read read_test,//将read字段指向read_test(...)函数.write write_test,//将write字段指向write_test(...)函数.release release_test,//将release字段指向release_test(...)函数
};static int __init atomic_init(void)
{if(alloc_chrdev_region(dev1.dev_num,0,1,chrdev_name) 0 ){//自动获取设备号设备名chrdev_nameprintk(alloc_chrdev_region is error \n);}printk(alloc_chrdev_region is ok \n);dev1.major MAJOR(dev1.dev_num);//使用MAJOR()函数获取主设备号dev1.minor MINOR(dev1.dev_num);//使用MINOR()函数获取次设备号printk(major is %d,minor is %d\n,dev1.major,dev1.minor);cdev_init(dev1.cdev_test,fops_test);//使用cdev_init()函数初始化cdev_test结构体并链接到fops_test结构体dev1.cdev_test.owner THIS_MODULE;//将owner字段指向本模块可以避免在模块的操作正在被使用时卸载该模块cdev_add(dev1.cdev_test,dev1.dev_num,1);//使用cdev_add()函数进行字符设备的添加dev1.class_test class_create(THIS_MODULE,class_test);//使用class_create进行类的创建类名称为class_testdevice_create(dev1.class_test,0,dev1.dev_num,0,device_test);//使用device_create进行设备的创建设备名称为device_testreturn 0;
}static void __exit atomic_exit(void)
{device_destroy(dev1.class_test,dev1.dev_num);//删除创建的设备class_destroy(dev1.class_test);//删除创建的类cdev_del(dev1.cdev_test);//删除添加的字符设备cdev_testunregister_chrdev_region(dev1.dev_num,1);//释放字符设备所申请的设备号printk(module exit \n);
}
module_init(atomic_init);
module_exit(atomic_exit)
MODULE_LICENSE(GPL v2);
MODULE_AUTHOR(topeet);20.2.2 编写测试 APP
本实验应用程序对应的网盘路径为iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\15\app。
本测试app代码和上一章节相同需要输入两个参数第一个参数为对应的设备节点第二个参数为“topeet”或者“itop”分别代表向设备写入的数据编写完成的应用程序app.c内容如下所示
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include string.h#include unistd.h
int main(int argc, char *argv[])
{int fd;//定义int类型的文件描述符char str1[10] {0};//定义读取缓冲区str1fd open(argv[1],O_RDWR);//调用open函数打开输入的第一个参数文件权限为可读可写if(fd 0 ){printf(file open failed \n);return -1;}/*如果第二个参数为topeet条件成立调用write函数写入topeet*/ if (strcmp(argv[2],topeet) 0 ){write(fd,topeet,10);}/*如果第二个参数为itop条件成立调用write函数写入itop*/ else if (strcmp(argv[2],itop) 0 ){write(fd,itop,10);}close(fd); return 0;
}20.3 运行测试
20.3.1 编译驱动程序
在上一小节中的atomic.c代码同一目录下创建 Makefile 文件Makefile 文件内容如下所示
export ARCHarm64#设置平台架构
export CROSS_COMPILEaarch64-linux-gnu-#交叉编译器前缀
obj-m atomic.o #此处要和你的驱动源文件同名
KDIR :/home/topeet/Linux/linux_sdk/kernel #这里是你的内核目录
PWD ? $(shell pwd)
all:make -C $(KDIR) M$(PWD) modules #make操作
clean:make -C $(KDIR) M$(PWD) clean #make clean操作对于Makefile的内容注释已在上图添加保存退出之后来到存放atomic.c和Makefile文件目录下如下图图20-4所示 图 20-4
然后使用命令“make”进行驱动的编译编译完成如下图图20-5所示 图 20-5
编译完生成atomic.ko目标文件如下图图20-6所示 图 20-6
至此驱动模块就编译成功了下面进行应用程序的编译。
20.3.2 编译应用程序
来到应用程序app.c文件的存放路径如下图图20-7所示 图 20-7
然后使用以下命令对app.c进行交叉编译编译完成如下图图20-8所示
aarch64-linux-gnu-gcc -o app app.c -static图 20-8
生成的app文件就是之后放在开发板上运行的可执行文件至此应用程序的编译就完成了。
20.3.3 运行测试
开发板启动之后使用以下命令进行驱动模块的加载如下图图20-9所示
insmod atomic.ko图 20-9
可以看到申请的主设备号和次设备号就被打印了出来然后使用以下代码对自动生成的设备节点device_test进行查看如下图图20-10所示 ls /dev/device_test图 20-10
可以看到device_test节点已经被自动创建了然后使用以下命令运行测试app运行结果如下图图20-11所示
./app /dev/device_test topeet图 20-11
可以看到传递的buf值为topeet然后输入以下命令在后台运行两个app来进行竞争测试运行结果如下图图20-12所示
./app /dev/device_test topeet ./app /dev/device_test itop 图 20-12
可以看到应用程序在打开第二次/dev/device_test 文件的时候出现了“file open failed”打印证明文件打开失败只有在第一个应用关闭相应的文件之后下一个应用才能打开通过限制同一时间内设备访问数量来对共享资源进行保护。
最后可以使用以下命令进行驱动的卸载如下图图20-13所示
rmmod flag.ko图 20-13
p /dev/device_test itop [外链图片转存中...(img-ggVvfwTR-1694222528726)] 图 20-12 可以看到应用程序在打开第二次/dev/device_test 文件的时候出现了“file open failed”打印证明文件打开失败只有在第一个应用关闭相应的文件之后下一个应用才能打开通过限制同一时间内设备访问数量来对共享资源进行保护。最后可以使用以下命令进行驱动的卸载如下图图20-13所示
rmmod flag.ko [外链图片转存中...(img-hcalLUxj-1694222528726)] 图 20-13至此原子操作实验就完成了。