Linux 併發與競爭實驗學習

Bathwind_W發表於2024-06-15

Linux 併發與競爭實驗學習

原子操作實驗

這裡原子操作就是採用原子變數來保護一個程式執行的完整過程,使用atomic 來實現一次只能允許一個應用訪問 LED,建立atomic.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.

atomic_set(&gpioled.lock, 1); /* 原子變數初始值為 1 */

然後open函式檢查原子變數的值,具體程式碼如下:

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。

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程式具體如下:

#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 函式。
具體驅動程式為:

#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和上面的保持一致即可。

訊號量實驗:

#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 燈使用權實驗現象如下:
在這裡插入圖片描述

互斥體實驗

互斥實驗類似,將訊號量對應的部分換為互斥體的部分即可,具體程式碼如下:

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 }

相關文章