以T10案例代碼爲例,咱們在內核中申請了一片緩衝區,假設此刻有2個及以上進程同時來訪問這個內核驅動,並且兩者同時執行到了下述代碼區,那麼此刻內核將會拷貝2個用戶空間的數據到一個緩衝區,假設進程A恰好拷貝了一半數據到內核buffer後進程B也拷貝部分數據給內核buffer,那麼將會致使內核中的buffer內數據變得不可控。node
ret = copy_from_user(kbuf, buf, write_bytes);
上述過程其實就是屬於一種競態(race condition),競態會致使對共享內存的非控制訪問,最終可能致使內存泄漏甚至系統崩潰等災難性的結果。linux
/kernel/arch/arm/include/asm/atomic.h
下/* 功能:設置原子的值 */ static inline void atomic_set(atomic_t *v, int i); /* 定義原子變量並初始化爲0 */ atomic_t value = ATOMIC_INIT(0); /* 獲取原子變量的值 */ #define atomic_read(v) ((v)->counter) /* 原子變量加減 */ #define atomic_add(i, v) (void) atomic_add_return(i, v) #define atomic_inc(v) (void) atomic_add_return(1, v) #define atomic_sub(i, v) (void) atomic_sub_return(i, v) #define atomic_dec(v) (void) atomic_sub_return(1, v)
atomic_dec_and_test
函數會返回0,仍表示驅動被佔用,那麼驅動將會返回一個忙的信號#include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/device.h> #include <asm/uaccess.h> #include <linux/slab.h> #include <linux/miscdevice.h> #include <asm/atomic.h> #include "cdev_test_v4.h" #define BUF_SIZR 1024 #define MAJOR_NUM 168 #define MINOR_NUM 0 struct demo_cdev { char *buffer; //my private memory int value; struct miscdevice *mdev; }; /* step1: malloc memory for char device */ struct demo_cdev *demo_dev; static atomic_t demo_available = ATOMIC_INIT(1); static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos) { int ret = -1; int read_bytes; struct demo_cdev *demo = file->private_data; char *kbuf = demo->buffer + *pos; printk(KERN_INFO "Enter: %s\n", __func__); /* determine whether user has finished reading data */ if (*pos >= BUF_SIZR) { return 0; } if (size > (BUF_SIZR - *pos)) { read_bytes = BUF_SIZR - *pos; } else { read_bytes = size; } ret = copy_to_user(buf, kbuf, read_bytes); if (ret != 0) { return -EFAULT; } *pos += read_bytes; return read_bytes; } static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos) { int ret; int write_bytes; struct demo_cdev *demo = file->private_data; char *kbuf = demo->buffer + *pos; printk(KERN_INFO "Enter: %s\n", __func__); if (*pos >= BUF_SIZR) { return 0; } if (size > (BUF_SIZR - *pos)) { write_bytes = BUF_SIZR - *pos; } else { write_bytes = size; } ret = copy_from_user(kbuf, buf, write_bytes); if (ret != 0) { return -EFAULT; } *pos += write_bytes; return write_bytes; } static int demo_open(struct inode *node, struct file *file) { printk(KERN_INFO "Enter: %s\n", __func__); if (!atomic_dec_and_test(&demo_available)) { printk(KERN_ERR "file already open\n"); /* 別忘了將信號量加回來 */ atomic_inc(&demo_available); return -EBUSY; } file->private_data = (void *)demo_dev; return 0; } static int demo_release(struct inode *node, struct file *file) { printk(KERN_INFO "Enter: %s\n", __func__); atomic_inc(&demo_available); return 0; } static long demo_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int ret = 0; struct demo_cdev *demo = file->private_data; switch(cmd) { case CDEV_TEST_V4_CLEAN: memset(demo->buffer, 0x00, BUF_SIZR); printk(KERN_INFO "cmd: clean\n"); break; case CDEV_TEST_V4_GETVAL: put_user(demo->value, (int *)arg); printk(KERN_INFO "cmd: getval\n"); break; case CDEV_TEST_V4_SETVAL: demo->value = (int)arg; printk(KERN_INFO "cmd: setval\n"); break; default: break; } return (long)ret; } static struct file_operations demo_operation= { .open = demo_open, .release = demo_release, .read = demo_read, .write = demo_write, .unlocked_ioctl = demo_ioctl, }; static struct miscdevice misc_struct = { .minor = MISC_DYNAMIC_MINOR, .name = "misc_dev", .fops = &demo_operation, }; static int __init demo_init(void) { int ret = -1; printk(KERN_INFO "Enter: %s\n", __func__); demo_dev = (struct demo_cdev *)kmalloc(sizeof(struct demo_cdev), GFP_KERNEL); if (!demo_dev) { printk(KERN_ERR "failed to malloc demo_dev\n"); ret = -ENOMEM; goto ERROR_MALLOC_DEMODEV; } demo_dev->value = 1; demo_dev->buffer = (char *)kmalloc(BUF_SIZR, GFP_KERNEL); if (!demo_dev->buffer) { printk(KERN_ERR "malloc %d bytes failed\n", BUF_SIZR); ret = -ENOMEM; goto ERROR_MALLOC_BUFFER; } memset(demo_dev->buffer, 0x00, BUF_SIZR); demo_dev->mdev = &misc_struct; ret = misc_register(demo_dev->mdev); if (ret < 0) { printk(KERN_ERR "failed to register misc\n"); goto ERROR_MISC; } printk(KERN_INFO "demo char device init done\n"); return 0; ERROR_MISC: kfree(demo_dev->buffer); demo_dev->buffer = NULL; ERROR_MALLOC_BUFFER: kfree(demo_dev); demo_dev = NULL; ERROR_MALLOC_DEMODEV: return ret; } static void __exit demo_exit(void) { printk(KERN_INFO "Enter: %s\n", __func__); misc_deregister(demo_dev->mdev); kfree(demo_dev->buffer); demo_dev->buffer = NULL; kfree(demo_dev); demo_dev = NULL; printk(KERN_INFO "demo char device exit done\n"); } module_init(demo_init); module_exit(demo_exit); MODULE_AUTHOR("Qi Han"); MODULE_LICENSE("GPL");
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/ioctl.h> #include <fcntl.h> #include "cdev_test_v4.h" #define DEVICE_PATH "/dev/misc_dev" int main(void) { int fd; int ret; printf("opening...\n"); fd = open(DEVICE_PATH, O_RDWR); if (fd < 0) { printf("failed to open\n"); return -1; } printf("sleeping...\n"); sleep(7); printf("closing\n"); close(fd); return 0; }
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo insmod atomic.ko [sudo] hq 的密碼: hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo chmod 666 /dev/misc_dev hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test opening... sleeping... closing hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test & [1] 7064 hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ opening... sleeping... hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test opening... failed to open hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test opening... failed to open hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test opening... failed to open hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test opening... failed to open hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ closing [1]+ 已完成 ./test hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$
自旋鎖(spin_lock)是一種典型的對臨界資源進行互斥訪問的手段,顧名思義,爲了得到一個自旋鎖,在某個CPU上運行的代碼要先執行一個原子操做,該操做測試並設置某個內存變量,在操做完成以前其餘執行單元不能訪問到這個內存變量。api
若是測試結果代表鎖已經空閒,則該程序將會得到這個自旋鎖並繼續執行;若是測試代表鎖被佔用,程序將會在一個小的循環內重複這個「測試並設置」的操做,即「自旋」的動做,即原地打轉,當自旋鎖持有者經過重置該變量釋放這個自旋鎖以後,某個等待的「測試並設置」操做向其調用者報告鎖已經被釋放。bash
/* 定義自旋鎖 */ spinlock_t lock; /* 動態初始化自旋鎖 */ spin_lock_init(&lock); /* 得到自旋鎖,若是可以馬上得到鎖就立刻返回,不然它將自旋在那裏直到鎖被釋放 */ spin_lock(lock); /* 嘗試得到自旋鎖,若是可以馬上得到鎖就返回真,不然馬上返回假,即退出原地打轉的狀態 */ spin_trylock(lock); /* 釋放自旋鎖,與lock和trylock配合使用 */ spin_unlock(lock);
spinlock_t lock; spin_lock_init(lock); spin_lock(lock); ... //臨界區 spin_unlock(lock);
#include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/device.h> #include <asm/uaccess.h> #include <linux/slab.h> #include <linux/miscdevice.h> #include <asm/atomic.h> #include <linux/spinlock.h> #include "cdev_test_v4.h" #define BUF_SIZR 1024 #define MAJOR_NUM 168 #define MINOR_NUM 0 struct demo_cdev { char *buffer; //my private memory int value; struct miscdevice *mdev; spinlock_t lock; int open_count; //要維護的變量值 }; /* step1: malloc memory for char device */ struct demo_cdev *demo_dev; static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos) { int ret = -1; int read_bytes; struct demo_cdev *demo = file->private_data; char *kbuf = demo->buffer + *pos; printk(KERN_INFO "Enter: %s\n", __func__); /* determine whether user has finished reading data */ if (*pos >= BUF_SIZR) { return 0; } if (size > (BUF_SIZR - *pos)) { read_bytes = BUF_SIZR - *pos; } else { read_bytes = size; } ret = copy_to_user(buf, kbuf, read_bytes); if (ret != 0) { return -EFAULT; } *pos += read_bytes; return read_bytes; } static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos) { int ret; int write_bytes; struct demo_cdev *demo = file->private_data; char *kbuf = demo->buffer + *pos; printk(KERN_INFO "Enter: %s\n", __func__); if (*pos >= BUF_SIZR) { return 0; } if (size > (BUF_SIZR - *pos)) { write_bytes = BUF_SIZR - *pos; } else { write_bytes = size; } ret = copy_from_user(kbuf, buf, write_bytes); if (ret != 0) { return -EFAULT; } *pos += write_bytes; return write_bytes; } static int demo_open(struct inode *node, struct file *file) { printk(KERN_INFO "Enter: %s\n", __func__); spin_lock(&demo_dev->lock); if (demo_dev->open_count) { printk(KERN_ERR "already open\n"); /* 退出的時候須要解鎖,否則會死鎖 */ spin_unlock(&demo_dev->lock); return -EBUSY; } demo_dev->open_count ++; spin_unlock(&demo_dev->lock); printk(KERN_INFO "open\n"); file->private_data = (void *)demo_dev; return 0; } static int demo_release(struct inode *node, struct file *file) { struct demo_cdev *demo = (struct demo_cdev *)file->private_data; printk(KERN_INFO "Enter: %s\n", __func__); spin_lock(&demo->lock); demo->open_count --; spin_unlock(&demo->lock); return 0; } static long demo_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int ret = 0; struct demo_cdev *demo = file->private_data; switch(cmd) { case CDEV_TEST_V4_CLEAN: memset(demo->buffer, 0x00, BUF_SIZR); printk(KERN_INFO "cmd: clean\n"); break; case CDEV_TEST_V4_GETVAL: put_user(demo->value, (int *)arg); printk(KERN_INFO "cmd: getval\n"); break; case CDEV_TEST_V4_SETVAL: demo->value = (int)arg; printk(KERN_INFO "cmd: setval\n"); break; default: break; } return (long)ret; } static struct file_operations demo_operation= { .open = demo_open, .release = demo_release, .read = demo_read, .write = demo_write, .unlocked_ioctl = demo_ioctl, }; static struct miscdevice misc_struct = { .minor = MISC_DYNAMIC_MINOR, .name = "misc_dev", .fops = &demo_operation, }; static int __init demo_init(void) { int ret = -1; printk(KERN_INFO "Enter: %s\n", __func__); demo_dev = (struct demo_cdev *)kmalloc(sizeof(struct demo_cdev), GFP_KERNEL); if (!demo_dev) { printk(KERN_ERR "failed to malloc demo_dev\n"); ret = -ENOMEM; goto ERROR_MALLOC_DEMODEV; } demo_dev->value = 1; demo_dev->buffer = (char *)kmalloc(BUF_SIZR, GFP_KERNEL); if (!demo_dev->buffer) { printk(KERN_ERR "malloc %d bytes failed\n", BUF_SIZR); ret = -ENOMEM; goto ERROR_MALLOC_BUFFER; } memset(demo_dev->buffer, 0x00, BUF_SIZR); spin_lock_init(&demo_dev->lock); demo_dev->mdev = &misc_struct; demo_dev->open_count = 0; ret = misc_register(demo_dev->mdev); if (ret < 0) { printk(KERN_ERR "failed to register misc\n"); goto ERROR_MISC; } printk(KERN_INFO "demo char device init done\n"); return 0; ERROR_MISC: kfree(demo_dev->buffer); demo_dev->buffer = NULL; ERROR_MALLOC_BUFFER: kfree(demo_dev); demo_dev = NULL; ERROR_MALLOC_DEMODEV: return ret; } static void __exit demo_exit(void) { printk(KERN_INFO "Enter: %s\n", __func__); misc_deregister(demo_dev->mdev); kfree(demo_dev->buffer); demo_dev->buffer = NULL; kfree(demo_dev); demo_dev = NULL; printk(KERN_INFO "demo char device exit done\n"); } module_init(demo_init); module_exit(demo_exit); MODULE_AUTHOR("Qi Han"); MODULE_LICENSE("GPL");
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/ioctl.h> #include <fcntl.h> #include "cdev_test_v4.h" #define DEVICE_PATH "/dev/misc_dev" int main(void) { int fd; int ret; printf("opening...\n"); fd = open(DEVICE_PATH, O_RDWR); if (fd < 0) { printf("failed to open\n"); return -1; } printf("sleeping...\n"); sleep(7); printf("closing\n"); close(fd); return 0; }
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo insmod spin_lock.ko hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo chmod 666 /dev/misc_dev hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test opening... sleeping... closing hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test & [1] 8647 hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ opening... sleeping... hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/misc_dev cat: /dev/misc_dev: 設備或資源忙 hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ closing [1]+ 已完成 ./test hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ dmesg [18503.722177] open [18505.514911] Enter: demo_open [18505.514912] already open [18513.726177] Enter: demo_release
copy_from_user()
、copy_to_user()
、kmalloc
、msleep
等函數,可能致使系統崩潰。由於當進程調度到其餘設備的時候可能不會執行解鎖操做,進而形成死鎖信號量(semaphore)是用於保護臨界區的一種經常使用方法,它的用法與自旋鎖相似,可是,與自旋鎖不同的是,當獲取不到信號量時,進程不會原地打轉,而是進入休眠狀態。架構
/* 定義信號量 */ struct semaphore sem; /* 初始化信號量,該函數初始化信號量並設置信號量sem的值爲val,儘管信號量能夠被初始化爲大於1的值進而成爲計數信號量,可是一般不建議這麼作??? */ void sema_init(struct semaphore *sem, int val); /* 初始化一個互斥的信號量並把sem的值設爲1 */ #define init_MUTEX(sem) sema_init(sem, 1) DELCEAR_MUTEX(name) /* 初始化一個信號量並把sem的值設爲0 */ #define init_MUTEX_LOCKED(sem) sema_init(sem, 0) DELCEAR_MUTEX_LOCKED(name) /* 獲取信號量,該函數用於獲取信號量sem,他會致使睡眠,所以不能再中斷上下文中使用,由於中斷要求快進快出,假設在中斷裏面執行down函數,那麼進程將會在中斷裏面休眠,進而使得CPU卡死在中斷裏面,沒法跳出(中斷優先級高,不可被打斷) */ void down(struct semaphore *sem); /* 獲取信號量,由於down進入睡眠狀態的進程不能被信號打斷,可是由於down_interrruptible而進入睡眠狀態的進程能被信號打斷,信號也會致使該函數返回,返回非0。使用該函數的時候須要對返回值進行檢查,若是爲非0,一般馬上返回-ERESTARTSYS */ void down_interruptible(struct semaphore *sem); /* 嘗試獲取信號量sem,若是能馬上得到就返回0,不然返回非0,不會致使調用者睡眠,能夠在中斷上下文中使用 */ int down_trylock(struct semaphore *sem); /* 釋放信號量,喚醒調用者 */ void up(struct semaphore *sem);
#include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/device.h> #include <asm/uaccess.h> #include <linux/slab.h> #include <linux/miscdevice.h> #include <linux/semaphore.h> #include "cdev_test_v4.h" #define BUF_SIZR 1024 #define MAJOR_NUM 168 #define MINOR_NUM 0 struct demo_cdev { char *buffer; //my private memory int value; struct miscdevice *mdev; struct semaphore lock; }; /* step1: malloc memory for char device */ struct demo_cdev *demo_dev; static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos) { int ret = -1; int read_bytes; struct demo_cdev *demo = file->private_data; char *kbuf = demo->buffer + *pos; printk(KERN_INFO "Enter: %s\n", __func__); /* determine whether user has finished reading data */ if (*pos >= BUF_SIZR) { return 0; } if (size > (BUF_SIZR - *pos)) { read_bytes = BUF_SIZR - *pos; } else { read_bytes = size; } ret = copy_to_user(buf, kbuf, read_bytes); if (ret != 0) { return -EFAULT; } *pos += read_bytes; return read_bytes; } static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos) { int ret; int write_bytes; struct demo_cdev *demo = file->private_data; char *kbuf = demo->buffer + *pos; printk(KERN_INFO "Enter: %s\n", __func__); if (*pos >= BUF_SIZR) { return 0; } if (size > (BUF_SIZR - *pos)) { write_bytes = BUF_SIZR - *pos; } else { write_bytes = size; } ret = copy_from_user(kbuf, buf, write_bytes); if (ret != 0) { return -EFAULT; } *pos += write_bytes; return write_bytes; } static int demo_open(struct inode *node, struct file *file) { printk(KERN_INFO "Enter: %s\n", __func__); if (down_trylock(&demo_dev->lock)) { printk(KERN_INFO "already open\n"); return -EBUSY; } file->private_data = (void *)demo_dev; return 0; } static int demo_release(struct inode *node, struct file *file) { struct demo_cdev *demo = (struct demo_cdev*)(file->private_data); printk(KERN_INFO "Enter: %s\n", __func__); up(&demo->lock); return 0; } static long demo_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int ret = 0; struct demo_cdev *demo = file->private_data; switch(cmd) { case CDEV_TEST_V4_CLEAN: memset(demo->buffer, 0x00, BUF_SIZR); printk(KERN_INFO "cmd: clean\n"); break; case CDEV_TEST_V4_GETVAL: put_user(demo->value, (int *)arg); printk(KERN_INFO "cmd: getval\n"); break; case CDEV_TEST_V4_SETVAL: demo->value = (int)arg; printk(KERN_INFO "cmd: setval\n"); break; default: break; } return (long)ret; } static struct file_operations demo_operation= { .open = demo_open, .release = demo_release, .read = demo_read, .write = demo_write, .unlocked_ioctl = demo_ioctl, }; static struct miscdevice misc_struct = { .minor = MISC_DYNAMIC_MINOR, .name = "misc_dev", .fops = &demo_operation, }; static int __init demo_init(void) { int ret = -1; printk(KERN_INFO "Enter: %s\n", __func__); demo_dev = (struct demo_cdev *)kmalloc(sizeof(struct demo_cdev), GFP_KERNEL); if (!demo_dev) { printk(KERN_ERR "failed to malloc demo_dev\n"); ret = -ENOMEM; goto ERROR_MALLOC_DEMODEV; } demo_dev->value = 1; demo_dev->buffer = (char *)kmalloc(BUF_SIZR, GFP_KERNEL); if (!demo_dev->buffer) { printk(KERN_ERR "malloc %d bytes failed\n", BUF_SIZR); ret = -ENOMEM; goto ERROR_MALLOC_BUFFER; } memset(demo_dev->buffer, 0x00, BUF_SIZR); /* init for semaphore lock */ sema_init(&demo_dev->lock, 1); demo_dev->mdev = &misc_struct; ret = misc_register(demo_dev->mdev); if (ret < 0) { printk(KERN_ERR "failed to register misc\n"); goto ERROR_MISC; } printk(KERN_INFO "demo char device init done\n"); return 0; ERROR_MISC: kfree(demo_dev->buffer); demo_dev->buffer = NULL; ERROR_MALLOC_BUFFER: kfree(demo_dev); demo_dev = NULL; ERROR_MALLOC_DEMODEV: return ret; } static void __exit demo_exit(void) { printk(KERN_INFO "Enter: %s\n", __func__); misc_deregister(demo_dev->mdev); kfree(demo_dev->buffer); demo_dev->buffer = NULL; kfree(demo_dev); demo_dev = NULL; printk(KERN_INFO "demo char device exit done\n"); } module_init(demo_init); module_exit(demo_exit); MODULE_AUTHOR("Qi Han"); MODULE_LICENSE("GPL");
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo insmod semaphore.ko hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo chm chmem chmod hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo chmod 666 /dev/misc_dev hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/misc_dev hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test & [1] 9715 hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ opening... sleeping... hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/misc_dev cat: /dev/misc_dev: 設備或資源忙 hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/misc_dev cat: /dev/misc_dev: 設備或資源忙 hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ closing [1]+ 已完成 ./test hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/misc_dev hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$
- 兩者都是解決互斥問題的基本手段,面對特定狀況咱們須要加以抉擇
- 信號量是進程級的,用於多個進程之間資源的互斥,若是競爭失敗,將會發生進程上下文切換(發生休眠),由於進程上下文切換開銷較大,所以只有當前進程用資源時間較長的時候,選用信號量纔是較好的選擇
- 而當咱們所保護的臨界資源訪問時間比較短的時候,使用自旋鎖比較方便,它不會引發進程睡眠而致使上下文切換
若是咱們將信號量初始化爲0,那麼它可用於同步。同步即須要執行單元擁有某特定的執行順序,須要保證執行的前後順序。此處以以下代碼段爲例子,在執行單元A中首先將互斥量初始化爲0,故在執行down
函數的時候執行單元A會休眠,直到執行單元B執行up
函數釋放信號量以後,執行單元A纔會得到信號量,進而繼續執行代碼區域b。也就是說執行單元A的執行須要執行單元B的喚醒,進而實現了同步。固然linux也爲咱們提供了一個完成量來實現同步。併發
執行單元A 執行單元B struct semphore sem; init_MUTEX_LOCKED(&sem);/*初始化信號量爲0*/ 代碼區域c 代碼區域a down(&sem); <------激活---------- up(&sem); 代碼區域b
/* step1:定義完成量 */ struct completion my_completion; /* step2:初始化完成量 */ init_completion(&my_completion); DECLARE_COMPLETION(my_completion); /* step3:等待完成量 */ void wait_for_completion(struct completion *c); /* step4:喚醒完成量 */ void complete(struct completion *c); //喚醒一個等待的執行單元 void complete_all(struct completion *c); //喚醒全部等待的執行單元
#include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/device.h> #include <asm/uaccess.h> #include <linux/slab.h> #include <linux/miscdevice.h> #include <linux/completion.h> #include "cdev_test_v4.h" #define BUF_SIZR 1024 #define MAJOR_NUM 168 #define MINOR_NUM 0 struct demo_cdev { char *buffer; //my private memory int value; struct miscdevice *mdev; struct completion com; }; /* step1: malloc memory for char device */ struct demo_cdev *demo_dev; static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos) { int ret = -1; int read_bytes; struct demo_cdev *demo = file->private_data; char *kbuf = demo->buffer + *pos; printk(KERN_INFO "Enter: %s\n", __func__); printk(KERN_INFO "Waitting for completion...\n"); wait_for_completion(&demo->com); printk(KERN_INFO "Waitting done\n"); /* determine whether user has finished reading data */ if (*pos >= BUF_SIZR) { return 0; } if (size > (BUF_SIZR - *pos)) { read_bytes = BUF_SIZR - *pos; } else { read_bytes = size; } ret = copy_to_user(buf, kbuf, read_bytes); if (ret != 0) { return -EFAULT; } *pos += read_bytes; return read_bytes; } static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos) { int ret; int write_bytes; struct demo_cdev *demo = file->private_data; char *kbuf = demo->buffer + *pos; printk(KERN_INFO "Enter: %s\n", __func__); if (*pos >= BUF_SIZR) { return 0; } if (size > (BUF_SIZR - *pos)) { write_bytes = BUF_SIZR - *pos; } else { write_bytes = size; } ret = copy_from_user(kbuf, buf, write_bytes); if (ret != 0) { return -EFAULT; } *pos += write_bytes; complete(&demo->com); return write_bytes; } static int demo_open(struct inode *node, struct file *file) { printk(KERN_INFO "Enter: %s\n", __func__); file->private_data = (void *)demo_dev; return 0; } static int demo_release(struct inode *node, struct file *file) { printk(KERN_INFO "Enter: %s\n", __func__); return 0; } static long demo_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int ret = 0; struct demo_cdev *demo = file->private_data; switch(cmd) { case CDEV_TEST_V4_CLEAN: memset(demo->buffer, 0x00, BUF_SIZR); printk(KERN_INFO "cmd: clean\n"); break; case CDEV_TEST_V4_GETVAL: put_user(demo->value, (int *)arg); printk(KERN_INFO "cmd: getval\n"); break; case CDEV_TEST_V4_SETVAL: demo->value = (int)arg; printk(KERN_INFO "cmd: setval\n"); break; default: break; } return (long)ret; } static struct file_operations demo_operation= { .open = demo_open, .release = demo_release, .read = demo_read, .write = demo_write, .unlocked_ioctl = demo_ioctl, }; static struct miscdevice misc_struct = { .minor = MISC_DYNAMIC_MINOR, .name = "misc_dev", .fops = &demo_operation, }; static int __init demo_init(void) { int ret = -1; printk(KERN_INFO "Enter: %s\n", __func__); demo_dev = (struct demo_cdev *)kmalloc(sizeof(struct demo_cdev), GFP_KERNEL); if (!demo_dev) { printk(KERN_ERR "failed to malloc demo_dev\n"); ret = -ENOMEM; goto ERROR_MALLOC_DEMODEV; } demo_dev->value = 1; demo_dev->buffer = (char *)kmalloc(BUF_SIZR, GFP_KERNEL); if (!demo_dev->buffer) { printk(KERN_ERR "malloc %d bytes failed\n", BUF_SIZR); ret = -ENOMEM; goto ERROR_MALLOC_BUFFER; } memset(demo_dev->buffer, 0x00, BUF_SIZR); init_completion(&demo_dev->com); demo_dev->mdev = &misc_struct; ret = misc_register(demo_dev->mdev); if (ret < 0) { printk(KERN_ERR "failed to register misc\n"); goto ERROR_MISC; } printk(KERN_INFO "demo char device init done\n"); return 0; ERROR_MISC: kfree(demo_dev->buffer); demo_dev->buffer = NULL; ERROR_MALLOC_BUFFER: kfree(demo_dev); demo_dev = NULL; ERROR_MALLOC_DEMODEV: return ret; } static void __exit demo_exit(void) { printk(KERN_INFO "Enter: %s\n", __func__); misc_deregister(demo_dev->mdev); kfree(demo_dev->buffer); demo_dev->buffer = NULL; kfree(demo_dev); demo_dev = NULL; printk(KERN_INFO "demo char device exit done\n"); } module_init(demo_init); module_exit(demo_exit); MODULE_AUTHOR("Qi Han"); MODULE_LICENSE("GPL");
cat
讀取數據的時候進程會阻塞,直到咱們向內核寫入數據後讀的線程纔會退出阻塞狀態。hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo insmod completion.ko [sudo] hq 的密碼: hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo chmod 666 /dev/misc_dev hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/misc_dev & [1] 5602 hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ echo "hanqi" -> /dev/misc_dev hanqi - hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ dmesg [ 4463.854757] Enter: demo_init [ 4463.854864] demo char device init done [ 4490.500256] Enter: demo_open [ 4490.500268] Enter: demo_read [ 4490.500269] Waitting for completion... [ 4546.197831] Enter: demo_open [ 4546.197841] Enter: demo_write [ 4546.197859] Enter: demo_release [ 4546.197907] Waitting done [ 4546.197989] Enter: demo_release
雖然咱們能夠經過信號量完成互斥的操做,可是在linux中也爲咱們提供了一套標準的mutex互斥鎖機制函數
/* 定義互斥鎖 */ struct mutex my_mutex; /* 初始化互斥鎖 */ mutex_init(&my_mutex); /* 獲取互斥體(上鎖) */ void inline __sched mutex_lock(struct mutex *lock); int __sched mutex_lock_interruptible(struct mutex *lock); int __sched mutex_trylock(struct mutex *lock); /* 釋放互斥鎖 */ void __sched mutex_unlock(struct mutex *lock); /* 1:__sched宏展開後以下,將帶有__sched的函數放到,sched.text段,即若是不想讓函數在waiting channel中顯示出來,就應該加上__sched */ /* Attach to any functions which should be ignored in wchan output */ #define __sched __attribute__((__section__(".sched.text")))
cat "/proc/<pid>/wchan"
,進而.sched.text
段的代碼是會被wchan所忽略的,schedule這個函數是不會出如今wchan的結果中cat "/proc/<pid>/wchan"
查看進程死在了哪一個函數中,顯然結果是read
函數中demo_readhq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/misc_dev demo_readhq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ps aux hq 5664 0.0 0.0 16860 592 pts/0 D+ 12:27 0:00 cat /dev/misc_dev hq 5665 2.0 0.0 20140 3348 pts/1 R+ 12:27 0:00 ps aux hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat "/proc/5664/wchan" demo_read hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$
#include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/device.h> #include <asm/uaccess.h> #include <linux/slab.h> #include <linux/miscdevice.h> #include <linux/mutex.h> #include "cdev_test_v4.h" #define BUF_SIZR 1024 #define MAJOR_NUM 168 #define MINOR_NUM 0 struct demo_cdev { char *buffer; //my private memory int value; struct miscdevice *mdev; struct mutex lock; }; /* step1: malloc memory for char device */ struct demo_cdev *demo_dev; static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos) { int ret = -1; int read_bytes; struct demo_cdev *demo = file->private_data; char *kbuf = demo->buffer + *pos; printk(KERN_INFO "Enter: %s\n", __func__); /* lock */ mutex_lock(&demo->lock); /* determine whether user has finished reading data */ if (*pos >= BUF_SIZR) { return 0; } if (size > (BUF_SIZR - *pos)) { read_bytes = BUF_SIZR - *pos; } else { read_bytes = size; } ret = copy_to_user(buf, kbuf, read_bytes); if (ret != 0) { mutex_unlock(&demo->lock); return -EFAULT; } *pos += read_bytes; /* unlock */ mutex_unlock(&demo->lock); return read_bytes; } static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos) { int ret; int write_bytes; struct demo_cdev *demo = file->private_data; char *kbuf = demo->buffer + *pos; printk(KERN_INFO "Enter: %s\n", __func__); /* lock */ mutex_lock(&demo->lock); if (*pos >= BUF_SIZR) { return 0; } if (size > (BUF_SIZR - *pos)) { write_bytes = BUF_SIZR - *pos; } else { write_bytes = size; } ret = copy_from_user(kbuf, buf, write_bytes); if (ret != 0) { mutex_unlock(&demo->lock); return -EFAULT; } *pos += write_bytes; /* unlock */ mutex_unlock(&demo->lock); return write_bytes; } static int demo_open(struct inode *node, struct file *file) { printk(KERN_INFO "Enter: %s\n", __func__); file->private_data = (void *)demo_dev; return 0; } static int demo_release(struct inode *node, struct file *file) { printk(KERN_INFO "Enter: %s\n", __func__); return 0; } static long demo_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int ret = 0; struct demo_cdev *demo = file->private_data; switch(cmd) { case CDEV_TEST_V4_CLEAN: memset(demo->buffer, 0x00, BUF_SIZR); printk(KERN_INFO "cmd: clean\n"); break; case CDEV_TEST_V4_GETVAL: put_user(demo->value, (int *)arg); printk(KERN_INFO "cmd: getval\n"); break; case CDEV_TEST_V4_SETVAL: demo->value = (int)arg; printk(KERN_INFO "cmd: setval\n"); break; default: break; } return (long)ret; } static struct file_operations demo_operation= { .open = demo_open, .release = demo_release, .read = demo_read, .write = demo_write, .unlocked_ioctl = demo_ioctl, }; static struct miscdevice misc_struct = { .minor = MISC_DYNAMIC_MINOR, .name = "misc_dev", .fops = &demo_operation, }; static int __init demo_init(void) { int ret = -1; printk(KERN_INFO "Enter: %s\n", __func__); demo_dev = (struct demo_cdev *)kmalloc(sizeof(struct demo_cdev), GFP_KERNEL); if (!demo_dev) { printk(KERN_ERR "failed to malloc demo_dev\n"); ret = -ENOMEM; goto ERROR_MALLOC_DEMODEV; } demo_dev->value = 1; demo_dev->buffer = (char *)kmalloc(BUF_SIZR, GFP_KERNEL); if (!demo_dev->buffer) { printk(KERN_ERR "malloc %d bytes failed\n", BUF_SIZR); ret = -ENOMEM; goto ERROR_MALLOC_BUFFER; } memset(demo_dev->buffer, 0x00, BUF_SIZR); mutex_init(&demo_dev->lock); demo_dev->mdev = &misc_struct; ret = misc_register(demo_dev->mdev); if (ret < 0) { printk(KERN_ERR "failed to register misc\n"); goto ERROR_MISC; } printk(KERN_INFO "demo char device init done\n"); return 0; ERROR_MISC: kfree(demo_dev->buffer); demo_dev->buffer = NULL; ERROR_MALLOC_BUFFER: kfree(demo_dev); demo_dev = NULL; ERROR_MALLOC_DEMODEV: return ret; } static void __exit demo_exit(void) { printk(KERN_INFO "Enter: %s\n", __func__); misc_deregister(demo_dev->mdev); kfree(demo_dev->buffer); demo_dev->buffer = NULL; kfree(demo_dev); demo_dev = NULL; printk(KERN_INFO "demo char device exit done\n"); } module_init(demo_init); module_exit(demo_exit); MODULE_AUTHOR("Qi Han"); MODULE_LICENSE("GPL");