顯示具有 Linux Kernel 標籤的文章。 顯示所有文章
顯示具有 Linux Kernel 標籤的文章。 顯示所有文章

2016年2月1日 星期一

[轉] linux下devicetree中常用的of函數

從device_node中獲取信息:
int of_property_read_u8_array(const struct device_node *np, const char *propname,u8 *out_values, size_t sz);
int of_property_read_u16_array(const struct device_node *np, const char *propname,u16 *out_values, size_t sz);
int of_property_read_u32_array(const struct device_node *np, const char *propname,u32 *out_values, size_t sz);
從設備結點np中讀取屬性名為propname,類型為8、16、32、位整型數組的屬性值,並放入out_values,sz指明了要讀取的個數。

static inline int of_property_read_u8(const struct device_node *np,const char *propname,u8 *out_value) 
static inline int of_property_read_u16(const struct device_node *np,const char *propname,u8 *out_value) 
static inline int of_property_read_u32(const struct device_node *np,const char *propname,u8 *out_value) 
從設備結點np中讀取屬性名為propname,類型為8、16、32位的屬性值,並放入out_values。實際上這裡調用的就是sz為1的XXX_array函數。

int of_property_read_u32_index(const struct device_node *np,const char*propname,u32 index, u32 *out_value)
從設備結點np中讀取屬性名為propname的屬性值中第index個u32數值給out_value

int of_property_read_u64(conststruct device_node *np, const char *propname,u64 *out_value)
從設備結點np中讀取屬性名為propname,類型為64位的屬性值,並放入out_values

int of_property_read_string(struct device_node *np, const char *propname,const char**out_string)
從設備結點np中讀取屬性名為propname的字符串型屬性值

int of_property_read_string_index(struct device_node *np, const char *propname,intindex, const char **output)
從設備結點np中讀取屬性名為propname的字符串型屬性值數組中的第index個字符串

int of_property_count_strings(struct device_node *np, const char *propname)
從設備結點np中讀取屬性名為propname的字符串型屬性值的個數

unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
從設備節點dev中讀取第index個irq號

int of_irq_to_resource(struct device_node *dev, int index, struct resource *r)
從設備節點dev中讀取第index個irq號,並填充一個irq資源結構體

int of_irq_count(struct device_node *dev)
獲取設備節點dev的irq個數

static inline bool of_property_read_bool(const struct device_node *np,const char *propname);
如果設備結點np含有propname屬性,則返回true,否則返回false。一般用於檢查空屬性是否存在。

struct property* of_find_property(const struct device_node *np,const char *name,int *lenp)
根據name參數,在指定的設備結點np中查找匹配的property,並返回這個property

const void * of_get_property(const struct device_node *np, const char *name,int *lenp)
根據name參數,在指定的設備結點np中查找匹配的property,並返回這個property的屬性值

struct device_node* of_get_parent(const struct device_node *node)
獲得node節點的父節點的device node

int of_device_is_compatible(const struct device_node *device,const char *compat);
判斷設備結點device的compatible屬性是否包含compat指定的字符串

從of_allnodes中查找信息:
struct device_node* of_find_node_by_path(const char *path)
根據路徑參數,在全局鏈表of_allnodes中,查找匹配的device_node

struct device_node* of_find_node_by_name(struct device_node *from,const char *name)
則根據name在全局鏈表of_allnodes中查找匹配的device_node,若from=NULL表示從頭開始查找

struct device_node* of_find_node_by_type(struct device_node *from,const char *type)
根據設備類型在全局鏈表of_allnodes中查找匹配的device_node

struct device_node * of_find_compatible_node(struct device_node *from, const char*type, const char,*compatible);
根據compatible的屬性值在全局鏈表of_allnodes中查找匹配的device_node,大多數情況下,from、type為NULL。

struct device_node* of_find_node_with_property(struct device_node *from,const char *prop_name)
根據節點屬性的name在全局鏈表of_allnodes中查找匹配的device_node

struct device_node* of_find_node_by_phandle(phandle handle)
根據phandle在全局鏈表of_allnodes中查找匹配的device_node

雜:
void __iomem* of_iomap(struct device_node *node, int index);
通過設備結點直接進行設備內存區間的 ioremap(),index是內存段的索引。若設備結點的reg屬性有多段,可通過index標示要ioremap的是哪一段,只有1段的情況,index為0

unsigned long __init of_get_flat_dt_root(void)
用來查找在dtb中的根節點,好像返回的都是0

int of_alias_get_id(struct device_node *np, const char *stem)
獲取節點np對應的aliasid號

struct device_node* of_node_get(struct device_node *node)
void of_node_put(struct device_node *node)
device node計數增加/減少

const struct of_device_id* of_match_node(const struct of_device_id *matches,const struct device_node*node)
將matches數組中of_device_id結構的name和type與device node的compatible和type匹配,返回匹配度最高的of_device_id結構

platform_device和resource相關:
int of_address_to_resource(struct device_node *dev, int index,struct resource *r)
根據設備節點dev的reg屬性值,填充資源結構體r。Index參數指明了使用reg屬性中第幾個屬性值,一般設置為0,表示第一個。

struct platform_device* of_device_alloc(struct device_node *np,const char *bus_id,struct device *parent)
根據device node,bus_id以及父節點創建該設備的platform_device結構,同時會初始化它的resource成員。

int of_platform_bus_probe(struct device_node *root,const struct of_device_id *matches,struct device *parent)
遍歷of_allnodes中的節點掛接到of_platform_bus_type總線上,由於此時of_platform_bus_type總線上還沒有驅動,所以此時不進行匹配

int of_platform_populate(struct device_node *root,const struct of_device_id *matches,const struct of_dev_auxdata *lookup,struct device *parent)
遍歷of_allnodes中的所有節點,生成並初始化所以節點的platform_device結構

struct platform_device* of_find_device_by_node(struct device_node *np)
根據device_node查找返回該設備對應的platform_device結構

2015年5月5日 星期二

[轉] ioctl,unlocked_ioctl和compat_ioctl

kernel 2.6.35 及之前的版本中struct file_operations 一共有3個ioctl :
ioctl
unlocked_ioctl
compat_ioctl
現在只有unlocked_ioctl和compat_ioctl 了

在kernel 2.6.36 中已經完全刪除了struct file_operations 中的ioctl 函數指針,取而代之的是unlocked_ioctl。

這個指針函數變了之後最大的影響是參數中少了inode,不過這個不是問題。
因為用戶程序中的ioctl對應的系統調用接口沒有變化,所以用戶程序不需要改變,一切都交給內核處理了。

如果想在 unlocked_ioctl 中獲得 inode 等信息可以用如下方法:
struct inode *inode = file->f_mapping->host;
struct block_device *bdev = inode->i_bdev;
struct gendisk *disk = bdev->bd_disk;
fmode_t mode = file->f_mode;
struct backing_dev_info *bdi;

這次內核函數的變化引出了一個問題,從ioctl系統調用往後,真正的ioctl調用順序是什麼?為什麼compat_ioctl 不被調用?
compat_ioctl被使用在用戶空間為32位模式,而內核運行在64位模式時。這時候,需要將64位轉成32位。
以下是2.6.36的情況:
SYSCALL_DEFINE3(ioctl ...) compat_sys_ioctl (是否直接調用compat_ioctl 取決於compat_ioctl 是否存在) 
| | |-----> compat_ioctl
| | |------>do_vfs_ioctl (下一步的調用取決於file->f_path.dentry->d_inode->i_node) 
| |------>file_ioctl
| | |---------------------->vfs_ioctl
|------->unlock_ioctl

其實compat_ioctl 沒有被調用的原因是compat_sys_ioctl 沒有被調用,
而它沒有被調用的原因似乎是壓根就沒有編譯到內核中,因為我沒有找到調用這個函數的代碼。
unlocked_ioctl 實際上取代了用了很久的ioctl,主要的改進就是不再需要上大內核鎖(調用之前不再先調用lock_kernel()然後再unlock_kernel())
總的來說kernel 開發者正在試圖朝移除大內核鎖的方向努力,ioctl的移除就是被革命了。
相信以後越來越多的內核函數會擺脫大內核鎖的依賴,並且大內核鎖最終會被移除。

轉載自:http://lp007819.wordpress.com/2011/01/06/kernel-2-6-36-ioctl-%E5%8F%98%E6%9B%B4/一、內核原型(linux2.6.28-7
     long (*compat_ioctl)(struct tty_struct *tty, struct file * file,
                     unsigned int cmd, unsigned long arg);

     implement ioctl processing for 32 bit process on 64 bit system
     Optional

二、What is compat_ioctl
Consider a scenario where application is 32 bit and kernel as well as architecture is 64 bit. In this case, when an application calls ioctl(), there should be some mechanism in kernel to handle 32 bit to 64 bit conversion. This conversion is especially required when user passes objects of type "long" and "void *".
There is one more method called as "compat_ioctl()" that a 64 bit driver has to implement. It gets called when 64 bit kernel gets ioctl() call from 32 bit user.
Tasks to be done by compat_ioctl() :
1. Acquire BKL, since kernel calls compat_ioctl without BKL.
2. 32 to 64 bit conversion for long and pointer objects passed by user
3. Process input data, get results.
4. 64 to 32 bit conversion in order to pass the output data back to user
5. Release BKL

三、中文檔案
Linux 64Bit 下的 ioctlcompat_ioctl ioctl32 Unknown cmd fd
前段時間將我們的程式移植到Mips64Linux 2.6環境下,做可行性試驗。
由於使用者態程程式規模太大,而且之前沒有對64bit的情況做考慮,
所以,使用者態程式任然使用32位元模式編譯,內核運行在64bit
我們有一個內核模組之前是在2.4.21下的,拿到2.6後,把部分api做了些更改,直接編譯並載入。
使用者態程式在調用ioctl的時候,總是失敗。
dmesg
看一下內核資訊,有如下類似資訊:
ioctl32(add_vopp:9767): Unknown cmd fd(3) cmd(80048f00){00} arg(ff240ae0)
後來在內核中的ioctl中添加debug代碼,發現根本沒調用到內核中的ioctl函數。
經過查找,發現了以下資源
The new way of ioctl()
32 bit user/64 bit kernel
What is compat_ioctl ()
more on compat_ioctl
產生問題的分析:
我們的程式通過Linux
ssize_t read(int fd, void *buf, size_t count); 
系統調用從虛擬裝置中讀取內核中。
使用
int ioctl(int fd, int request, ...);
系統調用來對設備進行控制,並傳遞一些資料。
ioctl
是我們擴展非標準系統調用的手段。


read
系統調用,對應內核中struct file_operations 結構中的
ssize_t (*read) (struct file *filp, char *buf, size_t count, loff_t *f_pos)
當用戶態位32bit, 內核位64bit的時候,
使用者態的程式,和內核態的程式,在資料類型不一致。
比如指針,用戶態的指針實際上是unsigned long 4Byte內核態的指針是unsigned ong: 8Byte.對於這種不一致,從用戶態陷入內核態的時候,大部分標準調用的參數已經做了相應的轉化。
不存在問題。比如ssize_t,內核態和用戶態不一致,對於這種已知類型,內核代碼已經做了轉換,因為他知道該怎麼轉,
所以我們的程式調用readwrite,等系統調用,都能正確的返回結果。
再來看看ioctl系統調用,
使用者態系統調用,int ioctl(int fd, int request, ...);內核中對應的函數
int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
request
是請求號,用來區分不同得到請求。後面的可變參數隨便選。
如果想傳入或傳出自訂結構,可以傳個指標類型。
如果沒資料傳遞,可以空著。
當然也可以傳這個intchar之類的類型來向內核傳遞資料。
問題來了,內核不知道你傳遞的是那種類型,他無法給你轉化。
總結一下:
1). Linux2.6
64bit kernel struct file_operation中增加了一個成員
long (*compat_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg);
用來提供32位元使用者態程式的方法,
可以通過HAVE_COMPAT_IOCTL宏來判斷該成員是否存在,並提供相應的操作方法。
2). 
int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)相比
compat_ioctl
少了inode參數可以通過filp->f_dentry->d_inode方法獲得。
3). 關於ioctl的請求號,經常是通過巨集定義生成的 舉例如下:
#define IoctlGetNetDeviceInfo    _IOWR(Ioctl_Magic, 1, struct_NetDeviceInfo)
#define IoctlNetQueueInit          _IOR(Ioctl_Magic, 4, struct_NetDeviceListen)
#define IoctlNetQueueDestroy     _IO(Ioctl_Magic, 5)
注意_IOWRIOR他們的最後一個參數是一個資料類型,展開時會包含sizeof()操作,
於是32bit使用者態程式和64bit內核之間,生成的ioctlrequest號很可能就會不同,
compat函數中switch()的時候,就會switch不到。
要想辦法避免:
提供64bit32bit大小一致的結構。
在使用者態下提供一個偽結構,偽結構和內核內的結構寬度一致。
_IO巨集,而不用_IOWR_IOR宏,反正只是為了得到一個號碼,實際傳輸資料大小,自己心理有數,內核代碼處理好就行了。
4). 
如果compat收到的最後參數arg是一個用戶態指標它在用戶態是32位的,在內核中為了保證安全,
可以使用compat_ptr(art)宏將其安全的轉化為一個64位的指標(仍然是用戶指標)


問題:

ioctl32: Unknown cmd

如果遇到這個錯誤 ... 都是在64/32轉換的問題
在你的 driver 加上 compat_ioctl .. 他會自己轉換到 compat_ioctl去

2015年2月28日 星期六

[轉] Linux中Workqueue機制分析

什麼是 workqueue ?
Linux 中的 Workqueue 機制就是為了簡化核心執行緒的建立。 通過呼叫 workqueue 的介面就能建立核心執行緒。 並且可以根據當前系統 CPU 的個數建立執行緒的數量,使得執行緒處理的事務能夠並行化。

workqueue 是核心中實現簡單而有效的機制,他顯然簡化了核心 daemon 的建立,方便了用戶的編程,



Workqueue 機制的實現
Workqueue 機制中定義了兩個重要的數據結構,分析如下:

1、  cpu_workqueue_struct 結構。 該結構將CPU 和核心執行緒進行了綁定。 在建立 workqueue 的過程中, Linux 根據當前系統 CPU 的個數建立 cpu_workqueue_struct 。 在該結構主要維護了一個任務隊列,以及核心執行緒需要睡眠的等待隊列,另外還維護了一個任務上下文,即 task_struct 。

2、  work_struct 結構是對任務的抽象。 在該結構中需要維護具體的任務方法,需要處理的數據,以及任務處理的時間。 該結構定義如下:

struct work_struct {
 unsigned long pending;
 struct list_head entry;/* 將任務掛載到 queue 的掛載點 */
 void (*func)(void *);  /* 任務方法 */
 void *data;            /* 任務處理的數據 */
 void *wq_data;         /* work 的屬主 */
 strut timer_list timer;/* 任務延時處理定時器 */
};

      

       當用戶呼叫 workqueue 的初始化介面 create_workqueue 或者 create_singlethread_workqueue 對 workqueue 隊列進行初始化時,核心就開始為用戶分配一個 workqueue 對象,並且將其鏈到一個全局的 workqueue 隊列中。 然後 Linux 根據當前 CPU 的情況,為 workqueue 對象分配與 CPU 個數相同的 cpu_workqueue_struct 對象,每個 cpu_workqueue_struct 對像都會存在一條任務隊列。 緊接著, Linux 為每個 cpu_workqueue_struct 對象分配一個核心 thread ,即核心 daemon 去處理每個隊列中的任務。 至此,用戶呼叫初始化介面將 workqueue 初始化完畢,返回 workqueue 的指標。



       在初始化 workqueue 過程中,核心需要初始化核心執行緒,註冊的核心執行緒工作比較簡單,就是不斷的掃描對應 cpu_workqueue_struct 中的任務隊列,從中獲取一個有效任務,然後執行該任務。 所以如果任務隊列為空,那麼核心 daemon 就在 cpu_workqueue_struct 中的等待隊列上睡眠,直到有人喚醒 daemon 去處理任務隊列。



       Workqueue 初始化完畢之後,將任務運行的上下文環境構建起來了,但是具體還沒有可執行的任務,所以,需要定義具體的 work_struct 對象。 然後將 work_struct 加入到任務隊列中, Linux 會喚醒 daemon 去處理任務。



       上述描述的 workqueue 核心實現原理可以描述如下:

点击看大图
    在 Workqueue 機制中,提供了一個系統默認的 workqueue 隊列—— keventd_wq ,這個隊列是 Linux 系統在初始化的時候就建立的。 用戶可以直接初始化一個 work_struct 對象,然後在該隊列中進行調度,使用更加方便。



Workqueue 編程介面

1
 create_workqueue
 用於建立一個 workqueue 隊列,為系統中的每個 CPU 都建立一個核心執行緒。

 輸入參數:
@name : workqueue 的名稱

2
 create_singlethread_workqueue 用於建立 workqueue ,只建立一個核心執行緒。

輸入參數:
@name : workqueue 名稱

3
 destroy_workqueue
 釋放 workqueue 隊列。

輸入參數:
@ workqueue_struct :需要釋放的 workqueue 隊列指標

4
 schedule_work
 調度執行一個具體的任務,執行的任務將會被掛入 Linux 系統提供的 workqueue —— keventd_wq

輸入參數:
@ work_struct :具體任務對象指標

5
 schedule_delayed_work
 延遲一定時間去執行一個具體的任務,功能與 schedule_work 類似,多了一個延遲時間,

輸入參數:
@work_struct :具體任務對象指標
@delay :延遲時間

6
 queue_work
 調度執行一個指定 workqueue 中的任務。

輸入參數:
@ workqueue_struct :指定的 workqueue 指標
@work_struct :具體任務對象指標

7
 queue_delayed_work
 延遲調度執行一個指定 workqueue 中的任務,功能與 queue_work 類似,輸入參數多了一個 delay 。

基本順序

1.創建工作隊列
struct workqueue_struct *create_workqueue(const char *name);
struct workqueue_struct *create_singlethread_workqueue(const char *name);
2.創建工作
DECLARE_WORK(name, void (*function)(void *), void *data);
//一個函數搞定定義和初始化
INIT_WORK(struct work_struct *work, void (*function)(void *), void *data);
PREPARE_WORK(struct work_struct *work, void (*function)(void *), void *data);
//要先定義work_struct,再調用這兩個函數
//INIT_WORK做更全的初始化,若需要改變工作隊列,則用PREPARE_WORK


3.提交工作給一個工作隊列
int queue_work(struct workqueue_struct *queue, struct work_struct *work);
int queue_delayed_work(struct workqueue_struct *queue, struct work_struct *work, unsigned long delay);
//返回0,添加成功,非0表明已存在此工作
int cancel_delayed_work(struct work_struct *work);
//返回非0,取消成功,返回0,取消不成功,工作仍在運行
void flush_workqueue(struct workqueue_struct *queue);
//確保cancel_delayed_work調用返回0後,停止運行工作

4.刪除隊列
void destroy_workqueue(struct workqueue_struct *queue);
//用完後刪掉工作隊列

2014年11月6日 星期四

[轉] 淺析Linux等待隊列

在Linux驅動程序中,可以使用等待隊列(wait queue)來實現阻塞進程的喚醒。等待很早就作為一個基本的功能單位出現在Linux內核中,它以隊列為基礎數據結構,與進程調度機制緊密結合,能夠用於實現內核中的異步事件通知機制。
我們從它的使用範例著手,看看等待隊列是如何實現異步信號功能的。以下代碼節選自kernel/printk.c。
DECLARE_WAIT_QUEUE_HEAD(log_wait); // 初始化等待隊列頭log_wait
static DEFINE_SPINLOCK(logbuf_lock); // 定義自旋鎖logbuf_lock
int do_syslog(int type, char __user *buf, int len)
{
        ... ... ...
        err = wait_event_interruptible(log_wait, (log_start - log_end)); // 等待
        /*
        // 我們先來看看linux/wait.h中定義的wait_event_interruptible()的原型
        #define wait_event_interruptible(wq, condition)
        ({
               int __ret = 0;
                if ( (!condition) )
                        __wait_event_interruptible(wq, condition, __ret);
                __ret;
        })
        // wait_event_interruptible()將等待隊列加入第一個參數wq為等待隊列頭的等待隊列鏈表中,
        // 當condition滿足時,wait_event_interruptible()會立即返回,否則,阻塞等待condition滿足。
        // 與之類似的還有wait_event(),不同點在於wait_event_interruptible()可以被信號喚醒。
        // __wait_event_interruptible()原型如下:
        #define __wait_event_interruptible(wq, condition, __ret)
        do {
                DEFINE_WAIT(__wait); // 定義等待隊列__wait
                for(;;) {
                        prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE);
                        // prepare_to_wait()的作用是:將等待隊列__wait加入以wq為等待隊列的等待隊列
                         // 鏈表中,並且將進程狀態設置為TASK_INTERRUPTIBLE
                        if (condition)
                                break; // 如果condition滿足則跳出
                       if (!signal_pending(current)) { // 如果不是被信號喚醒
                                ret = schedule_timeout(ret);
                                if (!ret)
                                        break;
                                continue;
                        }
                          ret = - ERESTARTSYS;
                          break;
                          schedule(); // 放棄CPU,調度其它進程執行
                }
                finish_wait(&wq, &__wait);
                // finish_wait()的作用是,將等待隊列__wait從等待隊列頭wq指向的等待隊列鏈表中移除,
                // 並且將進程狀態設置為TASK_RUNNING
        }while(0)
        */
        ... ... ...
        spin_lock_irq(&lock_wait); // 鎖定
        ... ... ...
        log_start++; // log_start自加,使得(log_start - log_end)這個等待隊列的condition滿足
        ... ... ...
         spin_unlock_irq(&lock_wait); // 解鎖
        ... ... ...
}
void wake_up_klogd(void)
{
        ... ... ...
        wake_up_interruptible(&lock_wait); //喚醒等待隊列
}
void release_console_sem(void)
{
        ... ... ...
        spin_lock_irqsave(&lock_wait); // 鎖定
        wake_klogd = log_start - log_end;
         ... ... ...
        spin_unlock_irqrestore(&lock_wait); // 解鎖
        ... ... ...
        if (wake_klogd)
                wake_up_klogd(); // 如果wake_klogd非負,則調用wake_up_klogd()來喚醒等待隊列
        ... ... ...
}
我們最後再總結一下等待隊列wait_event_interruptible():


在wait_event_interruptible()中首先判斷conditon是不是已經滿足,如果是則直接返回0,否則調用__wait_event_interruptible(),並用__ret來存放返回值。__wait_event_interruptible()首先將定義並初始化一個wait_queue_t變量__wait,其中數據為當前進程狀態current(struct task_struct),並把__wait加入等待隊列。在無限循環中,__wait_event_interruptible()將本進程置為可中斷的掛起狀態,反覆檢查condition是否成立,如果成立則退出,如果不成立則繼續休眠;條件滿足後,即把本進程運行狀態置為運行態,並把__wait從等待隊列中清除,從而進程能夠調度運行。

掛起的進程並不會自動轉入運行的,因此,還需要一個喚醒動作,這個動作由wake_up_interruptible()完成,它將遍歷作為參數傳入的lock_wait等待隊列,將其中所有的元素(通常都是task_struct)置為運行態,從而可被調度。

[轉] wait_event_interruptible 使用方法

1. 關於 wait_event_interruptible() 和 wake_up()的使用

讀一下wait_event_interruptible()的源碼,不難發現這個函數先將
當前進程的狀態設置成TASK_INTERRUPTIBLE,然後調用schedule(),
而schedule()會將位於TASK_INTERRUPTIBLE狀態的當前進程從runqueue
隊列中刪除。從runqueue隊列中刪除的結果是,當前這個進程將不再參
與調度,除非通過其他函數將這個進程重新放入這個runqueue隊列中,
這就是wake_up()的作用了。

由於這一段代碼位於一個由condition控制的for(;;)循環中,所以當由
shedule()返回時(當然是被wake_up之後,通過其他進程的schedule()而
再次調度本進程),如果條件condition不滿足,本進程將自動再次被設
置為TASK_INTERRUPTIBLE狀態,接下來執行schedule()的結果是再次被
從runqueue隊列中刪除。這時候就需要再次通過wake_up重新添加到
runqueue隊列中。

如此反覆,直到condition為真的時候被wake_up.
  
可見,成功地喚醒一個被wait_event_interruptible()的進程,需要滿足:

   在 1)condition為真的前提下,2) 調用wake_up()。 

所以,如果你僅僅修改condition,那麼只是滿足其中一個條件,這個時候,
被wait_event_interruptible()起來的進程尚未位於runqueue隊列中,因
此不會被 schedule。這個時候只要wake_up一下就立刻會重新進入運行調度。
  
2. 關於wait_event_interruptible的返回值

根據 wait_event_interruptible 的宏定義知:

   1) 條件condition為真時調用這個函數將直接返回0,而當前進程不會
      被 wait_event_interruptible和從runqueue隊列中刪除。

   2) 如果要被wait_event_interruptible的當前進程有nonblocked pending
      signals, 那麼會直接返回-ERESTARTSYS(i.e. -512),當前進程不會
      被wait_event_interruptible 和從runqueue隊列中刪除。

   3) 其他情況下,當前進程會被正常的wait_event_interruptible,並從
      runqueue隊列中刪除,進入TASK_INTERRUPTIBLE狀態退出運行調度,
      直到再次被喚醒加入runqueue隊列中後而參與調度,將正常返回0。

附1:wait_event_interruptible  宏

#define wait_event_interruptible(wq, condition)    \
({                                                 \
     int __ret = 0;                                  \
     if (!(condition))                               \
      __wait_event_interruptible(wq, condition, __ret); \
      __ret;                                         \
})

註: C語言中{a,b, ..., x}的的值等於最後一項,即x,因此上述
宏的值是 __ret。


附2:wait_event_interruptible()和 wake_up的等效代碼

wait_event_interruptible(wq, condition) /*等效沒有考慮返回值*/
{
     if (!(condition))
     {
         wait_queue_t _ _wait;
         init_waitqueue_entry(&_ _wait, current);
         add_wait_queue(&wq, &_ _wait);
         for (;;)
         {
            set_current_state(TASK_INTERRUPTIBLE);
            if (condition)
            break;
            schedule();  /* implicit call: del_from_runqueue(current)*/
         }
         current->state = TASK_RUNNING;
         remove_wait_queue(&wq, &_ _wait);
      }
}


void wake_up(wait_queue_head_t *q)
{
      struct list_head *tmp;
      wait_queue_t *curr;
      list_for_each(tmp, &q->task_list)
      {
        curr = list_entry(tmp, wait_queue_t, task_list);
        wake_up_process(curr->task);
        /* implicit call: add_to_runqueue(curr->task);*/
        if (curr->flags)
          break;
      }

[轉] add_timer的使用方法

add_timer的使用方法

首先你要申請一個timer struct:
static struct timer_list timer;
或是用pointer申請
static struct timer_list *timer;

在第一次使用時一定要初始化:
init_timer(&timer);

設定要傳的data
timer.data = (unsigned long )foo_data;

設定間隔時間
timer.expires = jiffies + DELAY_TIME;

設定timer到時要跑的function
timer.function = foo_function;

接著把這個timer加進timer list結構
add_timer(&timer);

時間到了就會跳起來去執行程式foo_function,
但是從此timer不會再跳起來,必須重新add_timer才會在下一次繼續啟動

在離開時,記得要
del_timer(&timer);

[轉] Linux Kernel: 簡介HZ, tick and jiffies

Linux核心幾個重要跟時間有關的名詞或變數,底下將介紹HZ、tick與jiffies。

HZ
Linux核心每隔固定週期會發出timer interrupt (IRQ 0),HZ是用來定義每一秒有幾次timer interrupts。舉例來說,HZ為1000,代表每秒有1000次timer interrupts。HZ可在編譯核心時設定,如下所示 (以核心版本2.6.20-15為例):
adrian@adrian-desktop:~$ cd /usr/src/linux
adrian@adrian-desktop:/usr/src/linux$ make menuconfig
Processor type and features ---> Timer frequency (250 HZ) --->

其中HZ可設定100、250、300或1000。以小弟的核心版本預設值為250。

小實驗
觀察/proc/interrupt的timer中斷次數,並於一秒後再次觀察其值。理論上,兩者應該相差250左右。
adrian@adrian-desktop:~$ cat /proc/interrupts | grep timer && sleep 1 && cat /proc/interrupts | grep timer
0: 9309306 IO-APIC-edge timer
0: 9309562 IO-APIC-edge timer

上面四個欄位分別為中斷號碼、CPU中斷次數、PIC與裝置名稱。



問題來了,timer interrupt會做哪些事情? 答案如下所列:
  • 更新時間、日期與系統從開機至目前經過多少時間 。
  • 更新系統資源使用率統計
  • 檢查正在執行的程序是否已經超過其所分配的執行時間額度。如果是的話,則侵佔(preempt)該程序以利執行其它等待執行的程序。
  • 檢查軟體時間器(Software timer,如alarm系統呼叫)跟時間延遲函式(Delay function)的延遲時間是否已經超過。
Tick
Tick是HZ的倒數,意即timer interrupt每發生一次中斷的時間。如HZ為250時,tick為4毫秒 (millisecond)。

Jiffies
Jiffies為Linux核心變數(32位元變數,unsigned long),它被用來紀錄系統自開幾以來,已經過多少的tick。每發生一次timer interrupt,Jiffies變數會被加一。值得注意的是,Jiffies於系統開機時,並非初始化成零,而是被設為-300*HZ (arch/i386/kernel/time.c),即代表系統於開機五分鐘後,jiffies便會溢位。那溢位怎麼辦? 事實上,Linux核心定義幾個macro(timer_after、time_after_eq、time_before與time_before_eq),即便是溢位,也能藉由這幾個macro正確地取得jiffies的內容。

另外,80x86架構定義一個與jiffies相關的變數jiffies_64 ,此變數64位元,要等到此變數溢位可能要好幾百萬年。因此要等到溢位這刻發生應該很難吧。那如何經由jiffies_64取得jiffies資訊呢? 事實上,jiffies被對應至jiffies_64最低的32位元。因此,經由jiffies_64可以完全不理會溢位的問題便能取得jiffies。

2014年9月26日 星期五

[轉] Linux Kernel - Kthread

在kernel中建立thread可以使用kthread_create(),建立一個task,然後在調用wake_up_process(task)讓task真正的運行,如果要kill一個kthread可以使用kthread_stop()。
在kernel中,將kthread_create()和wake_up_process()包裝成kthread_run(),也就是調用了kthread_run()之後,該thread會立刻被執行。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kthread.h>

MODULE_LICENSE("GPL");

static struct task_struct *brook_tsk;
static int data;
static int kbrook(void *arg);

static int kbrook(void *arg)
{
    unsigned int timeout;
    int *d = (int *) arg;

    for(;;) {
        if (kthread_should_stop()) break;
        printk("%s(): %d\n", __FUNCTION__, (*d)++);
        do {
            set_current_state(TASK_INTERRUPTIBLE);
            timeout = schedule_timeout(10 * HZ);
        } while(timeout);
    }
    printk("break\n");

    return 0;
}

static int __init init_modules(void)
{
    int ret;

    brook_tsk = kthread_create(kbrook, &data, "brook");
    if (IS_ERR(brook_tsk)) {
        ret = PTR_ERR(brook_tsk);
        brook_tsk = NULL;
        goto out;
    }
    wake_up_process(brook_tsk);

    return 0;

out:
    return ret;
}

static void __exit exit_modules(void)
{
    kthread_stop(brook_tsk);
}

module_init(init_modules);
module_exit(exit_modules);


linux/kthread.h
/**
 * kthread_run - create and wake a thread.
 * @threadfn: the function to run until signal_pending(current).
 * @data: data ptr for @threadfn.
 * @namefmt: printf-style name for the thread.
 *
 * Description: Convenient wrapper for kthread_create() followed by
 * wake_up_process().  Returns the kthread or ERR_PTR(-ENOMEM).
 */
#define kthread_run(threadfn, data, namefmt, ...)      \
({            \
 struct task_struct *__k         \
  = kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \
 if (!IS_ERR(__k))         \
  wake_up_process(__k);        \
 __k;           \
})

2014年7月11日 星期五

[轉] Linux中的工作隊列

工作隊列(work queue)Linux kernel中將工作推後執行的一種機制。這種機制和BHTasklets不同之處在於工作隊列是把推後的工作交由一個內核線程去執行,因此工作隊列的優勢就在於它允許重新調度甚至睡眠。
工作隊列是2.6內核開始引入的機制,在2.6.20之後,工作隊列的數據結構發生了一些變化,因此本文分成兩個部分對2.6.20之前和之後的版本分別做介紹。

I2.6.0~2.6.19


數據結構:
1
2
3
4
5
6
7
8
struct work_struct {
    unsigned long pending;
    struct list_head entry;
    void (*func)(void *);
    void *data;
    void *wq_data;
    struct timer_list timer;
};
pending是用來記錄工作是否已經掛在隊列上;
entry是循環鏈表結構;
func作為函數指針,由用戶實現;
data用來存儲用戶的私人數據,此數據即是func的參數;
wq_data一般用來指向工作者線程(工作者線程參考下文);
timer是推後執行的定時器。
work_struct的這些變量裡,funcdata是用戶使用的,其他是內部變量,我們可以不用太過關心。

API
1
2
3
4
5
INIT_WORK(_work, _func, _data);
int schedule_work(struct work_struct *work);
int schedule_delayed_work(struct work_struct *work, unsigned long delay);
void flush_scheduled_work(void);
int cancel_delayed_work(struct work_struct *work);
1、初始化指定工作,目的是把用戶指定的函數_func_func需要的參數_data賦給work_structfuncdata變量。
2、對工作進行調度,即把給定工作的處理函數提交給缺省的工作隊列和工作者線程。工作者線程本質上是一個普通的內核線程,在默認情況下,每個CPU均有一個類型為「events」的工作者線程,當調用schedule_work時,這個工作者線程會被喚醒去執行工作鏈表上的所有工作。
3、延遲執行工作,與schedule_work類似。
4、刷新缺省工作隊列。此函數會一直等待,直到隊列中的所有工作都被執行。
5、flush_scheduled_work並不取消任何延遲執行的工作,因此,如果要取消延遲工作,應該調用cancel_delayed_work

以上均是採用缺省工作者線程來實現工作隊列,其優點是簡單易用,缺點是如果缺省工作隊列負載太重,執行效率會很低,這就需要我們創建自己的工作者線程和工作隊列。
API
1
2
3
4
5
struct workqueue_struct *create_workqueue(const char *name);
int queue_work(struct workqueue_struct *wq, struct work_struct *work);
int queue_delayed_work(struct workqueue_struct *wq, struct work_struct *work, unsigned long delay);
void flush_workqueue(struct workqueue_struct *wq);
void destroy_workqueue(struct workqueue_struct *wq);
1、創建新的工作隊列和相應的工作者線程,name用於該內核線程的命名。
2、類似於schedule_work,區別在於queue_work把給定工作提交給創建的工作隊列wq而不是缺省隊列。
3、延遲執行工作。
4、刷新指定工作隊列。
5、釋放創建的工作隊列。

下面一段代碼可以看作一個簡單的實作:
1
2
3
4
5
6
7
8
9
10
11
12
13
void my_func(void *data)
{
    char *name = (char *)data;
    printk(KERN_INFO 「Hello world, my name is %s!\n」, name);
}
struct workqueue_struct *my_wq = create_workqueue(「my wq」);
struct work_struct my_work;
INIT_WORK(&my_work, my_func, 「Jack」);
queue_work(my_wq, &my_work);
destroy_workqueue(my_wq);


II2.6.20~2.6.??


2.6.20起,工作隊列的數據結構發生了一些變化,使用時不能沿用舊的方法。

數據結構:
1
2
3
4
5
6
7
typedef void (*work_func_t)(struct work_struct *work);
struct work_struct {
    atomic_long_t data;
    struct list_head entry;
    work_func_t func;
};
2.6.19之前的版本相比,work_struct瘦身不少。粗粗一看,entry和之前的版本相同,funcdata發生了變化,另外並無其他的變量。
entry我們不去過問,這個和以前的版本完全相同。data的類型是atomic_long_t,這個類型從字面上看可以知道是一個原子類型。第一次看到這個變量時,很容易誤認為和以前的data是同樣的用法,只不過類型變了而已,其實不然,這裡的data是之前版本的pendingwq_data的複合體,起到了以前的pendingwq_data的作用。
func的參數是一個work_struct指針,指向的數據就是定義funcwork_struct
看到這裡,會有兩個疑問,第一,如何把用戶的數據作為參數傳遞給func呢?以前有void *data來作為參數,現在好像完全沒有辦法做到;第二,如何實現延遲工作?目前版本的work_struct並沒有定義timer

解決第一個問題,需要換一種思路。2.6.20版本之後使用工作隊列需要把work_struct定義在用戶的數據結構中,然後通過container_of來得到用戶數據。具體用法可以參考稍後的實作。

對於第二個問題,新的工作隊列把timer拿掉的用意是使得work_struct更加單純。首先回憶一下之前版本,只有在需要延遲執行工作時才會用到timer,普通情況下timer是沒有意義的,所以之前的做法在一定程度上有些浪費資源。所以新版本中,將timerwork_struct中拿掉,然後又定義了一個新的結構delayed_work用於處理延遲執行:
1
2
3
4
struct delayed_work {
    struct work_struct work;
    struct timer_list timer;
};

下面把API羅列一下,每個函數的解釋可參考之前版本的介紹或者之後的實作:
1
2
3
4
5
6
7
8
9
10
11
INIT_WORK(struct work_struct *work, work_func_t func);
INIT_DELAYED_WORK(struct delayed_work *work, work_func_t func);
int schedule_work(struct work_struct *work);
int schedule_delayed_work(struct delayed_work *work, unsigned long delay);
struct workqueue_struct *create_workqueue(const char *name);
int queue_work(struct workqueue_struct *wq, struct work_struct *work);
int queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *work, unsigned long delay);
void flush_scheduled_work(void);
void flush_workqueue(struct workqueue_struct *wq);
int cancel_delayed_work(struct delayed_work *work);
void destroy_workqueue(struct workqueue_struct *wq);
其中,1、2、4、7和以前略有區別,其他用法完全一樣。

實作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct my_struct_t {
    char *name;
    struct work_struct my_work;
};
void my_func(struct work_struct *work)
{
    struct my_struct_t *my_name = container_of(work, struct my_struct_t, my_work);
    printk(KERN_INFO 「Hello world, my name is %s!\n」, my_name->name);
}
struct workqueue_struct *my_wq = create_workqueue(「my wq」);
struct my_struct_t my_name;
my_name.name = 「Jack」;
INIT_WORK(&(my_name.my_work), my_func);
queue_work(my_wq, &(my_name.my_work));
destroy_workqueue(my_wq);