Linux 并发与竞争实验学习

Linux 并发与竞争实验学习

原子操作实验

这里原子操作就是采用原子变量来保护一个程序运行的完整过程,使用atomic 来实现一次只能允许一个应用访问 LED,创建atomic.c文件,其实改动内容就是添加原子变量,

要在设备结构体数据添加原子变量,具体代码如下:

c 复制代码
struct gpioled_dev
{
    dev_t devid;            /* 设备号 */
    struct cdev cdev;       /* cdev */
    struct class *class;    /* 类 */
    struct device *device;  /* 设备 */
    int major;              /* 主设备号 */
    int minor;              /* 次设备号 */
    struct device_node *nd; /* 设备节点 */
    int led_gpio;           /* led 所使用的 GPIO 编号 */
    atomic_t lock;          /* 原子变量 */
};

首先是这个函数led_init

这个函数要先初始化原子变量,以便于首次运行APP检查原子变量不出错。这里是初始化为1.

c 复制代码
atomic_set(&gpioled.lock, 1); /* 原子变量初始值为 1 */

然后open函数检查原子变量的值,具体代码如下:

c 复制代码
if (!atomic_dec_and_test(&gpioled.lock))
{
    atomic_inc(&gpioled.lock); /* 小于 0 的话就加 1,使其原子变量等于 0 */
    return -EBUSY;             /* LED 被使用,返回忙 */
}

每次打开驱动设备的时候先使用 atomic_dec_and_test 函数将 lock 减 1,如果 atomic_dec_and_test函数返回值为真就表示 lock 当前值为 0,说明设备可以使用。如果 atomic_dec_and_test 函数返回值为假,就表示 lock 当前值为负数(lock 值默认是 1), lock 值为负数的可能性只有一个,那就是其他设备正在使用 LED。其他设备正在使用 LED 灯,那么就只能退出了,在退出之前调用函数 atomic_inc 将 lock 加 1,因为此时 lock 的值被减成了负数,必须要对其加 1,将 lock 的值变为 0。

c 复制代码
static int led_release(struct inode *inode, struct file *filp)
{
    struct gpioled_dev *dev = filp->private_data;
    /* 关闭驱动文件的时候释放原子变量 */
    atomic_inc(&dev->lock);
    return 0;
}

然后还要模拟占用LED25秒,atomicApp.c程序具体如下:

c 复制代码
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

#define LEDOFF 0
#define LEDON 1
int main(int argc, char *argv[])
{
    int fd, retvalue;
    char *filename;
    unsigned char cnt = 0;
    unsigned char databuf[1];
    if (argc != 3)
    {
        printf("Error Usage!\r\n");
        return -1;
    }
    filename = argv[1];
    fd = open(filename, O_RDWR);
    if (fd < 0)
    {
        printf("file %s open failed!\r\n", argv[1]);
        return -1;
    }
    databuf[0] = atoi(argv[2]); /* 要执行的操作:打开或关闭 */
    /* 向/dev/gpioled 文件写入数据 */
    retvalue = write(fd, databuf, sizeof(databuf));
    if (retvalue < 0)
    {
        printf("LED Control Failed!\r\n");
        close(fd);
        return -1;
    }
    while (1)
    {
        sleep(5);
        cnt++;
        printf("App running times:%d\r\n", cnt);
        if (cnt >= 5)
            break;
    }
    printf("App running finished!");
    retvalue = close(fd); /* 关闭文件 */
    if (retvalue < 0)
    {
        printf("file %s close failed!\r\n", argv[1]);
        return -1;
    }
    return 0;
}

测试 APP 在获取到 LED 灯驱动的使用权以后会使用 25S。编译程序,通过网络挂载,测试原子操作。

出现如图则证明成功。

自旋锁实验

①、自旋锁保护的临界区要尽可能的短,因此在 open 函数中申请自旋锁,然后在 release 函

数中释放自旋锁的方法就不可取。我们可以使用一个变量来表示设备的使用情况,如果设备被

使用了那么变量就加一,设备被释放以后变量就减 1,我们只需要使用自旋锁保护这个变量即

可。

②、考虑驱动的兼容性,合理的选择 API 函数。

具体驱动程序为:

c 复制代码
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define GPIOLED_CNT 1          /* 设备号个数 */
#define GPIOLED_NAME "gpioled" /* 名字 */
#define LEDOFF 0               /* 关灯 */
#define LEDON 1                /* 开灯 */
                               /* gpioled 设备结构体 */
struct gpioled_dev
{
    dev_t devid;            /* 设备号 */
    struct cdev cdev;       /* cdev */
    struct class *class;    /* 类 */
    struct device *device;  /* 设备 */
    int major;              /* 主设备号 */
    int minor;              /* 次设备号 */
    struct device_node *nd; /* 设备节点 */
    int led_gpio;           /* led 所使用的 GPIO 编号 */
    int dev_stats;          /* 设备状态, 0,设备未使用;>0,设备已经被使用 */
    spinlock_t lock;        /* 自旋锁变量 */
};
struct gpioled_dev gpioled; /* led 设备 */
static int led_open(struct inode *inode, struct file *filp)
{
    unsigned long flags;
    filp->private_data = &gpioled;           /* 设置私有数据 */
    spin_lock_irqsave(&gpioled.lock, flags); /* 上锁 */
    if (gpioled.dev_stats)
    {                                                 /* 如果设备被使用了 */
        spin_unlock_irqrestore(&gpioled.lock, flags); /* 解锁 */
        return -EBUSY;
    }
    gpioled.dev_stats++;                          /* 如果设备没有打开,那么就标记已经打开了 */
    spin_unlock_irqrestore(&gpioled.lock, flags); /*解锁*/
    return 0;
}
/*
 * @description : 从设备读取数据
 * @param -- filp : 要打开的设备文件(文件描述符)
 * @param - buf : 返回给用户空间的数据缓冲区
 * @param - cnt : 要读取的数据长度
 * @param -- offt : 相对于文件首地址的偏移
 * @return : 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    return 0;
}
/*
73 * @description : 向设备写数据
74 * @param - filp : 设备文件,表示打开的文件描述符
75 * @param - buf : 要写给设备写入的数据
76 * @param - cnt : 要写入的数据长度
77 * @param -- offt : 相对于文件首地址的偏移
78 * @return : 写入的字节数,如果为负值,表示写入失败
79 */
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue;
    unsigned char databuf[1];
    unsigned char ledstat;
    struct gpioled_dev *dev = filp->private_data;
    retvalue = copy_from_user(databuf, buf, cnt);
    if (retvalue < 0)
    {
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }
    ledstat = databuf[0]; /* 获取状态值 */

    if (ledstat == LEDON)
    {
        gpio_set_value(dev->led_gpio, 0); /* 打开 LED 灯 */
    }
    else if (ledstat == LEDOFF)
    {
        gpio_set_value(dev->led_gpio, 1); /* 关闭 LED 灯 */
    }
    return 0;
}
/*
104 * @description : 关闭/释放设备
105 * @param -- filp : 要关闭的设备文件(文件描述符)
106 * @return : 0 成功;其他 失败
*/
static int led_release(struct inode *inode, struct file *filp)
{
    unsigned long flags;
    struct gpioled_dev *dev = filp->private_data;
    /* 关闭驱动文件的时候将 dev_stats 减 1 */
    spin_lock_irqsave(&dev->lock, flags); /* 上锁 */
    if (dev->dev_stats)
    {
        dev->dev_stats--;
    }
    spin_unlock_irqrestore(&dev->lock, flags); /* 解锁 */
    return 0;
}
/* 设备操作函数 */
static struct file_operations gpioled_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .read = led_read,
    .write = led_write,
    .release = led_release,
};
static int __init led_init(void)
{
    int ret = 0;
    spin_lock_init(&gpioled.lock);
    /* 设置 LED 所使用的 GPIO */
    /* 1、获取设备节点: gpioled */
    gpioled.nd = of_find_node_by_path("/gpioled");
    if (gpioled.nd == NULL)
    {
        printk("gpioled node cant not found!\r\n");
        return -EINVAL;
    }
    else
    {
        printk("gpioled node has been found!\r\n");
    }

    /* 2、 获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */
    gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
    if (gpioled.led_gpio < 0)
    {
        printk("can't get led-gpio");
        return -EINVAL;
    }
    printk("led-gpio num = %d\r\n", gpioled.led_gpio);
    ret = gpio_direction_output(gpioled.led_gpio, 1);
    if (ret < 0)
    {
        printk("can't set gpio!\r\n");
    }
    /*1、创建设备号*/
    if (gpioled.major)
    {
        gpioled.devid = MKDEV(gpioled.major, 0);
        register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
    }
    else
    {
        alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);
        gpioled.major = MAJOR(gpioled.devid);
        gpioled.minor = MINOR(gpioled.devid);
    }
    printk("newcheled major: %d minor: %d", gpioled.major, gpioled.minor);
    gpioled.cdev.owner = THIS_MODULE;
    cdev_init(&gpioled.cdev, &gpioled_fops);
    cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
    gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
    if (IS_ERR(gpioled.class))
    {
        return PTR_ERR(gpioled.class);
    }
    gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
    if (IS_ERR(gpioled.device))
    {
        return PTR_ERR(gpioled.device);
    }
    return 0;
}
static void __exit led_exit(void)
{

    /* 注销字符设备驱动 */

    cdev_del(&gpioled.cdev); /* 删除 cdev */
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);

    device_destroy(gpioled.class, gpioled.devid);
    class_destroy(gpioled.class);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wyw");

测试App和上面的保持一致即可。

信号量实验:

c 复制代码
#include <linux/semaphore.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define GPIOLED_CNT 1          /* 设备号个数 */
#define GPIOLED_NAME "gpioled" /* 名字 */
#define LEDOFF 0               /* 关灯 */
#define LEDON 1                /* 开灯 */
                               /* gpioled 设备结构体 */
struct gpioled_dev
{
    dev_t devid;            /* 设备号 */
    struct cdev cdev;       /* cdev */
    struct class *class;    /* 类 */
    struct device *device;  /* 设备 */
    int major;              /* 主设备号 */
    int minor;              /* 次设备号 */
    struct device_node *nd; /* 设备节点 */
    int led_gpio;           /* led 所使用的 GPIO 编号 */
    struct semaphore sem;   /* 信号量 */
};
struct gpioled_dev gpioled; /* led 设备 */
static int led_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &gpioled; /* 设置私有数据 */
                                   /* 获取信号量,进入休眠状态的进程可以被信号打断 */
    if (down_interruptible(&gpioled.sem))
    {
        return -ERESTARTSYS;
    }
#if 0
 down(&gpioled.sem); /* 不能被信号打断 */
#endif
    return 0;
}
/*
 * @description : 从设备读取数据
 * @param -- filp : 要打开的设备文件(文件描述符)
 * @param - buf : 返回给用户空间的数据缓冲区
 * @param - cnt : 要读取的数据长度
 * @param -- offt : 相对于文件首地址的偏移
 * @return : 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    return 0;
}
/*
73 * @description : 向设备写数据
74 * @param - filp : 设备文件,表示打开的文件描述符
75 * @param - buf : 要写给设备写入的数据
76 * @param - cnt : 要写入的数据长度
77 * @param -- offt : 相对于文件首地址的偏移
78 * @return : 写入的字节数,如果为负值,表示写入失败
79 */
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue;
    unsigned char databuf[1];
    unsigned char ledstat;
    struct gpioled_dev *dev = filp->private_data;
    retvalue = copy_from_user(databuf, buf, cnt);
    if (retvalue < 0)
    {
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }
    ledstat = databuf[0]; /* 获取状态值 */

    if (ledstat == LEDON)
    {
        gpio_set_value(dev->led_gpio, 0); /* 打开 LED 灯 */
    }
    else if (ledstat == LEDOFF)
    {
        gpio_set_value(dev->led_gpio, 1); /* 关闭 LED 灯 */
    }
    return 0;
}
/*
104 * @description : 关闭/释放设备
105 * @param -- filp : 要关闭的设备文件(文件描述符)
106 * @return : 0 成功;其他 失败
*/
static int led_release(struct inode *inode, struct file *filp)
{
    struct gpioled_dev *dev = filp->private_data;
    /* 关闭驱动文件的时候释放原子变量 */
    up(&dev->sem); /* 释放信号量,信号量值加 1 */
    return 0;
}
/* 设备操作函数 */
static struct file_operations gpioled_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .read = led_read,
    .write = led_write,
    .release = led_release,
};
static int __init led_init(void)
{
    int ret = 0;
    sema_init(&gpioled.sem, 1);
    /* 设置 LED 所使用的 GPIO */
    /* 1、获取设备节点: gpioled */
    gpioled.nd = of_find_node_by_path("/gpioled");
    if (gpioled.nd == NULL)
    {
        printk("gpioled node cant not found!\r\n");
        return -EINVAL;
    }
    else
    {
        printk("gpioled node has been found!\r\n");
    }

    /* 2、 获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */
    gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
    if (gpioled.led_gpio < 0)
    {
        printk("can't get led-gpio");
        return -EINVAL;
    }
    printk("led-gpio num = %d\r\n", gpioled.led_gpio);
    ret = gpio_direction_output(gpioled.led_gpio, 1);
    if (ret < 0)
    {
        printk("can't set gpio!\r\n");
    }
    /*1、创建设备号*/
    if (gpioled.major)
    {
        gpioled.devid = MKDEV(gpioled.major, 0);
        register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
    }
    else
    {
        alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);
        gpioled.major = MAJOR(gpioled.devid);
        gpioled.minor = MINOR(gpioled.devid);
    }
    printk("newcheled major: %d minor: %d", gpioled.major, gpioled.minor);
    gpioled.cdev.owner = THIS_MODULE;
    cdev_init(&gpioled.cdev, &gpioled_fops);
    cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
    gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
    if (IS_ERR(gpioled.class))
    {
        return PTR_ERR(gpioled.class);
    }
    gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
    if (IS_ERR(gpioled.device))
    {
        return PTR_ERR(gpioled.device);
    }
    return 0;
}
static void __exit led_exit(void)
{

    /* 注销字符设备驱动 */

    cdev_del(&gpioled.cdev); /* 删除 cdev */
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);

    device_destroy(gpioled.class, gpioled.devid);
    class_destroy(gpioled.class);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wyw");

具体代码如上所示。open函数中申请信号量,可以使用down函数,也可以使用down_interruptible函数。如果信号量值大于等于 1 就表示可用,那么应用程序就会开始使用 LED 灯。如果信号量值为 0 就表示应用程序不能使用 LED 灯,此时应用程序就会进入到休眠状态。等到信号量值大于 1 的时候应用程序就会唤醒,申请信号量,获取 LED 灯使用权实验现象如下:

互斥体实验

互斥实验类似,将信号量对应的部分换为互斥体的部分即可,具体代码如下:

c 复制代码
55 static int led_open(struct inode *inode, struct file *filp)
56 {
57 filp->private_data = &gpioled; /* 设置私有数据 */
58
59 /* 获取互斥体,可以被信号打断 */
60 if (mutex_lock_interruptible(&gpioled.lock)) {
61 return -ERESTARTSYS;
62 }
63 #if 0
64 mutex_lock(&gpioled.lock); /* 不能被信号打断 */
65 #endif
66	return 0;
67	}

119 static int led_release(struct inode *inode, struct file *filp)
120 {
121 struct gpioled_dev *dev = filp->private_data;
122
123 /* 释放互斥锁 */
124 mutex_unlock(&dev->lock);
125
126 return 0;
127 }
143 static int __init led_init(void)
144 {
145 int ret = 0;
146
147 /* 初始化互斥体 */
148 mutex_init(&gpioled.lock);
......
205 return 0;
206 }
相关推荐
w***15319 分钟前
ubuntu 安装 Redis
linux·redis·ubuntu
AA陈超42 分钟前
Lyra学习004:GameFeatureData分析
c++·笔记·学习·ue5·虚幻引擎
liweiweili1261 小时前
Linux 中替换某个目录下所有文件中的特定字符串
linux·运维·服务器
弓弧名家_玄真君1 小时前
Ubuntu 20.04.3 LTS 安装vnc (Xfce4 + Xvfb)
linux·运维·ubuntu
zkl_zkl_2 小时前
地理信息系统学习笔记——第六章 空间数据采集与处理
笔记·学习·数据处理·数据质量·空间数据
光头程序员2 小时前
学习笔记——主攻 vite
笔记·学习
零匠学堂20252 小时前
移动学习系统,如何提升企业培训效果?
java·开发语言·spring boot·学习·音视频
唐·柯里昂7982 小时前
野火鲁班猫5使用正点原子 RTL8188EUS Wifi模块驱动移植(Linux5.10 Debian系统) 解决zsh报错
linux·c语言·mcu·物联网·ubuntu·硬件工程·软件构建
源梦想2 小时前
机甲恐龙动作冒险网页小游戏Linux部署教程
linux·运维·服务器
TL滕2 小时前
从0开始学算法——第四天(题目参考答案)
数据结构·笔记·python·学习·算法