嵌入式Linux開發-USB驅動

来源:https://www.cnblogs.com/abit-ll/p/18022232
-Advertisement-
Play Games

0.前言 哥們馬上就要被裁了,總得整理一下技術方面的積累,準備開始下一輪的面試和找工作之旅了。。。。 1.概述 通用串列匯流排(USB)是主機和外圍設備之間的一種連接。 從拓撲上來看,是一顆由幾個點對點的連接構建而成的樹。這些連接是連接設備和集線器(hub)的四線電纜(底線、電源線和兩根信號線)。US ...


0.前言

哥們馬上就要被裁了,總得整理一下技術方面的積累,準備開始下一輪的面試和找工作之旅了。。。。

1.概述

通用串列匯流排(USB)是主機和外圍設備之間的一種連接。
從拓撲上來看,是一顆由幾個點對點的連接構建而成的樹。這些連接是連接設備和集線器(hub)的四線電纜(底線、電源線和兩根信號線)。USB主控制器(host controller)負責詢問每一個USB設備是否有數據需要發送。
Linux內核支持兩種主要類型的USB驅動程式:宿主(host)系統上的驅動程式和設備(device)上的驅動程式。宿主系統上的USB驅動程式控制插入其中的USB設備,USB設備的驅動程式控制該設備如何作為一個USB設備和主機通信。
USB驅動程式存在於不同的內核子系統和USB硬體控制器之中,USB核心為USB驅動程式提供了一個用於訪問和控制USB硬體的介面。
image

2.USB設備基礎

USB設備的構成,包括配置、介面和端點,以及USB驅動程式如何綁定到USB介面上,而不是整個USB設備
image

  • 端點
    USB通信最基本的形式是通過一個名為端點(endpoint)的東西。USB端點只能往一個方向傳送數據,從主機到設備(稱為輸出端點)或者從設備到主機(稱為輸入端點)。端點可以看作是單向的管道。
    有四種不同類型的端點:
  • 控制
    用來控制對USB設備不同部分的訪問。通常用於配置設備、獲取設備信息、發送命令到設備或者獲取設備的狀態報告。每個USB設備都有一個名為“端點0”的控制端點,USB核心使用該端點在插入時進行設備的配置。
  • 中斷
    當USB宿主要求設備傳輸數據時,中斷端點就以一個固定的速率來傳送少量的數據。通常還用於發送數據到USB設備以控制設備,一般不用來傳輸大量的數據。
  • 批量
    傳輸大批量的數據。常見於需要確保沒有數據丟失的傳輸的設備。如果匯流排上的空間不足以發送整個批量包,它將被分割為多個包進行傳輸。
  • 等時
    同樣可以傳送大批量的數據,但數據是否到達是沒有保證的。實時的數據收集(例如音頻和視頻設備)幾乎毫無例外都使用這類端點。
    控制和批量端點用於非同步的數據傳輸。中斷和等時端點是周期性的。
    內核使用struct usb_host_endpoint結構體來描述USB端點。該結構體在另一個struct usb_endpoint_descriptor的結構體中包含真正的端點信息。
    bEndpointAddress,特定端點的USB地址。還包含了端點的方向。可以結合位掩碼USB_DIR_OUT和USB_DIR_IN,以確定該端點的數據是傳向設備還是主機。
    bmAttributes,端點的類型。結合位掩碼USB_ENDPOINT_XFERTYPE_MASK,以確定此端點的類型是USB_ENDPOINT_XFER_ISOC(等時)、USB_ENDPOINT_XFER_BULK(批量)還是USB_ENDPOINT_XFER_INT(中斷)。
    wMaxPacketSize,該端點一次可以處理的最大位元組數。驅動程式可以發送數量大於此值的數據到端點,在實際傳輸到設備時,數據將被分割為wMaxPacketSize大小的塊。
    bInterval,如果端點是中斷類型,該值是端點的間隔設置。
  • 介面
    USB介面只處理一種USB邏輯連接。內核使用struct usb_interface結構體來描述USB介面。USB核心把該結構體傳遞給USB驅動程式,之後由USB驅動程式來負責控制該結構體。
    struct usb_host_interface *altsetting,一個介面結構體數組,包含所有可能用於該介面的可選設置。
    unsigned num_altsetting,altsetting指針所指的可選設置的數量。
    struct usb_host_interface *cur_altsetting,指向altsetting數組內部的指針,表示該介面的當前活動設置。
    int minor,如果捆綁到該介面的USB驅動程式使用USB主設備號,這個變數包含USB核心分配給該介面的次設備號,僅在一個成功的usb_register_dev調用之後才有效。
  • 配置
    一個USB設備可以有多個配置,可以在配置之間切換以改變設備的狀態,而一個時刻只能激活一個配置。
    Linux使用struct usb_host_config結構體來描述USB配置,使用struct usb_device結構體來描述整個USB設備。
    USB設備驅動程式需要把一個給定的struct usb_interface結構體的數據轉換為struct usb_device結構體,用於轉換功能的函數是interface_to_usbdev。
    USB設備由許多不同的邏輯單元構成,邏輯單元之間的關係:
    設備通常具有一個或者更多的配置
    配置經常具有一個或者更多的介面
    介面通常具有一個或者更多的設置
    介面沒有或者具有一個以上的端點

3.USB urb

Linux內核中的USB代碼通過一個稱為urb(USB請求塊)的東西與所有的USB設備通信,使用struct urb結構體來描述這個請求塊。
urb被用來以一種非同步的方式往/從特定的USB設備上的特定USB端點發送/接收數據。
一個urb的典型生命周期:
由USB設備驅動程式創建。
分配給一個特定的USB設備的特定端點。
由USB設備驅動程式遞交到USB核心。
由USB核心遞交到特定設備的特定USB中控制器驅動程式。
由USB主控制器驅動程式處理,從設備進行USB傳送。
當urb結束之後,USB主控制器驅動程式通知USB設備驅動程式。

  • 創建和銷毀urb
    必須使用usb_alloc_urb函數來創建。
    struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);
    iso_packets,是該urb應該包含的等時數據包的數量,如果不打算創建等時urb,該值為0。
    mem_flags,和傳遞給用於從內核分配記憶體的kmalloc函數的標誌有相同的類型。
    如果成功為urb分配了足夠的記憶體空間,指向該urb的指針將被返回給調用函數,如果返回為NULL,表示USB核心發生了錯誤。
    必須調用usb_free_urb函數告訴USB核心驅動程式已經使用完urb。
    void usb_free_urb(struct urb *urb);
    urb,指向所需釋放的struct urb的指針,在該函數被調用後,urb結構體就會消失,驅動程式不能再訪問它。
    中斷urb
    usb_fill_int_urb用來正確地初始化即將被髮送到USB設備的中斷端點的urb。
    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);
    struct urb *urb,指向需要初始化的urb的指針。
    struct usb_device *dev,該urb所發送的目標USB設備。
    unsigned int pipe,該urb所發送的目標USB設備的特定端點。使用usb_sndintpipe或者usb_rcvintpipe函數創建。
    void *transfer_buffer,用來保存外發數據或者接收數據的緩衝區的指針。必須使用kmalloc調用來創建。
    int buffer_length,transfer_buffer指針所指向的緩衝區的大小。
    usb_complete_t complete_fn,當該urb結束之後調用的結束處理常式的指針。
    void *context,指向一小數據塊,該塊被添加到urb結構體中以便進行結束處理常式後面的查找。
    int interval,該urb應該被調度的間隔。
    批量urb
    使用的函數usb_fill_bulk_urb。
    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);
    參數和usb_fill_int_urb函數一樣,不過沒有時間間隔參數,pipe變數使用usb_sndbulkpipe或usb_rcvbulkpipe函數來初始化。
    控制urb
    調用usb_fill_control_urb函數。
    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);
    參數和usb_fill_bulk_urb函數一樣,setup_packet指向即將被髮送到端點的設備數據包的數據,pipe變數使用usb_sndctrlpipe或usb_rcvctrlpipe函數來初始化。
    等時urb
    必須在驅動程式中“手工地”進行初始化。
  • 提交urb
    一旦urb被USB驅動程式正確地創建和初始化之後,就可以提交到USB核心以發送到USB設備了。通過調用usb_submit_urb函數來完成。
    int usb_submit_urb(struct urb *urb, gfp_t mem_flags);
    urb參數指向即將被髮送到設備的urb的指針。
    mem_flags參數等同於傳遞給kmalloc調用的同一個參數,用於告訴USB核心如何在此時及時地分配記憶體緩衝區。
  • 結束urb:結束回調處理常式
    如果調用usb_submit_urb成功,把對urb的控制轉交給USB核心,該函數返回0,否則,返回負的錯誤號。如果函數調用成功,當 urb結束的時候,usb的結束處理常式(由結束函數指針指定)正好被調用一次。
    只有三種結束urb和調用結束函數的情形:
    urb被成功地發送到了設備,設備返回了正確的確認。urb中的status變數被設置為0。
    發送數據到設備或者從設備接收數據時發生了錯誤。錯誤情況由urb結構體中的status變數的錯誤值來指示。
    urb從USB核心中被“解開鏈接”。
  • 取消urb
    調用usb_kill_urb或usb_unlink_urb函數來終止一個已經被提交到USB核心的urb。
    void usb_kill_urb(struct urb *urb);
    int usb_unlink_urb(struct urb *urb);
    urb的參數指向即將被取消的urb的指針。
    如果調用usb_kill_urb函數,該urb的生命周期將被終止。通常是當設備從系統中被斷開時,在斷開回調函數中調用該函數。
    對於某些驅動程式而言,應該使用usb_unlink_urb函數來告訴USB核心終止一個urb。該函數並不等到urb完全被終止後才返回到調用函數。

4.USB驅動程式

驅動程式把驅動程式對象註冊到USB子系統中,稍後再使用製造商和設備標識來判斷是否已經安裝了硬體。
USB核心使用struct usb_device_id結構體來判斷對於一個設備該使用哪一個驅動程式,熱插拔腳本使用它來確定當一個特定的設備插入到系統時該自動裝載哪一個驅動程式

 struct usb_device_id {
	__u16		match_flags;      /*確定設備和結構體中下列欄位中的哪一個相匹配*/
	__u16		idVendor;         /*設備的USB製造商ID*/
	__u16		idProduct;        /*設備的USB產品ID*/
	/*定義了製造商指派的產品的版本號範圍的最低和最高值。bcdDevice_hi值包括在內,該值是最高編號的設備的編號*/
	__u16		bcdDevice_lo;     
	__u16		bcdDevice_hi;
    /*分別定義設備的類型、子類型和協議。這些值詳細說明瞭整個設備的行為,包括該設備上的所有介面*/
	__u8		bDeviceClass;
	__u8		bDeviceSubClass;
	__u8		bDeviceProtocol;
    /*分別定義了類型、子類型和單個介面的協議*/
	__u8		bInterfaceClass;
	__u8		bInterfaceSubClass;
	__u8		bInterfaceProtocol;

	__u8		bInterfaceNumber;
	/*不是用來比較是否匹配的,包含了驅動程式在USB驅動程式的探測回調函數中可以用來區分不同設備的信息*/
	kernel_ulong_t	driver_info
		__attribute__((aligned(sizeof(kernel_ulong_t))));
};

有許多用來初始化該結構體的巨集:
USB_DEVICE(vend, prod),僅和指定的製造商和產品ID值相匹配。用於需要一個特定驅動程式的USB設備。
USB_DEVICE_VER(vend, prod, lo, hi),僅和某版本範圍內的指定製造商和產品ID值相匹配。
USB_DEVICE_INFO(cl, sc, pr),僅和USB設備的指定類型相匹配。
USB_INTERFACE_INFO(cl, sc, pr),僅和USB介面的指定類型相匹配。

  • 註冊USB驅動程式
    必須創建的主結構體是struct usb_driver,必須由USB驅動程式來填寫,包括許多回調函數和變數,向USB核心代碼描述了USB驅動程式。
    struct usb_driver {
    	const char *name;    /*指向驅動程式名字的指針。在內核的所有USB驅動程式中必須是唯一的,通常被設置為和驅動程式模塊名相同的名字。*/
    
    	int (*probe) (struct usb_interface *intf,
    		      const struct usb_device_id *id);  /*指向USB驅動程式中的探測函數的指針。*/
    
    	void (*disconnect) (struct usb_interface *intf); /*指向USB驅動程式中的斷開函數的指針。當struct usb_interface被從系統中移除或者驅動程式正在從USB核心中卸載時,USB核心將調用該函數。*/
    
    	int (*unlocked_ioctl) (struct usb_interface *intf, unsigned int code,
    			void *buf);
    
    	int (*suspend) (struct usb_interface *intf, pm_message_t message);
    	int (*resume) (struct usb_interface *intf);
    	int (*reset_resume)(struct usb_interface *intf);
    
    	int (*pre_reset)(struct usb_interface *intf);
    	int (*post_reset)(struct usb_interface *intf);
    
    	const struct usb_device_id *id_table;   /*指向struct usb_device_id表的指針,包含了一系列驅動程式可以支持的所有不同類型的USB設備。*/
    
    	struct usb_dynids dynids;
    	struct usbdrv_wrap drvwrap;
    	unsigned int no_dynamic_id:1;
    	unsigned int supports_autosuspend:1;
    	unsigned int disable_hub_initiated_lpm:1;
    	unsigned int soft_unbind:1;
    };
    
    以struct usb_driver指針為參數的usb_register_driver函數調用把struct usb_driver註冊到USB核心。
    當USB驅動程式將要被卸載時,需要把struct usb_drvier從內核中註銷,通過調用usb_deregister_driver來完成該工作。
  • 探測和斷開
    探測和斷開回調函數都是在USB集線器內核線程的上下文中被調用的,因此在其中睡眠是合法的。
    在探測回調函數中,USB驅動程式應該初始化人任何可能用於控制USB設備的局部結構體,還應該把所需的任何設備相關信息保存到局部結構體中。
    USB驅動程式需要在設備生命周期的稍後時間獲取和該結構體struct usb_interface相關聯的局部數據結構體,可以調用usb_set_intfdata函數:
    void usb_set_intfdata(struct usb_interface *intf, void *data);
    該函數接受一個指向任意數據類型的指針,把它保存到struct usb_interface結構體中以便後面訪問,調用usb_get_intfdata函數來獲取數據:
    void *usb_get_intfdata(struct usb_interface *intf);
    usb_get_intfdata通常在USB驅動程式的打開函數和斷開函數中調用。
    如果USB驅動程式沒有和設備與用戶交互的另一種類型的子系統相關聯,驅動程式可以使用USB主設備號,以便在用戶空間使用傳統的字元驅動程式介面。USB驅動程式需要在探測函數中調用usb_register_dev函數來把設備註冊到USB核心。
    usb_register_dev函數需要一個指向struct usb_interface的指針和一個指向struct usb_class_driver結構的指針。
    int usb_register_dev(struct usb_interface *intf, struct usb_class_driver *class_driver);
    
    struct usb_class_driver {
    	char *name;     /*sysfs用來描述設備的名字*/
    	char *(*devnode)(struct device *dev, umode_t *mode); /*回調函數,創建設備節點*/
    	const struct file_operations *fops;  /*指向struct file_operations的指針,驅動程式定義該結構體,用它來註冊為字元設備*/
    	int minor_base;  /*為驅動程式指派的次設備號範圍的開始值*/
    };
    
    當一個USB設備被斷開時,和該設備相關聯的所有資源都應該被儘可能的清理掉。必須調用usb_deregister_dev函數把次設備號交還USB核心。
    在斷開函數中,從介面獲取之前調用usb_set_intfdata設置的任何數據也是很重要的,然後設置struct usb_interface結構體中的數據指針為NULL。
    在USB設備已經被斷開之後,如果驅動程式通過調用usb_submit_urb來提交一個urb給它,提交將會失敗並返回錯誤值-EPIPE。
  • 提交和控制urb
    當驅動程式有數據要發送到USB設備時,必須分配一個urb來把數據傳輸給設備(struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags)),在urb被成功分配之後,創建一個DMA緩衝區以最高效的方式發送數據到設備,傳遞給驅動程式的數據複製到該緩衝區中(void *usb_alloc_coherent(struct usb_device *dev, size_t size, gfp_t mem_flags, dma_addr_t *dma))。一旦數據從用戶空間正確的複製到局部緩衝區中,urb必須在可以被提交給USB核心之前被正確地初始化(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)),然後就可以被提交給USB核心以傳輸到設備(int usb_submit_urb(struct urb *urb, gfp_t mem_flags))。
    /* 創建一個urb */
    urb = usb_alloc_urb(0, GFP_KERNEL);
    if (!urb) {
    	retval = -ENOMEM;
    	goto error;
    }
    /* 創建DMA緩衝區,並把數據拷貝到緩衝區 */
    buf = usb_alloc_coherent(dev->udev, writesize, GFP_KERNEL,
    &urb->transfer_dma);
    if (!buf) {
    	retval = -ENOMEM;
    	goto error;
    }
    
    if (copy_from_user(buf, user_buffer, writesize)) {
    retval = -EFAULT;
    goto error;
    }
    
    /* this lock makes sure we don't submit URBs to gone devices */
    mutex_lock(&dev->io_mutex);
    if (!dev->interface) {		/* disconnect() was called */
    	mutex_unlock(&dev->io_mutex);
    	retval = -ENODEV;
    	goto error;
    }
    
    /* 初始化urb */
    usb_fill_bulk_urb(urb, dev->udev,
    			usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),
    			buf, writesize, skel_write_bulk_callback, dev);
    urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
    usb_anchor_urb(urb, &dev->submitted);
    
    /* 把數據從批量埠發出 */
    retval = usb_submit_urb(urb, GFP_KERNEL);
    mutex_unlock(&dev->io_mutex);
    if (retval) {
    	dev_err(&dev->interface->dev,
    	"%s - failed submitting write urb, error %d\n",
    	__func__, retval);
    	goto error_unanchor;
    }
    
    在urb被成功傳輸到USB設備之後(或者傳輸沖發生了某些事情),urb回調函數將被USB核心調用。
    static void skel_write_bulk_callback(struct urb *urb)
    {
    	struct usb_skel *dev;
    
    	dev = urb->context;
    
    	/* sync/async 解鏈接故障不是錯誤 */
    	if (urb->status) {
    		if (!(urb->status == -ENOENT ||
    		    urb->status == -ECONNRESET ||
    		    urb->status == -ESHUTDOWN))
    			dev_err(&dev->interface->dev,
    				"%s - nonzero write bulk status received: %d\n",
    				__func__, urb->status);
    
    		spin_lock(&dev->err_lock);
    		dev->errors = urb->status;
    		spin_unlock(&dev->err_lock);
    	}
    
    	/* 釋放已分配的緩衝區 */
    	usb_free_coherent(urb->dev, urb->transfer_buffer_length,
    			  urb->transfer_buffer, urb->transfer_dma);
    	up(&dev->limit_sem);
    }
    
    回調函數中做的第一件事是檢查urb的狀態,以確定該urb是否已經成功地結束。之後回調函數釋放傳輸時分配給該urb的緩衝區。
    urb回調函數是運行在中斷上下文中的,因此它不應該進行任何記憶體分配、持有任何信號量或者做任何其他可能導致進程睡眠的事情。
  • 不使用urb的USB傳輸
    有時候USB驅動程式只是要發送或者接收一些簡單的USB數據,有兩個更簡單的介面函數可以使用usb_bulk_msg和 usb_control_msg。
    usb_bulk_msg創建一個USB批量urb,把它發送到指定的設備,然後在返回調用者之前等待它的結束。
    int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe, void *data, int len, int *actual_length, int timeout);
    struct usb_device *usb_dev,指向批量消息所發送的目標USB設備的指針。
    unsigned int pipe,該批量消息所發送的目標USB設備的特定端點,調用usb_sndbulkpipe或usb_rcvbulkpipe來創建。
    void *data,如果是一個OUT端點,指向即將發送到設備的數據的指針。如果是一個IN端點,指向從設備讀取的數據應該存放的位置的指針。
    int len,data參數所指緩衝區的大小。
    int *actual_length,指向保存實際傳輸位元組數的位置的指針。
    int timeout,以jiffies為單位的應該等待的超時時間。如果該值為0,該函數將一直等待消息的結束。
    如果函數調用成功,返回值為0;否則,返回一個負的錯誤值。如果成功,actual_length參數包含從該消息發送或者接收的位元組數。
    不能在一個中斷上下文中或者在持有自旋鎖的情況下調用usb_bulk_msg函數。
    usb_control_msg,允許驅動程式發送和接收USB控制消息。
    int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request, __u8 requesttype, __u16 value, __u16 index, void *data, __u16 size, int timeout);
    struct usb_device *dev,指向控制消息所發送的目標USB設備的指針。
    unsigned int pipe,該控制消息所發送的目標USB設備的特定端點,調用usb_sndctrlpipe或usb_rcvctrlpipe來創建。
    __u8 request,控制消息的USB請求值。
    __u8 requesttype,控制消息的USB請求類型值。
    __u16 value,控制消息的USB消息值。
    __u16 index,控制消息的USB消息索引值。
    void *data,如果是一個OUT端點,指向即將發送到設備的數據的指針。如果是一個IN端點,指向從設備讀取的數據應該存放的位置的指針。
    __u16 size,data參數所指緩衝區的大小。
    int timeout,以jiffies為單位的應該等待的超時時間。如果該值為0,該函數將一直等待消息的結束。
    如果函數調用成功,返回傳輸到設備或者從設備讀取的位元組數;如果不成功,返回一個負的錯誤值。
    不能在一個中斷上下文中或者在持有自旋鎖的情況下調用usb_control_msg函數。
  • 其他USB數據函數
    USB核心中的許多輔助函數可以用來從所有USB設備中獲取標準的信息。這些函數不能在一個中斷上下文中或者持有自旋鎖的情況下調用。
    usb_get_descriptor函數從指定的設備獲取指定的USB描述符。
    int usb_get_descriptor(struct usb_device *dev, unsigned char desctype, unsigned char descindex, void *buf, int size);
    USB驅動程式可以使用該函數來從struct usb_device結構體中獲取任何沒有存在於已有struct usb_device和struct usb_interface結構體中的設備描述符。
    struct usb_device *dev,指向想要獲取設備描述符的目標USB設備的指針。
    unsigned char desctype,描述符的類型。
    unsigned char descindex,應該從設備獲取的描述符的編號。
    void *buf,指向複製描述符到其中的緩衝區的指針。
    int size,buf變數所指記憶體的大小。
    如果函數調用成功,返回從設備讀取的位元組數。否則,返回一個由該函數調用的底層的usb_control_msg函數返回的一個負的錯誤值。
    usb_get_descriptor調用更常用於從USB設備獲取一個字元串,提供usb_string的輔助函數來完成該工作。
    int usb_string(struct usb_device *dev, int index, char *buf, size_t size);
    返回從USB設備讀取的已經轉換為ISO 8859-1格式的字元串。是USB設備的字元串的典型格式。

    參考資料:
    《LINUX設備驅動程式第三版》
    linux-4.9.88

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 最近需要在伺服器上運行一些時間很長的命令,想讓伺服器自動通知我什麼時候命令完成,通過命令結束後發送郵件給我來提醒。 安裝 msmtp 和 mail # RedHat 系 sudo dnf install msmtp mailx # Debian 系 sudo apt install msmtp ma ...
  • 程式說明: 《幾何衝刺》是一款基於Scratch平臺開發的跑酷類游戲程式。在這個游戲中,玩家控制一個黃色的小方塊,在快速向前衝刺的過程中躲避各種障礙物。通過按下鍵盤上的上方向鍵,玩家可以操作小方塊進行跳躍,以避開途中的障礙。游戲的目標是儘可能讓黃色小方塊跑得更遠,挑戰玩家的反應速度和操作技巧。 小虎 ...
  • 關註我,緊跟本系列專欄文章,咱們下篇再續! 作者簡介:魔都技術專家兼架構,多家大廠後端一線研發經驗,各大技術社區頭部專家博主,編程嚴選網創始人。具有豐富的引領團隊經驗,深厚業務架構和解決方案的積累。 負責: 中央/分銷預訂系統性能優化 活動&優惠券等營銷中台建設 交易平臺及數據中台等架構和開發設計 ...
  • 洛谷試煉場的題目確實很具有代表性,但是近幾年以來,又有許多經典題目出現在 OI 界中,這個大題單就是作為洛谷試煉場的擴展和補充。 目錄新版本食用指南更新日誌題單Part 0 試機題Part 1 入門階段Part 2 基礎演算法Part 3 搜索Part 4 動態規劃Part 4.1-4.4 動態規劃P ...
  • 概述:C#軟體開發中,License扮演著確保軟體合法使用的重要角色。採用RSA非對稱加密方案,服務端生成帶簽名的License,客戶端驗證其有效性,從而實現對軟體的授權與安全保障。 License應用場景: License(許可證)在C#軟體開發中被廣泛應用,以確保軟體在合法授權的環境中運行。常見 ...
  • 概述:MVVM是一種在WPF開發中廣泛應用的設計模式,通過將應用程式分為模型、視圖、和視圖模型,實現瞭解耦、提高可維護性的目標。典型應用示例展示瞭如何通過XAML、ViewModel和數據綁定創建清晰、可測試的用戶界面。 什麼是MVVM? MVVM(Model-View-ViewModel)是一種用 ...
  • 在軟體開發中,應用程式的自動更新功能是一個重要的特性,它能讓用戶在不手動干預的情況下獲取最新的軟體版本。這不僅提高了用戶體驗,還有助於開發者及時修複潛在的問題、增加新功能,並確保軟體的安全性和穩定性。 對於.NET開發者來說,實現自動更新功能並不總是那麼簡單。幸運的是,有一個名為AutoUpdate ...
  • 隨著現代軟體對性能和響應速度的要求越來越高,非同步編程已經成為許多開發者必須掌握的技能。C# 提供了多種實現非同步編程的方式,每種方式都有其特定的適用場景和優缺點。本文將詳細介紹 C# 中實現非同步編程的常用方式,幫助讀者更好地理解並選擇合適的非同步編程方法。 一、Task 和 Task C# 5.0 引入 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...