1.移植uboot-分析uboot啟動流程(詳解)

来源:https://www.cnblogs.com/lifexy/archive/2017/12/28/8136378.html
-Advertisement-
Play Games

本節總結: uboot啟動流程如下: 1)設置CPU為管理模式 2)關看門狗 3)關中斷 4)設置時鐘頻率 5)關mmu,初始化各個bank 6)進入board_init_f()函數 (初始化定時器,GPIO,串口等,劃分記憶體區域) 7)重定位 複製uboot,然後修改SDRAM上的uboot鏈接地 ...


本節總結:

uboot啟動流程如下:

  • 1)設置CPU為管理模式
  • 2)關看門狗
  • 3)關中斷
  • 4)設置時鐘頻率   
  • 5)關mmu,初始化各個bank
  • 6)進入board_init_f()函數 (初始化定時器,GPIO,串口等,劃分記憶體區域)
  • 7)重定位     複製uboot,然後修改SDRAM上的uboot鏈接地址)
  • 8)清bss
  • 9)跳轉到board_init_r()函數,啟動流程結束

 

1.首先來安裝arm-linux-gcc-4.3.2交叉編譯器

mkdir  arm-linux-gcc-4.3.2                 //創建目錄

tar -xjf  arm-linux-gcc-4.3.2.tar.bz2 -C arm-linux-gcc-4.3.2/  //解壓到arm-linux-gcc-4.3.2目錄下

然後添加環境變數:

有兩種方法,第一種只是臨時修改,重啟虛擬機便會複位:

export PATH=/arm-linux-gcc-4.3.2/usr/local/arm/4.3.2/bin:/usr/sbin:/usr/bin... ...
             //將arm-linux-gcc-4.3.2添加到環境變數

第二種,重啟不複位:

vi /etc/environment
添加:
PATH=/arm-linux-gcc-4.3.2/usr/local/arm/4.3.2/bin:/usr/sbin:/usr/bin... ...
                   //將arm-linux-gcc-4.3.2添加到環境變數

2.然後進入ftp://ftp.denx.de/pub/u-boot/來下載u-boot-2012.04.01

2.1.創建source insight工程,來看代碼

1)board 目錄下只添加:

board/samsung/smdk2410/               // (2410單板文件)

 2)arch 目錄下只添加:

arch/arm/cpu/arm920t/                //(只添加這個目錄下的*.c,*.S公用文件)                

arch/arm/cpu/arm920t/s3c24x0/        //(24x0架構所有文件)

arch/arm/include/asm/                //(只添加這個目錄下的*.h公用頭文件)

arch/arm/include/asm/proc-armv/      //(arm架構的文件)

arch/arm/include/asm/arch-s3c24x0/   //(24x0架構頭文件)

arch/arm/lib/                        //(與arm相關的庫文件)

3)include/configs目錄下只添加:

include/configs/smdk2410.h              // (用來配置2410單板的頭文件)

 2.2編譯燒寫:

tar xjf u-boot-2012.04.01.tar.bz2

cd u-boot-2012.04.01                 //進入解壓後文件目錄

make smdk2410_config                 //由於該uboot不支持2440板卡,所以只有配置2410板卡

make                                 //編譯,生成u-boot.bin 

 

3.最後燒寫u-boot.bin,發現無法啟動,接下來便來分析uboot的啟動流程

 4.首先查看arch/arm/cpu/u-boot.lds鏈接腳本

如下圖所示,看到uboot最開始會進入_start:

 

 5. _start位於arch/arm/cpu/arm920t/start.S         

所以,我們從start.S開始分析uboot啟動流程:

.globl _start                                //聲明_start全局符號,這個符號會被lds鏈接腳本用到
_start:    
b     start_code                            //跳轉到start_code符號處,0x00
       ldr   pc, _undefined_instruction                    //0x04
       ldr   pc, _software_interrupt                       //0x08
       ldr   pc, _prefetch_abort                           //0x0c
       ldr   pc, _data_abort                               //0x10
       ldr   pc, _not_used                                 //0x14
       ldr   pc, _irq                                      //0x18
       ldr   pc, _fiq                                      //0x20

_undefined_instruction:  .word undefined_instruction
           //定義_undefined_instruction指向undefined_instruction(32位地址)

_software_interrupt:      .word software_interrupt
_prefetch_abort:    .word prefetch_abort
_data_abort:          .word data_abort
_not_used:             .word not_used
_irq:               .word irq
_fiq:               .word fiq

   .balignl 16,0xdeadbeef        //balignl使用,參考http://www.cnblogs.com/lifexy/p/7171507.html

 其中符號保存的地址都在頂層目錄/system.map中列出來了

 

6. 從上面看到, _start會跳轉到start_code

start_code:

    /*設置CPSR寄存器,讓CPU進入管理模式*/
       mrs  r0, cpsr                 //讀出cpsr的值
       bic   r0, r0, #0x1f           //清位
       orr   r0, r0, #0xd3          //位或
       msr  cpsr, r0                 //寫入cpsr

#if   defined(CONFIG_AT91RM9200DK) || defined(CONFIG_AT91RM9200EK)
       /*
        * relocate exception table
        */
       ldr   r0, =_start            
       ldr   r1, =0x0                //r1等於異常向量基地址
       mov r2, #16
copyex:
       subs       r2, r2, #1           //減16次,s表示每次減都要更新條件標誌位
       ldr   r3, [r0], #4       
       str   r3, [r1], #4      //將_start標號後的16個符號存到異常向量基地址0x0~0x3c處
       bne  copyex             //直到r2減為0
#endif

#ifdef CONFIG_S3C24X0

       /* 關看門狗*/
#  define pWTCON       0x53000000
#  define INTMSK 0x4A000008    /* Interrupt-Controller base addresses */
#  define INTSUBMSK  0x4A00001C
#  define CLKDIVN       0x4C000014    /* clock divisor register */

       ldr   r0, =pWTCON       
       mov r1, #0x0        
       str   r1, [r0]           //關看門狗,使WTCON寄存器=0

       /*關中斷*/
       mov r1, #0xffffffff
       ldr   r0, =INTMSK
       str   r1, [r0]                  //關閉所有中斷
# if defined(CONFIG_S3C2410)
       ldr   r1, =0x3ff
       ldr   r0, =INTSUBMSK
       str   r1, [r0]                  //關閉次級所有中斷
# endif

    /* 設置時鐘頻率, FCLK:HCLK:PCLK = 1:2:4 ,而FCLK預設為120Mhz*/
       ldr   r0, =CLKDIVN
       mov r1, #3
       str   r1, [r0]

 #ifndef CONFIG_SKIP_LOWLEVEL_INIT
       bl    cpu_init_crit                         //關閉mmu,並初始化各個bank

#endif

call_board_init_f:
       ldr   sp, =(CONFIG_SYS_INIT_SP_ADDR) //CONFIG_SYS_INIT_SP_ADDR=0x30000f80
       bic   sp, sp, #7         //sp=0x30000f80
       ldr   r0,=0x00000000
       bl    board_init_f   

 上面的CONFIG_SYS_INIT_SP_ADDR =0x30000f80,是通過arm-linux-objdump -D u-boot>u-boot.dis生成反彙編,然後從u-boot.dis得到的,如下圖所示:

 

 

 

7.然後進入第一個C函數:board_init_f()

該函數主要工作是:

清空gd指向的結構體、通過init_sequence函數數組,來初始化各個函數以及逐步填充結構體,最後劃分記憶體區域,然後調用relocate_code()對uboot重定位

1)具體代碼如下所示:

void board_init_f(ulong bootflag) // bootflag=0x00000000
{
       bd_t *bd;
       init_fnc_t **init_fnc_ptr;         //函數指針
       gd_t *id;
       ulong addr, addr_sp;
#ifdef CONFIG_PRAM
       ulong reg;
#endif

       bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_F, "board_init_f");
       /* Pointer is writable since we allocated a register for it */
       gd = (gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07);

 其中gd是一個全局變數,用來傳遞給內核的參數用的,如下圖所示,在arch/arn/include/asm/global_data.h中定義,*gd指向r8寄存器,所以r8專門提供給gd使用

 

而CONFIG_SYS_INIT_SP_ADDR,在6節里得到=0x30000f80,所以gd=0x30000f80

 2)繼續來看board_init_f():

      __asm__ __volatile__("": : :"memory");           //memory:讓cpu重新讀取記憶體的數據

      memset((void *)gd, 0, sizeof(gd_t));        //將0x30000f80地址上的gd_t結構體清0

      gd->mon_len = _bss_end_ofs;  
         // _bss_end_ofs =__bss_end__ - _start,在反彙編找到等於0xae4e0,所以mon_len等於uboot的數據長度
      gd->fdt_blob = (void *)getenv_ulong("fdtcontroladdr", 16, (uintptr_t)gd->fdt_blob);

       for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr)
            //調用init_sequence[]數組裡的各個函數
      {
              if ((*init_fnc_ptr)() != 0)     //執行函數,若函數執行出錯,則進入hang()
             {    
           hang ();
//列印錯誤信息,然後一直while } }

 上面的init_sequence[]數組裡存了各個函數,比如有:

  • board_early_init_f():設置系統時鐘,設置各個GPIO引腳
  • timer_init():初始化定時器
  • env_init():設置gd的成員變數
  • init_baudrate():設置波特率
  • dram_init():設置gd->ram_size= 0x04000000(64MB)

3)繼續來看board_init_f():

addr = CONFIG_SYS_SDRAM_BASE + gd->ram_size;  // addr=0x34000000 
// CONFIG_SYS_SDRAM_BASE:  SDRAM基地址,為0X30000000
// gd->ram_size:          等於0x04000000 


#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF))
       /* reserve TLB table */
       addr -= (4096 * 4);        //addr=33FFC000

       addr &= ~(0x10000 - 1);  // addr=33FF0000,   

       gd->tlb_addr = addr;   //將64kB分配給TLB,所以TLB地址為33FF0000~33FFFFFF
#endif

       /* round down to next 4 kB limit */
       addr &= ~(4096 - 1);                    //4kb對齊, addr=33FF0000
       debug("Top of RAM usable for U-Boot at: %08lx\n", addr);
/* * reserve memory for U-Boot code, data & bss * round down to next 4 kB limit */ addr -= gd->mon_len; // 在前面分析過gd->mon_len=0xae4e0, //所以addr=33FF0000 -0xae4e0=33F41B20, addr &= ~(4096 - 1); //4095=0xfff,4kb對齊, addr=33F41000 //所以分配給uboot各個段的重定位地址為33F41000~33FFFFFF debug("Reserving %ldk for U-Boot at: %08lx\n", gd->mon_len >> 10, addr); #ifndef CONFIG_SPL_BUILD addr_sp = addr - TOTAL_MALLOC_LEN; //分配一段malloc空間給addr_sp //TOTAL_MALLOC_LEN=1024*1024*4,所以malloc空間為33BF1000~33F40FFF addr_sp -= sizeof (bd_t); //分配一段bd_t結構體大的空間     bd = (bd_t *) addr_sp; //bd指向剛剛分配出來的bd_t結構體     gd->bd = bd; // 0x30000f80處的gd變數的成員bd等於bd_t基地址     addr_sp -= sizeof (gd_t); //分配一個gd_t結構體大的空間     id = (gd_t *) addr_sp; //id指向剛剛分配的gd_t結構體     gd->irq_sp = addr_sp; //0x30000f80處的gd變數的成員irq_sp等於gd_t基地址
    addr_sp -= 12;     addr_sp &= ~0x07;     ... ...     relocate_code(addr_sp, id, addr); //進入relocate_code()函數,重定位代碼,以及各個符號     // addr_sp: 棧頂,該棧頂向上的位置用來存放gd->irq_sp、id 、gd->bd、malloc、uboot、TLB(64kb),     //id: 存放 gd_t結構體的首地址     // addr: 等於存放uboot重定位地址33F41000 }

 執行完board_init_f()後,最終記憶體會劃分如下圖所示:

 

其實此時uboot還在flash中運行,然後會進入start.S的relocate_code()里進行uboot重定位 

8.接下來進入重定位

1)start.S的relocate_code()代碼如下所示

relocate_code:
       mov r4, r0      /* save addr_sp */              // addr_sp棧頂值
       mov r5, r1      /* save addr of gd */           // id值
       mov r6, r2      /* save addr of destination */  // addr值:uboot重定位地址

       /* Set up the stack        */
stack_setup:
       mov sp, r4                //設置棧addr_sp
       adr  r0, _start           //在頂層目錄下system.map符號文件中找到_start =0,所以r0=0
       cmp r0, r6                //判斷_start(uboot重定位之前的地址)和addr(重定位地址)是否一樣
       beq clear_bss             /* skip relocation */ 
mov r1, r6
/* r1 <- scratch for copy_loop */ //r1= addr(重定位地址) ldr r3, _bss_start_ofs //_bss_start_ofs=__bss_start - _start(uboot代碼大小) add r2, r0, r3 /* r2 <- source end address*/ //r2= uboot重定位之前的結束地址 copy_loop: ldmia r0!, {r9-r10} /* copy from source address [r0] */ //將r0處的兩個32位數據拷到r9-r10中,然後r0+=8 stmia r1!, {r9-r10} /* copy to target address [r1]*/ //將拷出來的兩個數據放入r1(重定位地址)處,然後r1+=8 cmp r0, r2 /* until source end address [r2]*/ //判斷拷貝的數據是否到結束地址 blo copy_loop

 上面只是把代碼複製到SDRAM上,而鏈接地址內容卻沒有改變,比如異常向量0x04的代碼內容還是0x1e0,

我們以異常向量0x04為例,來看它的反彙編:

 

如上圖所示,即使uboot在SDRAM運行,由於代碼沒修改,PC也會跳到0x1e0(flash地址)

和之前老的uboot有很大區別,以前老的uboot直接是使用的SDRAM鏈接地址,如下圖所示:

 

所以,新的uboot採用了動態鏈接地址的方法,在鏈接腳本uboot.lds中,可以看到這兩個段(.rel.dyn、.dynsym):

 

該兩個段里,便是保存了各個文件的相對動態信息(.rel.dyn)、動態鏈接地址的符號(.dynsym)

以上圖的.rel.dyn段為例來分析,找到__rel_dyn_start符號處:

 

如上圖所示,其中0x17表示的是符號的結束標誌位,我們以0x20為例來講解:

在之前,我們講過0x20裡面保存的是異常向量0x04跳轉的地址(0x1e0),如下圖所示:

 

所以,接下來的代碼,便會根據0x20里的值0x1e0(flash地址),將SDRAM的33F41000+0x20的內容改為33F41000+0x1e0(SDRAM地址),來改變uboot的鏈接地址

2)重定位的剩餘代碼,如下所示:

#ifndef CONFIG_SPL_BUILD
       /*
        * fix .rel.dyn relocations
        */
       ldr   r0, _TEXT_BASE             /* r0 <- Text base */  //r0=text段基地址=0
       sub  r9, r6, r0         /* r9 <- relocation offset */   //r9= 重定位後的偏移值=33F41000
       ldr   r10, _dynsym_start_ofs  /* r10 <- sym table ofs */ 
                                          //_dynsym_start_ofs =__dynsym_start - _start=0x73608
                                          //所以r10=動態符號表的起始偏移值=0x73608

       add r10, r10, r0            /* r10 <- sym table in FLASH */
                                       //r10=flash上的動態符號表基地址=0x73608

       ldr   r2, _rel_dyn_start_ofs     /* r2 <- rel dyn start ofs */
                                          //r2=__rel_dyn_start - _start=0x6b568
                                          //所以r2=相對動態信息的起始偏移值=0x6b568

       add r2, r2, r0         /* r2 <- rel dyn start in FLASH */
                                      //r2=flash上的相對動態信息基地址=0x6b568

       ldr   r3, _rel_dyn_end_ofs      /* r3 <- rel dyn end ofs */
                                          // _rel_dyn_end_ofs=__rel_dyn_end - _start=00073608
                                          //所以r3=相對動態信息的結束偏移值=00073608
add r3, r3, r0 /* r3 <- rel dyn end in FLASH */ //r3=flash上的相對動態信息結束地址=0x6b568
fixloop: ldr r0, [r2] /* r0 <- location to fix up, IN FLASH! */ //以0x20為例,r0=0x6b568地址處的內容= 0x20 add r0, r0, r9 /* r0 <- location to fix up in RAM */ //r0=33F41000+0x20=33F41020 ldr r1, [r2, #4] //r1= 33F41024地址處的內容=0x17 and r7, r1, #0xff cmp r7, #23 /* relative fixup? */ //0x17=23,所以相等 beq fixrel //跳到:fixerl cmp r7, #2 /* absolute fixup? */ beq fixabs /* ignore unknown type of fixup */ b fixnext fixabs: /* absolute fix: set location to (offset) symbol value */ mov r1, r1, LSR #4 /* r1 <- symbol index in .dynsym */ add r1, r10, r1 /* r1 <- address of symbol in table */ ldr r1, [r1, #4] /* r1 <- symbol value */ add r1, r1, r9 /* r1 <- relocated sym addr */ b fixnext fixrel: /* relative fix: increase location by offset */ ldr r1, [r0] //r1=33F41020地址處的內容=0x1e0 add r1, r1, r9 //r1=0x1e0+33F41000= 33F411e0 fixnext: str r1, [r0] //改變鏈接地址里的內容, 33F41020=33F411e0 (之前為0x1e0) add r2, r2, #8 //r2等於下一個相對動態信息(0x24)的地址 cmp r2, r3 //若沒到尾部__rel_dyn_end,便繼續執行: fixloop blo fixloop #endif

 9.清除bss段

/*重定位完成後,清除bss段*/
clear_bss:
 #ifndef CONFIG_SPL_BUILD
       ldr   r0, _bss_start_ofs                        //獲取flash上的bss段起始位置
       ldr   r1, _bss_end_ofs                          //獲取flash上的bss段結束位置
       mov r4, r6                    /* reloc addr */     //獲取r6(SDRAM上的uboot基地址)
       add r0, r0, r4                                  //加上重定位偏移值,得到SDRAM上的bss段起始位置
       add r1, r1, r4                                     //得到SDRAM上的bss段結束位置
       mov r2, #0x00000000           /* clear*/

clbss_l:
    str    r2, [r0]           /* clear loop...       */                 //開始清除SDRAM上的bss段
       add r0, r0, #4
       cmp r0, r1
       bne  clbss_l
       bl coloured_LED_init
       bl red_led_on
#endif

9.1繼續往下分析

#ifdef CONFIG_NAND_SPL                   //未定義,所以不執行
  ... ...                          
#else                                   //執行else

       ldr   r0, _board_init_r_ofs         //r0=flash上的board_init_r()函數地址偏移值
       adr  r1, _start                    //0
       add lr, r0, r1                     //返回地址lr=flash上的board_init_r()函數
       add lr, lr, r9                     //加上重定位偏移值(r9)後,lr=SDRAM上的board_init_r()函數

       /* setup parameters for board_init_r */
       mov r0, r5             /* gd_t */              //r0=id值
       mov r1, r6             /* dest_addr */         //r1=uboot重定位地址
       /* jump to it ... */
       mov pc, lr              //跳轉:  board_init_r()函數

_board_init_r_ofs:
       .word board_init_r - _start        //獲取在flash上的board_init_r()函數地址偏移值

#endif

從上面代碼看出, 接下來便會進入uboot的board_init_r()函數,該函數會對各個外設初始化、環境變數初始化等.

 

uboot的啟動過程到此便結束了,下一章便來修改uboot,使它能夠運行起來

 


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

-Advertisement-
Play Games
更多相關文章
  • 生產,操作,常需要,用LED屏顯示一些信息 下麵給一個最簡單的LED實常式序:本實例使用LEDSender2010.dll類庫 適用於中天等LED! 1.建立窗體: /* * Created by SharpDevelop. * User: gaofajin * Date: 2015/10/20 * ...
  • 註:歡迎大家轉載,非商業用途請在醒目位置註明本文鏈接和作者名dijia478,商業用途請聯繫本人[email protected]。 之前步驟:Solr集群搭建詳細教程(一) 三、solr集群搭建 註意,在搭建solr集群前,建議最好有一個solr服務是已經搭建好的,可以簡化大量重覆的配置操作。 單機 ...
  • http://blog.csdn.net/taokai_110/article/details/72934818 終於解決了問題 ...
  • 22.添加附加文件刪除後,文件大小沒有發生改變的(優化文件和清空回收站) ...
  • LabVIEW的TCP/IP函數庫非常好用,但是不恰當地設置打開連接結點的參數將帶來一些問題,麻煩。如下圖的打開連接的參數設置: 上圖中指定了本地的埠,會發生這樣的情況。當我們關閉應用程式之後,連接被關閉,按照TCP/IP的協議,連接會話資源會處於一種time_wait的狀態,存活一段時間,一般都 ...
  • 當新增硬碟時,要做的幾個事情就是:先按需要進行分區、然後對分區進行格式化、再進行掛載即將指定分區掛到指定目錄上;必要的時候做下校驗; 常用的命令有: fdisk :磁碟分區相關指令 如:查看 新增 刪除 合併 分區等 partproabe:重讀分區表 mkfs:將分區格式化成指定文件系統格式 mou ...
  • 1.在macOS High Sierras上安裝VMware for mac 下載地址:VMware Fusion 8.5.1 https://pan.baidu.com/s/1skQ1OyL 2.Ubuntu14.04安裝 2.1 下載ubuntu14.04鏡像文件 下載地址:中科大鏡像源 htt ...
  • 操作前需要以下軟體,且,Ubuntu已經安裝在VMware Workstation上。 Ubuntu16.04 LTSVMware WorkstationSecureCRT VMware的編輯tab >虛擬網路編輯 菜單查看網路配置: 子網IP:192.168.92.0子網掩碼:255.255 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...