信號量
- 1 信號量
- 2 驅動程序和測試程序
- 3 內核的具體實現
- 總結
1 信號量
Linux中的信號量是一種睡眠鎖。如果有一個任務試圖獲得一個已經被占用的信號量時,信號量會將其放到一個等待隊列,然后讓其睡眠,這時處理器去執行其他代碼。當持有信號量的進程將信號量釋放后,處于等待隊列中的那個任務將被喚醒,并獲得該信號量。
信號量定義在文件include/linux/semaphore.h中
/* Please don't access any members of this structure directly */
struct semaphore {raw_spinlock_t lock;unsigned int count;struct list_head wait_list;
};
信號量可以同時允許任意數量的鎖持有者,信號量同時允許的持有者數量可以在聲明信號量時指定,這個值稱為使用者數量。通常情況下,信號量和自旋鎖一樣,在一個時刻僅允許有一個鎖持有者。當數量等于1,這樣的信號量被稱為二值信號量或者被稱為互斥信號量;初始化時也可以把數量設置為大于1的非0值,這種情況,信號量被稱為計數信號量,它允許在一個時刻至多有count個鎖持有者。
信號量支持兩個原子操作P()和V()。前者叫做測試操作,后者叫做增加操作,后來系統把這兩種操作分別叫做down()和up(),Linux也遵從這種叫法。down()通過對信號量減1來請求一個信號量,如果減1結果是0或者大于0,那么就獲得信號量鎖,任務就可以進入臨界區,如果結果是負的,那么任務會被放入等待隊列。相反,當臨界區的操作完成后,up()操作用來釋放信號量,如果在該信號量上的等待隊列不為空,那么處于隊列中等待的任務被喚醒。
信號量的操作函數如下:
2 驅動程序和測試程序
在驅動中,我們僅允許一個進程打開設備,這個功能用互斥信號量來實現。
先執行:
sudo mknod /dev/hello c 232 0
驅動程序semaphore.c:
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/semaphore.h>#define BUFFER_MAX (64)
#define OK (0)
#define ERROR (-1)struct cdev *gDev;
struct file_operations *gFile;
dev_t devNum;
unsigned int subDevNum = 1;
int reg_major = 232;
int reg_minor = 0;
char buffer[BUFFER_MAX];
struct semaphore sema;
int open_count = 0;int hello_open(struct inode *p, struct file *f)
{/* 加鎖 */down(&sema);if(open_count>=1){up(&sema);printk(KERN_INFO "device is busy,hello_open fail");return -EBUSY;}open_count++;up(&sema);printk(KERN_INFO"hello_open ok\r\n");return 0;
}int hello_close(struct inode *inode,struct file *filp)
{if(open_count!=1){printk(KERN_INFO"something wrong,hello_close fail");return -EFAULT;}open_count--;printk(KERN_INFO"hello_close ok\r\n");return 0;
}ssize_t hello_write(struct file *f, const char __user *u, size_t s, loff_t *l)
{int writelen =0;printk(KERN_EMERG"hello_write\r\n");writelen = BUFFER_MAX>s?s:BUFFER_MAX;if(copy_from_user(buffer,u,writelen)){return -EFAULT;}return writelen;
}
ssize_t hello_read(struct file *f, char __user *u, size_t s, loff_t *l)
{int readlen;printk(KERN_EMERG"hello_read\r\n"); readlen = BUFFER_MAX>s?s:BUFFER_MAX; if(copy_to_user(u,buffer,readlen)){return -EFAULT;}return readlen;
}
int hello_init(void)
{devNum = MKDEV(reg_major, reg_minor); /* 獲取設備號 */if(OK == register_chrdev_region(devNum, subDevNum, "helloworld")){printk(KERN_EMERG"register_chrdev_region ok \n"); }else {printk(KERN_EMERG"register_chrdev_region error n");return ERROR;}printk(KERN_EMERG" hello driver init \n");gDev = kzalloc(sizeof(struct cdev), GFP_KERNEL);gFile = kzalloc(sizeof(struct file_operations), GFP_KERNEL);gFile->open = hello_open;gFile->release = hello_close;gFile->read = hello_read;gFile->write = hello_write;gFile->owner = THIS_MODULE;cdev_init(gDev, gFile);cdev_add(gDev, devNum, 3);/* 初始化信號量 */sema_init(&sema,1);return 0;
}void __exit hello_exit(void)
{printk(KERN_INFO"hello driver exit\n");cdev_del(gDev);kfree(gDev);unregister_chrdev_region(devNum, subDevNum);return;
}
module_init(hello_init); /* 驅動入口 */
module_exit(hello_exit); /* 驅動出口 */
MODULE_LICENSE("GPL");
測試程序test.c:
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/select.h>
#include <unistd.h>#define DATA_NUM (64)
int main(int argc, char *argv[])
{int fd, i;int r_len, w_len;fd_set fdset;char buf[DATA_NUM]="hello world";fd = open("/dev/hello", O_RDWR);if(-1 == fd) {perror("open file error\r\n");return -1;} else {printf("open successe\r\n");}w_len = write(fd,buf, DATA_NUM);if(w_len==-1){perror("write error\n");return -1;}sleep(5);printf("write len:%d\n",w_len);close(fd);return 0;
}
Makefile:
obj-m := semaphore.oKERNELDIR := /lib/modules/$(shell uname -r)/buildall default:modules
install:modules_installmodules modules_install help clean:$(MAKE) -C $(KERNELDIR) M=$(shell pwd) $@test:test.cgcc $^ -o $@
執行命令:
make
make test
當我們同時打開兩個測試時,只有一個能打開,另一個打開失敗,實現了互斥訪問。
3 內核的具體實現
信號量定義在文件include/linux/semaphore.h中,下面的函數也定義在這個文件中
/* Please don't access any members of this structure directly */
struct semaphore {raw_spinlock_t lock;unsigned int count;struct list_head wait_list;
};
初始化函數
static inline void sema_init(struct semaphore *sem, int val)
{static struct lock_class_key __key;*sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);
}
該初始化會將val值賦值給struct semaphore里的count,wait_list初始化為鏈表頭,lock值設定為解鎖狀態,lock是自旋鎖。
down函數的實現在kernel/locking/semaphore.c文件中
/*** down - acquire the semaphore* @sem: the semaphore to be acquired** Acquires the semaphore. If no more tasks are allowed to acquire the* semaphore, calling this function will put the task to sleep until the* semaphore is released.** Use of this function is deprecated, please use down_interruptible() or* down_killable() instead.*/
void down(struct semaphore *sem)
{unsigned long flags;raw_spin_lock_irqsave(&sem->lock, flags);if (likely(sem->count > 0))sem->count--;else__down(sem);raw_spin_unlock_irqrestore(&sem->lock, flags);
}
首先是raw_spin_lock_irqsave加鎖,接著判斷count是不是大于0,大于0就count就減去1,否則,轉到__down函數執行
static noinline void __sched __down(struct semaphore *sem)
{__down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}
TASK_UNINTERRUPTIBLE表示進程不可中斷,MAX_SCHEDULE_TIMEOUT表示休眠時間
static inline int __sched __down_common(struct semaphore *sem, long state,long timeout)
{struct task_struct *task = current;struct semaphore_waiter waiter;/* 將等待信號量的等待者加入到信號量的等待隊列wait_list中 */list_add_tail(&waiter.list, &sem->wait_list);waiter.task = task;waiter.up = false;for (;;) {/* 檢查有無信號打斷 */if (signal_pending_state(state, task))goto interrupted;/* 檢查timeout是否小于0 */if (unlikely(timeout <= 0))goto timed_out;/* 設置進程的狀態 */__set_task_state(task, state);/* 解鎖 */raw_spin_unlock_irq(&sem->lock);/* schedule_timeout用來讓出CPU;在指定的時間用完以后或者其它事件到達并喚醒進程(比如接收了一個信號量)時,該進程才可以繼續運行 */timeout = schedule_timeout(timeout);/* 加鎖 */raw_spin_lock_irq(&sem->lock);if (waiter.up)return 0;}timed_out:list_del(&waiter.list);return -ETIME;interrupted:list_del(&waiter.list);return -EINTR;
}
semaphore_waiter 的實例表示信號的一個等待者
struct semaphore_waiter {struct list_head list;struct task_struct *task;bool up;
};
list_head是一個雙向鏈表。
__down會先將進程加入到信號的等待隊列中,然后將進程設置為不可打斷的睡眠狀態,接著讓出CPU,在指定的時間用完以后或者其它事件到達并喚醒進程,如果等待進程waiter的up不為真,將一直for循環,直到up為真,返回0。
所以down函數的功能就是先判斷count是否大于0(即是否還有資源),如果大于0,減1,繼續執行,否則就調用__down,將進程加入信號的等待隊列中,一直for循環,直到up為真,然后繼續執行。
up函數的實現也在kernel/locking/semaphore.c文件中
/*** up - release the semaphore* @sem: the semaphore to release** Release the semaphore. Unlike mutexes, up() may be called from any* context and even by tasks which have never called down().*/
void up(struct semaphore *sem)
{unsigned long flags;raw_spin_lock_irqsave(&sem->lock, flags);/* 判斷信號的等待隊列是否為空,為空直接讓count加1 */if (likely(list_empty(&sem->wait_list)))sem->count++;else__up(sem);raw_spin_unlock_irqrestore(&sem->lock, flags);
}
首先判斷信號的等待隊列是否為空,為空直接讓count加1,否則進入__up函數:
static noinline void __sched __up(struct semaphore *sem)
{/* 獲得包含鏈表第一個成員的結構體指針 */struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list,struct semaphore_waiter, list);/* 從信號量的等待隊列中刪除該進程 */list_del(&waiter->list);/* 喚醒該進程 */waiter->up = true;wake_up_process(waiter->task);
}
__up函數首先拿到等待該信號的第一個進程,在等待隊列中刪除該進程,并且將up置為true,最后喚醒該進程。
總結
信號量會讓進程休眠,讓出CPU,這個時候有進程調度,進程調度開銷比較大,并且不能在中斷處理程序中使用信號量,因為信號量會睡眠。