基於tiny4412的Linux內核移植 --- 實例學習中斷背後的知識(2)

来源:http://www.cnblogs.com/pengdonglin137/archive/2017/01/26/6350308.html
-Advertisement-
Play Games

作者:彭東林 郵箱:[email protected] QQ:405728433 平臺 tiny4412 ADK Linux-4.9 概述 前面一篇博文基於tiny4412的Linux內核移植 實例學習中斷背後的知識(1)結合示例分析了一下新版kernel引入設備樹和irq domain後 ...


作者:彭東林

郵箱:[email protected]

QQ:405728433

 

平臺

tiny4412 ADK

Linux-4.9

 

概述

前面一篇博文基於tiny4412的Linux內核移植 --- 實例學習中斷背後的知識(1)結合示例分析了一下新版kernel引入設備樹和irq domain後中斷幕後的一些知識,其中的示例只是使用gpio中斷的一種方式,此外,還有一種,就像博文

基於tiny4412的Linux內核移植--- 中斷和GPIO學習(1)中描述的那樣,這種實現方式又是如何進行的呢?下麵還是結合示例的方式分析。

正文

框圖可以參考前一篇博文。

在前一篇博文的第三部分 GPIO控制器驅動中有一個函數我們沒有分析,就是samsung_gpiolib_register,把這函數看懂了,後面的分析就順了,下麵的分析最好結合前一篇博文的第三部分 GPIO控制器驅動一塊看。

這裡還是以pinctrl@11000000這個節點為例分析。

samsung_gpiolib_register

 1 static int samsung_gpiolib_register(struct platform_device *pdev,
 2                     struct samsung_pinctrl_drv_data *drvdata)
 3 {
 4     struct samsung_pin_bank *bank = drvdata->pin_banks;
 5     struct gpio_chip *gc;
 6     int ret;
 7     int i;
 8     for (i = 0; i < drvdata->nr_banks; ++i, ++bank) {  // 遍歷pinctrl@11000000下的所有bank,我們關心的是gpx3這個bank
 9         bank->gpio_chip = samsung_gpiolib_chip;   // gpio_chip
10         gc = &bank->gpio_chip;
11  // 這個bank的gpio在系統中的邏輯起始號, 其中drvdata->pin_base是pinctrl@11000000的在系統中的邏輯gpio起始號,
12  // 而bank->pin_base是這個bank在pinctrl@11000000中的邏輯起始號(從0開始)
13         gc->base = drvdata->pin_base + bank->pin_base; 
14         gc->ngpio = bank->nr_pins;  // 這個bank中含有的gpio的個數
15         gc->parent = &pdev->dev;
16         gc->of_node = bank->of_node;  //對於gpx3來說,就是gpx3那個節點的node
17         gc->label = bank->name;
18         ret = gpiochip_add_data(gc, bank);
19 ...
20     }
21     return 0;
22 ...
23 }

    ---> gpiochip_add_data(struct gpio_chip *chip, void *data)

 1 int gpiochip_add_data(struct gpio_chip *chip, void *data)
 2 {
 3     unsigned long    flags;
 4     int        status = 0;
 5     unsigned    i;
 6     int        base = chip->base;
 7     struct gpio_device *gdev;
 8  // 每一個bank都都應一個唯一的gpio_device和gpio_chip
 9     gdev = kzalloc(sizeof(*gdev), GFP_KERNEL);
10     gdev->dev.bus = &gpio_bus_type;
11     gdev->chip = chip;
12     chip->gpiodev = gdev;
13  ... ...
14     if (chip->of_node)
15         gdev->dev.of_node = chip->of_node;
16  
17  // 分配一個唯一的id
18     gdev->id = ida_simple_get(&gpio_ida, 0, 0, GFP_KERNEL);
19     dev_set_name(&gdev->dev, "gpiochip%d", gdev->id);
20  ... ...
21  // 為這個chip下的每一個gpio都要分配一個gpio_desc結構體
22     gdev->descs = kcalloc(chip->ngpio, sizeof(gdev->descs[0]), GFP_KERNEL);
23  ... ...
24  // 這個chip中含有的gpio的個數
25     gdev->ngpio = chip->ngpio;
26  // gpx3 這個bank
27     gdev->data = data;
28  ... ...
29  // base表示的是這個bank在系統中的邏輯gpio號
30     gdev->base = base;
31  // 將這個bank對應的gpio_device添加到全局鏈表gpio_devices中
32  // 在添加的時候會根據gdev->base和ngpio在gpio_devices鏈表中找到合適的位置
33     status = gpiodev_add_to_list(gdev);
34  ... ...
35     for (i = 0; i < chip->ngpio; i++) {
36         struct gpio_desc *desc = &gdev->descs[i];
37         desc->gdev = gdev;
38   ... ...
39     }
40  ... ...
41  // 預設這個chip下的所有gpio都是可以產生中斷
42     status = gpiochip_irqchip_init_valid_mask(chip);
43     status = of_gpiochip_add(chip);
44  ... ...
45     return 0;
46  ... ...
47 }

        ---> of_gpiochip_add(struct gpio_chip *chip)

 1 int of_gpiochip_add(struct gpio_chip *chip)
 2 {
 3     int status;
 4 ... ...
 5     if (!chip->of_xlate) {
 6         chip->of_gpio_n_cells = 2;
 7         chip->of_xlate = of_gpio_simple_xlate;
 8     }
 9 ... ...
10 }

這裡需要看一下of_gpio_simple_xlate的實現,這個在下麵的分析中會被回調

1 int of_gpio_simple_xlate(struct gpio_chip *gc,
2              const struct of_phandle_args *gpiospec, u32 *flags)
3 {
4 .. ...
5     if (flags)  // 第二個參數表示的是flag
6         *flags = gpiospec->args[1];
7  // 第一個參數表示的是gpio號
8     return gpiospec->args[0];
9 }

從上面的分析中我們知道瞭如下幾點:

1. 每一個bank(如gpx3)都對應一個gpio_chip和gpio_device

2. 這個bank下的每一個gpio都會對應一個唯一的gpio_desc結構體,這些結構提的首地址存放在gpio_device的desc中

3. 上面的gpio_device會加入到全局gpio_devices鏈表中

4. gpio_chip的of_gpio_n_cells被賦值為2,表示引用一個gpio資源需要兩個參數,負責解析這兩個參數函數以的of_xlate函數為of_gpio_simple_xlate,其中第一個參數表示gpio號(在對應的bank中),第二個表示flag

這裡還是先把設備樹中涉及到的節點列在這裡:

 1 / {
 2     interrupt-parent = <&gic>;
 3     #address-cells = <0x1>;
 4     #size-cells = <0x1>;
 5     compatible = "friendlyarm,tiny4412", "samsung,exynos4412", "samsung,exynos4";
 6     model = "FriendlyARM TINY4412 board based on Exynos4412";
 7     aliases {
 8         pinctrl1 = "/pinctrl@11000000";
 9     };
10     gic: interrupt-controller@10490000 {
11         compatible = "arm,cortex-a9-gic";
12         #interrupt-cells = <0x3>;
13         interrupt-controller;
14         reg = <0x10490000 0x10000>, <0x10480000 0x10000>;
15         cpu-offset = <0x4000>;
16     };
17     pinctrl@11000000 {
18         compatible = "samsung,exynos4x12-pinctrl";
19         reg = <0x11000000 0x1000>;
20         interrupts = <0x0 0x2e 0x0>;
21         gpx3: gpx3 {
22             gpio-controller;
23             #gpio-cells = <0x2>;
24             interrupt-controller;
25             #interrupt-cells = <0x2>;
26         };
27         wakeup-interrupt-controller {
28             compatible = "samsung,exynos4210-wakeup-eint";
29             interrupt-parent = <0x1>;
30             interrupts = <0x0 0x20 0x0>;
31         };
32     };
33     interrupt_xeint26: interrupt_xeint26 {
34             compatible = "tiny4412,interrupt_xeint26";
35             int-gpio = <&gpx3 2 GPIO_ACTIVE_HIGH>;
36     };
37 };

上面的節點interrupt_xeint26中引用了gpx3_2,而且在驅動中打算將這個gpio當作中斷引腳來使用。

下麵是對應的驅動程式:

  1 #include <linux/init.h>
  2 #include <linux/module.h>
  3 #include <linux/platform_device.h>
  4 #include <linux/gpio.h>
  5 #include <linux/of.h>
  6 #include <linux/of_gpio.h>
  7 #include <linux/interrupt.h>
  8 typedef struct 
  9 {
 10     int gpio;
 11     int irq;
 12     char name[20];
 13 }xeint26_data_t;
 14 static irqreturn_t xeint26_isr(int irq, void *dev_id)
 15 {
 16     xeint26_data_t *data = dev_id;
 17     printk("%s enter, %s: gpio:%d, irq: %d\n", __func__, data->name, data->gpio, data->irq);
 18     return IRQ_HANDLED;
 19 }
 20 static int xeint26_probe(struct platform_device *pdev) {
 21     struct device *dev = &pdev->dev;
 22     int irq_gpio = -1;
 23     int irq = -1;
 24     int ret = 0;
 25     int i = 0;
 26     xeint26_data_t *data = NULL;
 27     printk("%s enter.\n", __func__);
 28     if (!dev->of_node) {
 29         dev_err(dev, "no platform data.\n");
 30         goto err1;
 31     }
 32     data = devm_kmalloc(dev, sizeof(*data)*1, GFP_KERNEL);
 33     if (!data) {
 34         dev_err(dev, "no memory.\n");
 35         goto err0;
 36     }
 37     for (i = 0; i < 1; i++) {
 38         sprintf(data[i].name, "int-gpio");
 39         irq_gpio = of_get_named_gpio(dev->of_node,
 40             data[i].name, 0);
 41         if (irq_gpio < 0) {
 42             dev_err(dev, "Looking up %s property in node %s failed %d\n",
 43                 data[i].name, dev->of_node->full_name, irq_gpio);
 44             goto err1;
 45         }
 46         data[i].gpio = irq_gpio;
 47         irq = gpio_to_irq(irq_gpio);
 48         if (irq < 0) {
 49             dev_err(dev,
 50                 "Unable to get irq number for GPIO %d, error %d\n",
 51                 irq_gpio, irq);
 52             goto err1;
 53         }
 54         data[i].irq = irq;
 55         printk("%s: gpio: %d ---> irq (%d)\n", __func__, irq_gpio, irq);
 56         ret = devm_request_any_context_irq(dev, irq,
 57             xeint26_isr, IRQF_TRIGGER_FALLING, data[i].name, data+i);
 58         if (ret < 0) {
 59             dev_err(dev, "Unable to claim irq %d; error %d\n",
 60                 irq, ret);
 61             goto err1;
 62         }
 63     }
 64     return 0;
 65 err1:
 66     devm_kfree(dev, data);
 67 err0:
 68     return -EINVAL;
 69 }
 70 static int xeint26_remove(struct platform_device *pdev) {
 71     printk("%s enter.\n", __func__);
 72     return 0;
 73 }
 74 static const struct of_device_id xeint26_dt_ids[] = {
 75     { .compatible = "tiny4412,interrupt_xeint26", },
 76     {},
 77 };
 78 MODULE_DEVICE_TABLE(of, xeint26_dt_ids);
 79 static struct platform_driver xeint26_driver = {
 80     .driver        = {
 81         .name    = "interrupt_xeint26",
 82         .of_match_table    = of_match_ptr(xeint26_dt_ids),
 83     },
 84     .probe        = xeint26_probe,
 85     .remove        = xeint26_remove,
 86 };
 87 static int __init xeint26_init(void)
 88 {
 89     int ret;
 90     ret = platform_driver_register(&xeint26_driver);
 91     if (ret)
 92         printk(KERN_ERR "xeint26: probe failed: %d\n", ret);
 93     return ret;
 94 }
 95 module_init(xeint26_init);
 96 static void __exit xeint26_exit(void)
 97 {
 98     platform_driver_unregister(&xeint26_driver);
 99 }
100 module_exit(xeint26_exit);
101 MODULE_LICENSE("GPL");

其中我們只需要分析兩個關鍵的函數:of_get_named_gpio 和 gpio_to_irq.

of_get_named_gpio

這個函數的作用是根據傳遞的屬性的name和索引號,得到一個gpio號

of_get_named_gpio

    ---> of_get_named_gpio_flags(np, propname, index, NULL)

1 int of_get_named_gpio_flags(struct device_node *np, const char *list_name,
2                 int index, enum of_gpio_flags *flags)
3 {
4     struct gpio_desc *desc;
5     desc = of_get_named_gpiod_flags(np, list_name, index, flags);
6   ... ...
7         return desc_to_gpio(desc);
8 }

        ---> of_get_named_gpiod_flags

 1 struct gpio_desc *of_get_named_gpiod_flags(struct device_node *np,
 2              const char *propname, int index, enum of_gpio_flags *flags)
 3 {
 4     struct of_phandle_args gpiospec;
 5     struct gpio_chip *chip;
 6     struct gpio_desc *desc;
 7     int ret;
 8  // 解析"int-gpio"屬性中第index欄位,將解析結果存放到gpiospec中
 9  /*
10 struct of_phandle_args {
11     struct device_node *np;  // int-gpio屬性所引用的gpio-controller的node,對於'int-gpio'來說就是gpx3
12     int args_count;  // gpx3這個gpio-controller的#gpio-cells屬性的值
13     uint32_t args[MAX_PHANDLE_ARGS];  // 具體描述這個gpio屬性的每一個參數
14 };
15  */
16     ret = of_parse_phandle_with_args(np, propname, "#gpio-cells", index,
17                      &gpiospec);
18  
19  // 上面gpiospec的np存放的索引用的gpio-controller的node,
20  // 遍歷gpio_devices鏈表,找到對應的gpio_device,也就找到了gpio_chip
21     chip = of_find_gpiochip_by_xlate(&gpiospec);
22  // 調用chip->of_xlate解析gpiospec,返回gpiospec的args中的第一個參數args[0],
23  // 也就是前面分析的在bank中的邏輯gpio號
24  // 知道了gpio號,就可以在gpio_device->desc中索引到對應的gpio_desc
25     desc = of_xlate_and_get_gpiod_flags(chip, &gpiospec, flags);
26     return desc;
27 }
        ---> desc_to_gpio
1 int desc_to_gpio(const struct gpio_desc *desc)
2 {
3  // 獲得這個gpio_desc對應的gpio在系統中的邏輯gpio號
4     return desc->gdev->base + (desc - &desc->gdev->descs[0]);
5 }
gpio_to_irq

將這個gpio轉換成對應的virq

gpio_to_irq(irq_gpio)

    ---> __gpio_to_irq(gpio)

        ---> gpiod_to_irq(gpio_to_desc(gpio))

這裡調用了兩個函數,函數gpio_to_desc根據傳入的全局邏輯gpio號找到對應的gpio_desc,原理是:遍歷gpio_devices鏈表,根據傳入的邏輯gpio號,就可以定位到所屬的gpio_device,前面說過,在將gpio_device加入到gpio_devices鏈表的時候,不是亂加的,而是根據gpio_device的base和ngpio找到一個合適的位置。找到了gpio_device,那麼通過索引它的desc成員,就可以找到對應的gpio_desc

gpio_to_desc

 1 struct gpio_desc *gpio_to_desc(unsigned gpio)  // 傳入的是全局邏輯gpio號
 2 {
 3     struct gpio_device *gdev;
 4     unsigned long flags;
 5     list_for_each_entry(gdev, &gpio_devices, list) {
 6         if (gdev->base <= gpio &&
 7             gdev->base + gdev->ngpio > gpio) {
 8             return &gdev->descs[gpio - gdev->base];  // 獲得gpio_desc
 9         }
10     }
11 ... ...
12 }

gpiod_to_irq

 1 int gpiod_to_irq(const struct gpio_desc *desc)
 2 {
 3     struct gpio_chip *chip;
 4     int offset;
 5  ... ...
 6     chip = desc->gdev->chip;
 7  // 這個函數通過desc - &desc->gdev->descs[0]就可以計算出,對於gpx3_2,就是2
 8  // 這個gpio_desc在所屬的bank中的邏輯gpio號
 9     offset = gpio_chip_hwgpio(desc);
10         int retirq = chip->to_irq(chip, offset);
11  ... ...
12         return retirq;
13  ... ...
14 }

上面的第11行就是前面samsung_gpiolib_register中設置的samsung_gpiolib_chip,其to_irq定義如下

1 static int samsung_gpio_to_irq(struct gpio_chip *gc, unsigned offset)
2 {
3     struct samsung_pin_bank *bank = gpiochip_get_data(gc);
4     unsigned int virq;
5  .. ..
6     virq = irq_create_mapping(bank->irq_domain, offset);
7     return (virq) ? : -ENXIO;
8 }

需要註意的是offset,比如對於gpx3_2,那麼offset就是2, 結合前一篇的博文,這裡的offset就是hwirq,調用irq_create_mapping可以為該hwirq在kernel中分配一個唯一的virq,同時將hwirq和virq的映射關係存放到bank->irq_domain中。

實驗

載入驅動

1 [root@tiny4412 mnt]# insmod xeint26.ko 
2 [  152.084809] xeint26_probe enter.
3 [  152.085104] of_get_named_gpiod_flags: parsed 'int-gpio' property of node '/interrupt_xeint26[0]' - status (0)
4 [  152.085286] irq: irq_create_mapping(0xef205d00, 0x2)
5 [  152.085423] irq: -> using domain @ef205d00
6 [  152.085590] __irq_alloc_descs: alloc virq: 100, cnt: 1
7 [  152.090160] irq: irq 2 on domain /pinctrl@11000000/gpx3 mapped to virtual irq 100
8 [  152.097376] xeint26_probe: gpio: 238 ---> irq (100)

可以看到在載入驅動的時候才創建了hwirq和virq之間的映射,分配到的virq是100.此時可以去按下tiny4412上面的key1,可以看到中斷處理函數中列印出來的log

1 [root@tiny4412 mnt]# [  170.718118] xeint26_isr enter, int-gpio: gpio:238, irq: 100
2 [  170.910928] xeint26_isr enter, int-gpio: gpio:238, irq: 100

可以看看當前的中斷觸發情況:

 1 [root@tiny4412 mnt]# cat /proc/interrupts 
 2            CPU0       CPU1       CPU2       CPU3       
 3  36:          0          0          0          0     GIC-0  89 Edge      mct_comp_irq
 4  37:       4702       2840       1176        787     GIC-0  28 Edge      MCT
 5  44:         34          0          0          0     GIC-0 107 Edge      mmc0
 6  45:          1          0          0          0     GIC-0 103 Edge      12480000.hsotg, 12480000.hsotg, dwc2_hsotg:usb1
 7  46:        881          0          0          0     GIC-0 102 Edge      ehci_hcd:usb2, ohci_hcd:usb3
 8  48:        341          0          0          0     GIC-0  84 Edge      13800000.serial
 9  52:          4          0          0          0     GIC-0  67 Edge      12680000.pdma
10  53:          0          0          0          0     GIC-0  68 Edge      12690000.pdma
11  54:          0          0          0          0     GIC-0  66 Edge      12850000.mdma
12  67:          0          0          0          0     GIC-0 144 Edge      10830000.sss
13  68:          0          0          0          0     GIC-0  79 Edge      11400000.pinctrl
14  69:          0          0          0          0     GIC-0  78 Edge      11000000.pinctrl
15  87:          0          0          0          0  COMBINER  80 Edge      3860000.pinctrl
16  88:          0          0          0          0     GIC-0 104 Edge      106e0000.pinctrl
17 100:          2          0          0          0  exynos4210_wkup_irq_chip   2 Edge      int-gpio
18 IPI0:          0          1          1          1  CPU wakeup interrupts
19 IPI1:          0          0          0          0  Timer broadcast interrupts
20 IPI2:        896        894        374        149  Rescheduling interrupts
21 IPI3:          0          2          3          2  Function call interrupts
22 IPI4:          0          0          0          0  CPU stop interrupts
23 IPI5:        212         45         91          8  IRQ work interrupts
24 IPI6:          0          0          0          0  completion interrupts
25 Err:          0

完。


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

-Advertisement-
Play Games
更多相關文章
  • 給自家的Ubuntu下載軟體速度有點慢,畢竟是從國外下載軟體,就想更換到國內比較好的更新源(就是這些軟體所在的伺服器),一般直接百度Ubuntu更新源就能出來一大堆,這時候最好是找和自己Ubuntu版本一致的更新源,我的Ubuntu版本是16.04,下麵是我找到的一個比較好的更新源 http://w ...
  • 1. 點燈法 可直接使用bl led_flicker來使用該程式。2. 串口列印及棧初步分析2.1 使用的前提是串口已經初始化完畢且可以正常使用 直接在需要定位錯誤的地方加上printk語句,看串口是否有相應的輸出即可。2.2 nand_setup的分析 a.棧和局部變數是臨時生成的 b.局部變數的 ...
  • 原本的字元設備只能有255個驅動程式,原因是一個主設備號占用了0~255的次設備號 把register_chrdev展開可得到一下幾個部分:register_chrdev_region/alloc_chrdev_region,cdev_init,cdev_add 參照register_chrdev的 ...
  • 1 分析i2c設備的識別過程i2c_add_driver i2c_register_driver driver->driver.bus = &i2c_bus_type; driver_register(&driver->driver); list_for_each_entry(adapter, &a ...
  • 1 確定相異性 1.1 選中網卡晶元nGCS4 1.2 確定相異性:基地址,中斷號,設置時序(記憶體控制器BWSCON,BANKCONn) 1.3 修改相應的部分 2 測試DM9000C驅動程式:2.1 把dm9dev9000c.c放到內核的drivers/net目錄下2.2 修改drivers/ne ...
  • 1 網路傳輸的簡介 (1)接收過程,如上圖,網路上的數據包到達網卡後,網卡產生中斷,然後設備驅動層收到中斷後,開始進行網路包的接收,接收完之後調用一個netif_rx函數交給網路協議層(層次結構上圖一),然後就是一層一層的網上傳到用戶空間了。 (2)發送過程,從用戶空間過來的數據包,經過層層穿越之後 ...
  • Linux 意外操作後如何進行數據搶救 在 GUI 中使用 組合鍵或是 CLI 下使用 刪除選項,這個文件並沒有從硬碟(或是其它存儲設備)上徹底銷毀。當它文件被刪除以後, 的數據指針部分被清零,僅僅是從系統的目錄結構中被移除,但是這個文件仍然存在你磁碟中的某個 物理位置上。( 或 查詢一個文件所對應 ...
  • Hook是什麼? hook翻譯之後是鉤子的意思,hook的用途主要是用來攔截消息的,看到這裡大家可能會迷茫,What is a hook?所以這時就不得不普及一下操作系統的原理。 舉個例子:假如你是一名游戲熱衷者,在玩游戲時,你要釋放技能那麼你就會對鍵盤滑鼠進行操作,錶面上你是直接操作游戲,其實中間 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...