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 }