同步---信號量

信號量

  • 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,這個時候有進程調度,進程調度開銷比較大,并且不能在中斷處理程序中使用信號量,因為信號量會睡眠。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/379570.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/379570.shtml
英文地址,請注明出處:http://en.pswp.cn/news/379570.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Java Float類floatToIntBits()方法與示例

Float類floatToIntBits()方法 (Float class floatToIntBits() method) floatToIntBits() method is available in java.lang package. floatToIntBits()方法在java.lang包中可用。 floatToIntBits() method follows IEEE 754 floating-point standards and according to standa…

解釋三度帶和六度帶的概念以及各坐標系如何定義

★ 地形圖坐標系&#xff1a;我國的地形圖采用高斯&#xff0d;克呂格平面直角坐標系。在該坐標系中&#xff0c;橫軸&#xff1a;赤道&#xff0c;用&#xff39;表示&#xff1b;縱軸&#xff1a;中央經線&#xff0c;用&#xff38;表示&#xff1b;坐標原點&#xff1a;中央…

0-1背包問題(物品不可分割)

問題背景&#xff1a; 所謂“鐘點秘書”&#xff0c;是指年輕白領女性利用工余時間為客戶提供秘書服務&#xff0c;并按鐘點收取酬金。“鐘點秘書”為客戶提供有償服務的方式一般是&#xff1a;采用電話、電傳、上網等“遙控”式 服務&#xff0c;或親自到客戶公司處理部分業務…

算法---KMP算法

字符串1 KMP算法狀態機概述構建狀態轉移1 KMP算法 原文鏈接&#xff1a;https://zhuanlan.zhihu.com/p/83334559 先約定&#xff0c;本文用pat表示模式串&#xff0c;長度為M&#xff0c;txt表示文本串&#xff0c;長度為N&#xff0c;KMP算法是在txt中查找子串pat&#xff0…

cache初接觸,并利用了DataView

我們在寫代碼的時候,如果數據控件要獲得數據,一般方法,Conn.Open();OleDbCommand cmd;cmd new OleDbCommand(sql, Conn);GridView1.DataSource dbcenter.accessGetDataSet(sql);GridView1.DataBind();Conn.close();但如果多個數據控件要綁定數據,則比較頻繁打開數據庫,效率一…

Java ByteArrayInputStream reset()方法及示例

ByteArrayInputStream類reset()方法 (ByteArrayInputStream Class reset() method) reset() method is available in java.util package. reset()方法在java.util包中可用。 reset() method is used to reset this ByteArrayInputStream to the last time marked position and …

回文數猜想

問題描述&#xff1a; 一個正整數&#xff0c;如果從左向右讀&#xff08;稱之為正序數&#xff09;和從右向左讀&#xff08;稱之為倒序數&#xff09;是一樣的&#xff0c;這樣的數就叫回文數。任取一個正整數&#xff0c;如果不是回文數&#xff0c;將該數與他的倒序數相加…

文件上傳 帶進度條(多種風格)

文件上傳 帶進度條 多種風格 非常漂亮&#xff01; 友好的提示 以及上傳驗證&#xff01; 部分代碼&#xff1a; <form id"form1" runat"server"><asp:ScriptManager ID"scriptManager" runat"server" EnablePageMethods&quo…

同步---自旋鎖

1 自旋鎖的基本概念 自旋鎖最多只能被一個可執行線程持有&#xff0c;如果一個執行線程試圖獲得一個已經被使用的自旋鎖&#xff0c;那么該線程就會一直進行自旋&#xff0c;等待鎖重新可用。在任何時刻&#xff0c;自旋鎖都可以防止多余一個的執行線程同時進入臨界區。 Linu…

實習日志----4.播放時段參數設置

由于客戶在下發廣告時&#xff0c;一則廣告可在多個時段播放&#xff0c;這就需要設置多個播放時段的參數。 但在這種情況下&#xff0c;我并不知道用戶每次需要下發幾個時段&#xff0c;所以前臺不能設定死。 因此我要實現這么一個功能&#xff0c;讓用戶根據自己的需要來動態…

線性插值算法實現圖像_C程序實現插值搜索算法

線性插值算法實現圖像Problem: 問題&#xff1a; We are given an array arr[] with n elements and an element x to be searched amongst the elements of the array. 給定一個數組arr []&#xff0c;其中包含n個元素和一個要在該數組的元素中搜索的元素x 。 Solution: 解&…

hdu 1197

地址&#xff1a;http://acm.hdu.edu.cn/showproblem.php?pid1197 題意&#xff1a;求一個數轉換成10&#xff0c;12&#xff0c;16進制后各個位上的數的和是否相等。 mark&#xff1a;模擬進制轉換。 代碼&#xff1a; #include <stdio.h>int zh(int a, int n) {int su…

linux系統編程---線程總結

線程總結1 線程的實現線程創建線程退出線程等待線程清理2 線程的屬性線程的分離線程的棧地址線程棧大小線程的調度策略線程優先級3 線程的同步互斥鎖讀寫鎖條件變量信號量線程是系統獨立調度和分配的基本單位。同一進程中的多個線程將共享該進程中的全部系統資源&#xff0c;例…

博客上一些項目相關源碼鏈接

GitHub&#xff1a;https://github.com/beyondyanyu/Sayingyy

重新開啟Ctrl+Alt+Backspace快捷鍵

UBUNTU老用戶知道CtrlAltBackspace這個快捷鍵是用來快速重啟X的在9.04中被默認關閉了&#xff0c;那如何來打開它呢&#xff1f;在終端中輸入&#xff1a;sudo gedit /etc/X11/xorg.conf在其中加入&#xff1a;Section “ServerFlags”Option “DontZap” “false”EndSection退…

Java LocalDate類| 帶示例的getDayOfYear()方法

LocalDate類的getDayOfYear()方法 (LocalDate Class getDayOfYear() method) getDayOfYear() method is available in java.time package. getDayOfYear()方法在java.time包中可用。 getDayOfYear() method is used to get the day-of-year field value of this LocalDate obje…

火腿三明治定理

定理&#xff1a;任意給定一個火腿三明治&#xff0c;總有一刀能把它切開&#xff0c;使得火腿、奶酪和面包片恰好都被分成兩等份。 而且更有趣的是&#xff0c;這個定理的名字真的就叫做“火腿三明治定理”&#xff08;ham sandwich theorem&#xff09;。它是由數學家亞瑟?斯…

如何給Linux操作系統(CentOS 7為例)云服務器配置環境等一系列東西

1.首先&#xff0c;你得去購買一個云服務器&#xff08;這里以阿里云學生服務器為例&#xff0c;學生必須實名認證&#xff09; 打開阿里云&#xff0c;搜索學生服務器點擊進入即可 公網ip為連接云服務器的主機 自定義密碼為連接云服務器是需要輸入的密碼 購買即可 點擊云服…

Linux系統編程---I/O多路復用

文章目錄1 什么是IO多路復用2 解決什么問題說在前面I/O模型阻塞I/O非阻塞I/OIO多路復用信號驅動IO異步IO3 目前有哪些IO多路復用的方案解決方案總覽常見軟件的IO多路復用方案4 具體怎么用selectpollepolllevel-triggered and edge-triggered狀態變化通知(edge-triggered)模式下…

[轉帖]純屬娛樂——變形金剛vs天網

[轉帖]變形金剛2的影評-《變形金剛3 天網反擊戰》有一個問題困擾了我足足二十年&#xff1a;為什么汽車人要幫地球人&#xff1f;光用“所有有感知的生物都應享有自由”這個法則是根本說不過去的&#xff0c;因為豬也有感知&#xff0c;但人類就把豬圈養起來&#xff0c;隨意殺…