共享资源:多个线程进程等能都访问的变量或内存等。比如共享单车。
临界区:操作临界资源的那一段程序称之为临界区。
共享资源与临界资源的关系:临界资源一定是共享资源,但是共享资源不一定是临界资源。当共享资源在一段时间内能否允许被多个进程访问时,就可称之为临界资源。
线程:进程中的一个执行任务的控制单元,负责当前进程中程序的执行。在一个进程至少有一个线程,一个进程可以运行多个线程,同时多个线程也可共享数据。
进程:一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程,比如在ubuntu中,可使用ps
命令来查看当前的进程信息。
并发:是指在单核CPU中的某一时间间隔内,多个任务分时运行。如下图:
并行:是指在多核CPU中的某个时刻,多个任务同时运行。如下图:
竞争:此处的竞争,主要是指资源竞争。如并发可能会造成多个程序同时访问一个共享资源,这时候由并发同时访问一个共享资源产生的问题就叫做竞争。引起竞争的原因有以下几点:
注意在单核CPU中,并行无法实现;同理,在多核CPU内,一般不会有并发。
自制一个用于并发的驱动
制作一个用于并发的驱动简单来说,就是需要处理驱动中的write
函数,因为用户在应用程序中调用write
最终还是会调用驱动中的write
。
这里可以使第一个调用驱动暂停5秒,第二次调用驱动就暂停10秒,这里需要注意的是驱动开发中需要使用ssleep
函数来进行休眠,其中参数是休眠时间,单位秒。
if(strcmp(kbuff,"test1") == 0)
{
ssleep(5);
}
else if(strcmp(kbuff,"test2") == 0)
{
ssleep(10);
}
为了编写驱动简单,此处直接使用杂项设备驱动而不使用字符驱动,整个驱动文件如下:
#include <linux/kernel.h>
#include <linux/init.h> //初始化头文件
#include <linux/module.h> //最基本的文件,支持动态添加和卸载模块。
#include <linux/miscdevice.h> //注册杂项设备头文件
#include <linux/fs.h> //注册设备节点的文件结构体
#include <linux/uaccess.h>
#include <linux/delay.h>
/* 进程并发与并行的驱动 */
// 驱动名
#define DEVICE_NAME "threadtest"
// 数据buffer
static char kbuff[32] ;
// 打开设备
int misc_open(struct inode *inode,struct file*file)
{
printk("misc_open is ok.\r\n");
return 0;
}
// 关闭设备
int misc_close(struct inode * inode, struct file *file)
{
printk("misc_close\r\n");
return 0;
}
// 读取设备中信息
ssize_t misc_read (struct file *file, char __user *buff, size_t size, loff_t *loff)
{
int len = strlen(kbuff);
if(copy_to_user(buff,kbuff,len) != 0) // 将内核中的数据给应用
{
printk("copy_to_user error\r\n");
return -1;
}
printk("copy_to_user is successful\r\n");
return len;
}
// 写入设备信息
ssize_t misc_write (struct file *file, const char __user *buff, size_t size, loff_t *loff)
{
int ret = copy_from_user(kbuff,buff,size);
if( ret != 0) // 从应用那儿获取数据
{
printk("ret of error is %d.\r\n",ret);
return ret;
}
if(strcmp(kbuff,"test1") == 0)
{
ssleep(2);
}
else if(strcmp(kbuff,"test2") == 0)
{
ssleep(4);
}
printk("copy_from_user data:%s.\r\n",kbuff);
return size;
}
// 设备文件操作集
struct file_operations misc_fops ={
.owner = THIS_MODULE,
.open = misc_open,
.release = misc_close,
.read = misc_read,
.write = misc_write
};
// 设备文件信息描述集
struct miscdevice misc_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &misc_fops
};
// 驱动的入口函数
static int __init misc_init(void)
{
// 申请杂项设备
int ret = 0;
ret = misc_register(&misc_dev);
printk("--------------------------------------------\r\n");
if(ret < 0)
{
printk("misc register is error.\r\n");
}
else
printk("misc register is seccussful.\r\n");
return ret;
}
//驱动的出口函数
static void __exit misc_exit(void)
{
// 注销杂项设备
misc_deregister(&misc_dev);
printk("misc_exit\r\n");
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("zxj");
在开发板上使用insmod + 驱动名.ko
后就可编写测试程序,并编译运行。
创建一个并发程序
测试程序的逻辑为,打开文件后先写入数据,然后读取刚才写入的数据并且打印在控制台上,详细的测试程序见下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
if(argc != 2)
{
printf("the number of your input is eror\n");
return -1;
}
char filePath[] = "/dev/threadtest";
int fd = open(filePath,O_RDWR);
if(fd < 0)
{
printf("opening is failed.\n");
return -1;
}
else
printf("opening is successful.\n");
// 写入数据
write(fd,argv[1],strlen(argv[1]));
char buff[10];
// 读出数据
read(fd,buff,sizeof(buff));
printf("buff:%s argv:%s\r\n",buff,argv[1]);
close(fd);
return 0;
}
将测试程序传送到开发板后,可先试用cp
命令拷贝一份文件,命令格式为cp 源文件 目的文件
。
然后再后台运行可执行文件,后台运行只需要在运行命令后加上一个&
符号即可。执行结果如下:
原子操作,是借用化学中元素组成最小单位为原子来命名的。顾名思义,原子操作是指最小的操作,该操作是不可再分的,也是不能够被中断打断的。
原子操作函数
在32位操作系统中定义一个原子操作变量可使用atomic_t
结构体,该结构体的具体定义见下方:
typedef struct {
int counter;
} atomic_t;
在64位操作系统中定义一个原子操作变量可使用atomic64_t
结构体,该结构体的具体定义见下方:
typedef struct {
long counter;
} atomic64_t;
在32位机器中,常见的原子操作函数如下:
函数 | 描述 |
---|---|
ATOMIC_INIT(int i) | 定义原子变量的时候初始化 |
int 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) | 自增 |
void atomic_dec(atomic_t *v) | 自减 |
int atomic_dec_return(atomic_t *v) | 自减,并返回 v 的值 |
int atomic_inc_return(atomic_t *v) | 自增,并返回 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_inc_and_test(atomic_t *v) | 给 v 加 1,如果结果为 0 就返回真,否则返回假 |
int atomic_add_negative(int i, atomic_t *v) | 给 v 加 i,如果结果为负就返回真,否则返回假 |
64位机器中,原子操作函数的作用与32位机器一样,不过函数名有些许差别,64位机器上操作需要加上一个数字64,如32位机器中的读取函数为atomic_read
,而64位机器中为atomic64_read
。
编写一个驱动,完成该驱动仅允许一个应用程序打开。
atomic64_t V = ATOMIC64_INIT(1)
open
中,借助原子变量判断该驱动是否已经打开。如果已经打开就先恢复原子变量的值,再返回打开设备失败。 // 判断是否已经有设备打开
if(!(atomic64_sub_and_test(1,&V)))
{
// 已经打开一个设备 本次打开失败
atomic64_add(1,&V); // 因为if判断中已经减1 因此此处的值就小于1 因此需要恢复
return -1;
}
// 恢复原子锁
atomic64_add(1,&V);
整个驱动的源码如下:
#include <linux/kernel.h>
#include <linux/init.h> //初始化头文件
#include <linux/module.h> //最基本的文件,支持动态添加和卸载模块。
#include <linux/miscdevice.h> //注册杂项设备头文件
#include <linux/fs.h> //注册设备节点的文件结构体
#include <linux/uaccess.h>
#include <linux/atomic.h>
#include <asm/atomic.h>
#include <asm/bitops.h>
#include <linux/sched.h>
/* 含原子操作驱动 */
atomic64_t V = ATOMIC64_INIT(1);
// 驱动名
#define DEVICE_NAME "atomictest"
// 数据buffer
static char kbuff[32] ;
// 打开设备
int misc_open(struct inode *inode,struct file*file)
{
// 判断是否已经有设备打开
if(!(atomic64_sub_and_test(1,&V)))
{
// 已经打开一个设备 本次打开失败
atomic64_add(1,&V); // 因为if判断中已经减1 因此此处的值就小于1 因此需要恢复
return -1;
}
printk("misc_open is ok.\r\n");
return 0;
}
// 关闭设备
int misc_close(struct inode * inode, struct file *file)
{
// 恢复原子锁
atomic64_add(1,&V);
printk("misc_close\r\n");
return 0;
}
// 读取设备中信息
ssize_t misc_read (struct file *file, char __user *buff, size_t size, loff_t *loff)
{
int len = strlen(kbuff);
if(copy_to_user(buff,kbuff,len) != 0) // 将内核中的数据给应用
{
printk("copy_to_user error\r\n");
return -1;
}
printk("copy_to_user is successful\r\n");
return len;
}
// 写入设备信息
ssize_t misc_write (struct file *file, const char __user *buff, size_t size, loff_t *loff)
{
int ret = copy_from_user(kbuff,buff,size);
if( ret != 0) // 从应用那儿获取数据
{
printk("ret of error is %d.\r\n",ret);
return ret;
}
printk("copy_from_user data:%s.\r\n",kbuff);
return size;
}
// 设备文件操作集
struct file_operations misc_fops ={
.owner = THIS_MODULE,
.open = misc_open,
.release = misc_close,
.read = misc_read,
.write = misc_write
};
// 设备文件信息描述集
struct miscdevice misc_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &misc_fops
};
// 驱动的入口函数
static int __init misc_init(void)
{
// 申请杂项设备
int ret = 0;
ret = misc_register(&misc_dev);
printk("--------------------------------------------\r\n");
if(ret < 0)
{
printk("misc register is error.\r\n");
}
else
printk("misc register is seccussful.\r\n");
return ret;
}
//驱动的出口函数
static void __exit misc_exit(void)
{
// 注销杂项设备
misc_deregister(&misc_dev);
printk("misc_exit\r\n");
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("zxj");
使用insmod
加载驱动后,直接写一个打开驱动的测试函数即可,由于测试函数过于简单,此处就不再编写。如果大家想要源码参考,可点击杂项设备的详细说明
测试结果如下(说明第一个failed与第二个successful相隔的时间远远大于驱动暂停的时间):
位原子操作函数
与原子整形变量不同,原子位操作没有 atomic_t 的数据结构,原子位操作是直接对内存进行操作,原子位操作相关 API 函数如下:
函数 | 描述 |
---|---|
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 位原来的值 |
具体使用较为简单,可设置一个变量保存需要的数据,然后再定义一个指针指向该变量,后续就可直接使用位原子操作函数间接操作该变量了。例如:
编写一个驱动,首先将位原子变量的第 1位置1,并打印数据;然后再将位原子操作的第 2位翻转,并打印数据;最后将位原子操作的第2位置0,并打印数据。
unsigned long
类型变量,并且定义一个同型指针,指向该地址。 // 保存数据
unsigned long data = 5;
// 定义指针 指向数据保存位置
unsigned long *p = &data;
//将p地址的第1个位置数据置1
set_bit(1,p);
printk("data1:%ld %ld\r\n",*p,data);
// 将p地址的第2为位置数据翻转
change_bit(2,p);
printk("data2:%ld %ld\r\n",*p,data);
//将 p 地址的第 nr 位置 0,并且返回 nr 位原来的值
ret = test_and_clear_bit(2,p);
printk("data3:%d %ld %ld\r\n",ret,*p,data);
完成驱动源码如下:
#include <linux/kernel.h>
#include <linux/init.h> //初始化头文件
#include <linux/module.h> //最基本的文件,支持动态添加和卸载模块。
#include <linux/miscdevice.h> //注册杂项设备头文件
#include <linux/fs.h> //注册设备节点的文件结构体
#include <linux/uaccess.h>
#include <linux/atomic.h>
#include <asm/atomic.h>
#include <asm/bitops.h>
#include <linux/sched.h>
// 驱动名
#define DEVICE_NAME "atomictest"
unsigned long data = 5;
unsigned long *p = &data;
// 设备文件操作集
struct file_operations misc_fops ={
.owner = THIS_MODULE,
};
// 设备文件信息描述集
struct miscdevice misc_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &misc_fops
};
// 驱动的入口函数
static int __init misc_init(void)
{
// 申请杂项设备
int ret = 0;
ret = misc_register(&misc_dev);
printk("--------------------------------------------\r\n");
if(ret < 0)
{
printk("misc register is error.\r\n");
}
else
printk("misc register is seccussful.\r\n");
//将p地址的第1个位置数据置1
set_bit(1,p);
printk("data1:%ld %ld\r\n",*p,data);
// 将p地址的第2为位置数据翻转
change_bit(2,p);
printk("data2:%ld %ld\r\n",*p,data);
//将 p 地址的第 nr 位置 0,并且返回 nr 位原来的值
ret = test_and_clear_bit(2,p);
printk("data3:%d %ld %ld\r\n",ret,*p,data);
return ret;
}
//驱动的出口函数
static void __exit misc_exit(void)
{
// 注销杂项设备
misc_deregister(&misc_dev);
printk("misc_exit\r\n");
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("zxj");
使用insmod
加载驱动后,就可直接查看执行结果:
咱还可以手动分析来校验位原子操作的正确性,检验过程如下: