日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

Linux那些事儿 之 戏说USB(22)设备的生命线(五)

發布時間:2023/11/27 生活经验 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux那些事儿 之 戏说USB(22)设备的生命线(五) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
下面接著看那三個基本點。
第一個基本點,usb_alloc_urb函數,創建urb的專用函數,為一個urb申請內存并做初始化,在drviers/usb/core/urb.c里定義。
struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags)
{struct urb *urb;urb = kmalloc(sizeof(struct urb) +iso_packets * sizeof(struct usb_iso_packet_descriptor),mem_flags);if (!urb) {printk(KERN_ERR "alloc_urb: kmalloc failed\n");return NULL;}usb_init_urb(urb);return urb;
}
這函數長的很讓人提神,是個親民的角色。它只做了兩件事情,拿kmalloc來為urb申請內存,然后調用usb_init_urb進行初始化。usb_init_urb函數在前面說struct urb中的引用計數的時候已經貼過了,它主要的作用就是初始化urb的引用計數,還用memset順便把這里給urb申請的內存清零。
沒什么說的了么?usb_alloc_urb說:別看我簡單,我也是很有內涵的。先看第一個問題,它的第一個參數iso_packets,表示的是struct urb結構最后那個變長數組iso_frame_desc的元素數目,也就是應該與number_of_packets的值相同,所以對于控制/中斷/批量傳輸,這個參數都應該為0。這也算是給咱們示范了下變長數組咋個用法,內核里到處都是C的示范工程。
第二個問題是參數mem_flags的類型gfp_t,早幾個版本的內核,這里還是int,當然這里變成gfp_t是因為kmalloc參數里的那個標志參數的類型從int變成gfp_t了,你要用kmalloc來申請內存,就得遵守它的規則。不過這里要說的不是kmalloc,而是gfp_t,它在江湖上也沒出現多久,名號還沒打出來,很多人不了解,咱們來調查一下它的背景。它在include/linux/types.h里定義
typedef unsigned __bitwise__ gfp_t;
很顯然,要了解gfp_t,關鍵是要了解__bitwise__,它在include/uapi/linux/types.h里定義
#ifdef __CHECKER__
#define __bitwise__ __attribute__((bitwise))
#else
#define __bitwise__
#endif
__bitwise__的含義又取決于是否定義了__CHECKER__,如果沒有定義__CHECKER__,那__bitwise__就啥也不是。哪里定義了__CHECKER__?穿別人的鞋,走自己的路,讓他們去找吧,咱們不找,因為內核代碼里就沒有哪個地方定義了__CHECKER__,它是有關Sparse工具的,內核編譯時的參數決定了是不是使用Sparse工具來做類型檢查。那Sparse又是什么?它是一種靜態分析工具(static analysis tool), 用于在linux內核源代碼中發現各種類型的漏洞,一直都是比較神秘的角色,最初由Linus Torvalds寫的,后來linus沒有繼續維護,直到去年的冬天,它才又有了新的維護者Josh Triplett。有關Sparse再多的東東,咱們還是各自去研究吧,這里不多說了。
可能還會有第三個問題,usb_alloc_urb也沒做多少事啊,它做的那些咱們自己很容易就能做了,為什么還說驅動里一定要使用它來創建urb那?按照C++的說法,它就是urb的構造函數,構造函數是創建對象的唯一方式,你抬杠說C++里面兒使用位拷貝去復制一個簡單對象給新對象就沒使用構造函數,那是你不知道,C++的ARM里將這時的構造函數稱為trivial copy constructor。再說,現在它做這些事兒,以后還是做這些么?它將創建urb的工作給包裝了,咱們只管調用就是了,孫子兵法里有,以不變應萬變。
對應的,當然還會有個析構函數,銷毀urb的,也在urb.c里定義
void usb_free_urb(struct urb *urb)
{if (urb)kref_put(&urb->kref, urb_destroy);
}
usb_free_urb更瀟灑,只調用kref_put將urb的引用計數減一,減了之后如果變為0,也就是沒人再用它了,就調用urb_destroy將它銷毀掉。
接著看第二個基本點,usb_fill_control_urb函數,初始化剛才創建的控制urb,你要想使用urb進行usb傳輸,不是光為它申請點內存就夠的,你得為它初始化,充實點實實在在的內容。它是在include/linux/usb.h里定義的內聯函數
static inline void usb_fill_control_urb(struct urb *urb,struct usb_device *dev,unsigned int pipe,unsigned char *setup_packet,void *transfer_buffer,int buffer_length,usb_complete_t complete_fn,void *context)
{urb->dev = dev;urb->pipe = pipe;urb->setup_packet = setup_packet;urb->transfer_buffer = transfer_buffer;urb->transfer_buffer_length = buffer_length;urb->complete = complete_fn;urb->context = context;
}
這個函數長的就讓人興奮,純粹是來增長咱們自信的,自信多一分,成功就多十分,你就能搞懂內核,你就能成為一個成功的男人。這個函數基本上都是賦值語句,把你在參數里指定的值充實給剛剛創建的urb,urb的元素有很多,這里只是填充了一部分,剩下那些不是控制傳輸管不著的,就是自有安排可以不用去管的。

你想想,一個struct urb結構要應付四種傳輸類型,每種傳輸類型總會有點自己特別的要求,總會有些元素專屬于某種傳輸類型,而其它傳輸類型不用管的。如果按C++的做法,這稱不上是一個好的設計思想,應該有個基類urb,里面放點兒四種傳輸類型公用的,比如pipe,transfer_buffer等,再搞幾個子類,control_urb,bulk_urb等等,專門應付具體的傳輸類型,如果不用什么虛函數,實際的時間空間消耗也不會增加什么。但是實在沒必要這么搞,這年頭兒內核的結構已經夠多了,你創建什么類型的urb,就填充相關的一些字段好了,況且寫usb core的哥們兒已經給咱們提供了不同傳輸類型的初始化函數,就像上面的usb_fill_control_urb,對于批量傳輸有usb_fill_bulk_urb,對于中斷傳輸有usb_fill_int_urb,一般來說這也就夠了,下面就看看usb_fill_control_urb函數的這倆孿生兄弟。

include/linux/usb.h

static inline void usb_fill_bulk_urb(struct urb *urb,struct usb_device *dev,unsigned int pipe,void *transfer_buffer,int buffer_length,usb_complete_t complete_fn,void *context)
{urb->dev = dev;urb->pipe = pipe;urb->transfer_buffer = transfer_buffer;urb->transfer_buffer_length = buffer_length;urb->complete = complete_fn;urb->context = context;
}
static inline void usb_fill_int_urb(struct urb *urb,struct usb_device *dev,unsigned int pipe,void *transfer_buffer,int buffer_length,usb_complete_t complete_fn,void *context,int interval)
{urb->dev = dev;urb->pipe = pipe;urb->transfer_buffer = transfer_buffer;urb->transfer_buffer_length = buffer_length;urb->complete = complete_fn;urb->context = context;if (dev->speed == USB_SPEED_HIGH || dev->speed == USB_SPEED_SUPER) {/* make sure interval is within allowed range */interval = clamp(interval, 1, 16);urb->interval = 1 << (interval - 1);} else {urb->interval = interval;}urb->start_frame = -1;
}
負責批量傳輸的usb_fill_bulk_urb和負責控制傳輸的usb_fill_control_urb的相比,只是少初始化了一個setup_packet,因為批量傳輸里沒有Setup包的概念,中斷傳輸里也沒有,所以usb_fill_int_urb里也沒有初始化setup_packet這一說。不過usb_fill_int_urb比那兩個還是多了點兒內容的,因為它有個interval,比控制傳輸和批量傳輸多了個表達自己期望的權利,17行還做了次判斷,如果是高速就怎么怎么著,否則又怎么怎么著,主要是高速和低速/全速的間隔時間單位不一樣,低速/全速的單位為幀,高速的單位為微幀,還要經過2的(bInterval-1)次方這么算一下。至于26行start_frame,它是給等時傳輸用的,這里自然就設置為-1,關于為什么既然start_frame是等時傳輸用的這里還要設置那么一下,你往后看吧,現在我也不知道。
作為一個共產主義接班人,我們很快就能發現usb_fill_control_urb的孿生兄弟里,少了等時傳輸對應的那個初始化函數,三缺一啊,在哪里都會是個遺憾。不是不想有,而是沒辦法,對于等時傳輸,urb里是可以指定進行多次傳輸的,你必須一個一個的對那個變長數組iso_frame_desc里的內容進行初始化,沒人幫得了你。難道你能想出一個辦法搞出一個適用于各種情況等時傳輸的初始化函數?我是不能。如果想不出來,使用urb進行等時傳輸的時候,還是老老實實的對里面相關的字段一個一個的填內容吧。如果想找個例子旁觀一下別人是咋初始化的,可以去找個攝像頭驅動,或者其它usb音視頻設備的驅動看看,內核里也有一些的。
現在,你應該還記得咱們是因為要設置設備的地址,讓設備進入Address狀態,調用了usb_control_msg,才走到這里遇到usb_fill_control_urb的,參數里的setup_packet就是之前創建和賦好值的struct usb_ctrlrequest結構體,設備的地址已經在struct usb_ctrlrequest結構體wValue字段里了,這次控制傳輸并沒有DATA transaction階段,也并不需要urb提供什么transfer_buffer緩沖區,所以transfer_buffer應該傳遞一個NULL,當然transfer_buffer_length也就為0了,有意思的是這時候傳遞進來的結束處理函數usb_api_blocking_completion,可以看一下當這次控制傳輸已經完成,設備地址已經設置好后,接著做了些什么,它的定義在drivers/usb/core/message.c里
static void usb_api_blocking_completion(struct urb *urb)
{struct api_context *ctx = urb->context;ctx->status = urb->status;complete(&ctx->done);
}
這個函數更簡潔,就那么一句,沒有前面說的釋放urb,也沒有重新提交它,本來就想設置個地址就完事兒了,沒必要再將它提交給HCD,你就是再提交多少次,設置多少次,也只能有一個地址。那在這僅僅一句里面都做了些什么?你接著往下看。
然后就是第三個基本點,usb_start_wait_urb函數,將前面歷經千辛萬苦創建和初始化的urb提交給咱們的usb core,讓它移交給特定的主機控制器驅動進行處理,然后望眼欲穿的等待HCD回饋的結果,如果等待的時間超過了預期的限度,它不會再等,不會去變成望夫石。它在message.c里定義
static int usb_start_wait_urb(struct urb *urb, int timeout, int *actual_length)
{struct api_context ctx;unsigned long expire;int retval;init_completion(&ctx.done);urb->context = &ctx;urb->actual_length = 0;retval = usb_submit_urb(urb, GFP_NOIO);if (unlikely(retval))goto out;expire = timeout ? msecs_to_jiffies(timeout) : MAX_SCHEDULE_TIMEOUT;if (!wait_for_completion_timeout(&ctx.done, expire)) {usb_kill_urb(urb);retval = (ctx.status == -ENOENT ? -ETIMEDOUT : ctx.status);dev_dbg(&urb->dev->dev,"%s timed out on ep%d%s len=%u/%u\n",current->comm,usb_endpoint_num(&urb->ep->desc),usb_urb_dir_in(urb) ? "in" : "out",urb->actual_length,urb->transfer_buffer_length);} elseretval = ctx.status;
out:if (actual_length)*actual_length = urb->actual_length;usb_free_urb(urb);return retval;
}

3行,定義了一個struct api_context結構體。也在drivers/usb/core/message.c文件中

struct api_context {struct completion	done;int			status;
};
包含了一個結構體struct completion,completion是內核里一個比較簡單的同步機制,一個線程可以通過它來通知另外一個線程某件事情已經做完了。你使用某個下載軟件去下載A片,然后撇一邊兒不管就去忙著聊QQ泡mm了,下載完了那個軟件會通知你,然后你想怎么做就怎么做,自個看也成,不怕被扁和正在聊的mm一塊看也成,沒人會去管你。怎么?你的那個軟件下載完了也沒通知你?那就緊趕的換個別的吧,寫那個軟件的也太沒職業道德了,該做的事情不做。completion機制也同樣是這么回事兒,你的代碼執行到某個地方,需要再忙點兒其它的,就新開個線程,讓它去忙活,然后自個接著忙自己的,想知道那邊兒忙活的結果了,就停在某個地方等著,那邊兒忙活完了會通知一下已經有結果了,于是你的代碼又可以繼續往下走。

completion機制圍繞struct completion結構去實現,有兩種使用方式,一種是通過DECLARE_COMPLETION宏在編譯時就創建好struct completion的結構體,另外一種就是上面的形式,運行時才創建的,先定義一個struct completion結構體,然后在7行使用init_completion去初始化。光是創建struct completion的結構體沒用,關鍵的是如何通知任務已經完成了,和怎么去等候完成的好消息。片子下載完了可能會用聲音、對話框等多種方式來通知你,同樣這里用來通知已經完成了的函數也不只一個,
void complete(struct completion *c);
void complete_all(struct completion *c);
complete只通知一個等候的線程,complete_all可以通知所有等候的線程,大家都一個宿舍的好兄弟,你總不好意思自己藏著好東西,不讓大家欣賞吧,所以可能會有多個人來等著片子下完。
你不可能會毫無限度的等下去,21世紀最缺的是什么?耐心,凡事都有個限度,即使片子再精彩,多會兒下不完也不等它了,當然會有比我還窮極無聊的人愿意一直在那里等著,畢竟林子大了什么鳥兒都有,或者說正等著那,一個ppmm過來打斷你,你趕著花前月下去了,也不會去繼續等了。所以針對不同的情況,等候的方式就有好幾種,都在kernel/sched/completion.c里定義
void wait_for_completion(struct completion *);
int wait_for_completion_interruptible(struct completion *x);
unsigned long wait_for_completion_timeout(struct completion *x,unsigned long timeout);
long wait_for_completion_interruptible_timeout(struct completion *x, unsigned long timeout);
上面15行使用的就是wait_for_completion_timeout,設定一個時間限度,然后在那里候著,直到得到通知,或者超過時間。既然有等的一方,也總得有通知的一方吧,不然豈不是每次都超時?寫代碼的哥們兒沒這么變態,記得上面剛出現過的那個結束處理函數usb_api_blocking_completion不?不是吧,被窩都熱乎著就不認人了啊,它里面唯一的一句complete就是用來通知這里的15行的。有疑問的話看7行,將剛剛初始過的struct api_context 結構體 ctx 的地址賦值給了urb,15行等的就是這個done。再看10行, usb_submit_urb函數將這個控制urb提交給usb core,它是異步的,也就是說提交了之后不會等到傳輸完成了才返回。
現在就比較清晰了,usb_start_wait_urb函數將urb提交給usb core去處理后,就停在15行等候usb core和HCD的處理結果,而這個urb代表的控制傳輸完成之后會調用結束處理函數usb_api_blocking_completion,從而調用complete來通知usb_start_wait_urb說不用再等了,傳輸已經完成了,當然還有種可能是usb_start_wait_urb已經等的超過了時間限度仍然沒有接到通知,不管是哪種情況,usb_start_wait_urb都可以不用再等,繼續往下走了。
下邊兒挨個說一下,10行,提交urb,并返回一個值表示是否提交成功了,顯然,成功的可能性要遠遠大于失敗的可能性,不然就是或者寫代碼的哥們兒有問題,或者就是我有問題,所以接下來的判斷加上了unlikely,如果你真的那么衰,遇上了小概率事件,那也就沒必要在15行等通知了,直接到后邊兒去吧。
14行,計算超時值。超時值在參數里不是已經給了么,還計算什么?沒錯,你是在參數里是指定了自己能夠忍受的最大時間限度,不過那是以ms為單位的,作為一個平頭小百姓,咱們的時間概念里也只有分鐘啊妙啊毫秒啊什么的,不過作為一個要在linux里混的平頭小百姓,咱們的時間概念里必須得加上一個號稱jiffy的東東,因為函數wait_for_completion_timeout里的超時參數是必須以jiffy為單位的。
jiffy,金山詞霸告訴我們,是瞬間,短暫的時間跨度,短暫到什么程度?linux里它表示的是兩次時鐘中斷的間隔,時鐘中斷是由定時器周期性產生的,關于這個周期,內核里有個巨直白巨形象的變量來描述,就是HZ,它是個體系結構相關的常數。內核里還提供了專門的計數器來記錄從系統引導開始所度過的jiffy值,每次時鐘中斷發生時,這個計數器就增加1。
既然你指定的時間和wait_for_completion_timeout需要的時間單位不一致,就需要轉換一下,msecs_to_jiffies函數可以完成這個工作,它將ms值轉化為相應的jiffy值。這一行里還剩個MAX_SCHEDULE_TIMEOUT比較陌生,在include/linux/sched.h里它被定義為LONG_MAX,最大的長整型值,我知道你會好奇這個LONG_MAX是怎么來的,好奇就說出來嘛,好奇又不會真的害死貓,我也很好奇,所以咱們到生它養它的include/linux/kernel.h里看看
#define INT_MAX		((int)(~0U>>1))
#define INT_MIN		(-INT_MAX - 1)
#define UINT_MAX	(~0U)
#define LONG_MAX	((long)(~0UL>>1))
#define LONG_MIN	(-LONG_MAX - 1)
#define ULONG_MAX	(~0UL)
#define LLONG_MAX	((long long)(~0ULL>>1))
#define LLONG_MIN	(-LLONG_MAX - 1)
#define ULLONG_MAX	(~0ULL)
各種整型數的最大值最小值都在這里了,俺現在替譚浩強再普及點基礎知識,‘~’是按位取反,‘UL’是無符號長整型,那么‘ULL’就是64位的無符號長整型,‘<<’左移運算的話就是直接一股腦的把所有位往左邊兒移若干位,‘>>’右移運算比較容易搞混,主要是牽涉到怎么去補缺,有關空缺兒的事情在哪里都會比較的復雜,勾心斗角階級斗爭的根源,在C里主要就是無符號整數和有符號整數的之間的沖突,在你不管三七二十一一直往右移多少位之后,空出來的那些空缺,對于無符號整數得補0,對于有符號的,得補上符號位。
還是拿LONG_MAX來說事兒,上邊定義為((long)(~0UL>>1)),0UL按位取反之后全為1的無符號長整型,向右移1位,左邊兒空出來的那個補0,這個數對于無符號的long來說不算什么,但是再經過long這么強制轉化一下變為有符號的長整型,它就是老大了。每個老大的成長過程都是一部血淚史,都要歷經很多曲折。
現在你可以很明白的知道寫代碼的哥們兒在14行都做了些什么,你指定的超時時間被轉化為相應的jiffy值,或者直接被設定為最大的long值。
15行,等待通知,我們需要知道的是怎么去判斷等待的結果,也就是wait_for_completion_timeout的返回值代表什么意思?一般來說,一個函數返回了0代表了好消息,一切順利,如果你這么想那可就錯了。wait_for_completion_timeout返回0恰恰表示的是壞消息,表示直到超過了自己的忍耐的極限仍沒有接到任何的回饋,而返回了一個大于0的值則表示接到通知了,那邊兒不管是完成了還是出錯了總歸是告訴這邊兒不用再等了,這個值具體的含義就是距你設定的時限提前了多少時間。為什么會這樣?你去看看wait_for_completion_timeout的定義就知道了,我就不貼了,它是通過schedule_timeout來實現超時的,schedule_timeout的返回值就是這么個意思。
那么現在就很明顯了,如果超時了,就打印一些調試信息提醒一下,然后調用usb_kill_urb終止這個urb,再將返回值設定一下。如果收到了通知,就簡單了,直接設定了返回值,就接著往下走。
30行,actual_length是用來記錄實際傳輸的數據長度的,是上頭兒的上頭兒usb_control_msg需要的。不要給我說這個值urb里本來就有保存,何必再多次一舉找個地兒去放,沒看接下來的32行就用usb_free_urb把這個urb給銷毀了啊,到那時花非花樹非樹,urb也已經不是urb,哪里還去找這個值。actual_length是從上頭兒那里傳遞過來的一個指針,寫內核的哥們兒教導我們,遇到指針一定要小心再小心啊,同志們。所以這里要先判斷一下actual_length是不是空的。

現在,只剩一個usb_submit_urb在剛才被有意無意的飄過了,咱們下面說。

總結

以上是生活随笔為你收集整理的Linux那些事儿 之 戏说USB(22)设备的生命线(五)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。