記憶體映射函數remap_pfn_range學習——示例分析(1)

来源:https://www.cnblogs.com/pengdonglin137/archive/2017/12/30/8149859.html
-Advertisement-
Play Games

作者 彭東林 QQ 405728433 平臺 Linux-4.10.17 Qemu-2.8 + vexpress-a9 DDR:1GB 參考 Linux 虛擬記憶體和物理記憶體的理解 Linux進程分配記憶體的兩種方式--brk() 和mmap() Linux中的mmap的使用 程式(進程)記憶體分佈 解析 ...


作者

彭東林 QQ 405728433  

平臺

Linux-4.10.17 Qemu-2.8 + vexpress-a9 DDR:1GB  

參考

Linux 虛擬記憶體和物理記憶體的理解 Linux進程分配記憶體的兩種方式--brk() 和mmap() Linux中的mmap的使用 程式(進程)記憶體分佈 解析  

概述

Linux內核提供了remap_pfn_range函數來實現將內核空間的記憶體映射到用戶空間:
 1 /**
 2  * remap_pfn_range - remap kernel memory to userspace
 3  * @vma: user vma to map to
 4  * @addr: target user address to start at
 5  * @pfn: physical address of kernel memory
 6  * @size: size of map area
 7  * @prot: page protection flags for this mapping
 8  *
 9  *  Note: this is only safe if the mm semaphore is held when called.
10  */
11 int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
12             unsigned long pfn, unsigned long size, pgprot_t prot);
上面的註釋對參數進行了說明。當用戶調用mmap時,驅動中的file_operations->mmap會被調用,可以在mmap中調用remap_pfn_range,它的大部分參數的值都由VMA提供。具體可以參考LDD3的P420.  

正文

下麵結合一個簡單的例子學習一下。在驅動中申請一個32個Page的緩衝區,這裡的PAGE_SIZE是4KB,所以內核中的緩衝區大小是128KB。user_1和user_2將前64KB映射到自己的用戶空間,其中user_1向緩衝區中寫入字元串,user_2去讀取。user_3和user_4將後64KB映射到自己的用戶空間,其中user_3向緩衝區中寫入字元串,user_4讀取字元串。user_5將整個128KB映射到自己的用戶空間,然後將緩衝區清零。此外,在驅動中申請緩衝區的方式有多種,可以用kmalloc、也可以用alloc_pages,當然也可用vmalloc,下麵會分別針對這三個介面實現驅動。   涉及到的測試程式和驅動程式可以到下麵的鏈接下載: https://github.com/pengdonglin137/remap_pfn_demo  

一、驅動程式

下麵現以kzalloc申請緩衝區的方式為例介紹,調用kmalloc申請32個頁,我們知道kzalloc返回的虛擬地址的特點是對應的物理地址也是連續的,所以在調用remap_pfn_range的時候很方便。首先在驅動init的時候申請128KB的緩衝區:
 1 static int __init remap_pfn_init(void)
 2 {
 3     int ret = 0;
 4 
 5     kbuff = kzalloc(BUF_SIZE, GFP_KERNEL);  // 這裡的BUF_SIZE是128KB
 6     if (!kbuff) {
 7         ret = -ENOMEM;
 8         goto err;
 9     }
10 
11     ret = misc_register(&remap_pfn_misc);   // 註冊一個misc設備
12     if (unlikely(ret)) {
13         pr_err("failed to register misc device!\n");
14         goto err;
15     }
16 
17     return 0;
18 
19 err:
20     return ret;
21 }

第11行註冊了一個misc設備,相關信息如下:

1 static struct miscdevice remap_pfn_misc = {
2     .minor = MISC_DYNAMIC_MINOR,
3     .name = "remap_pfn",
4     .fops = &remap_pfn_fops,
5 };

這樣載入驅動後會在/dev下生成一個名為remap_pfn的節點,用戶程式可以通過這個節點跟驅動通信。其中remap_pfn_fops的定義如下:

1 static const struct file_operations remap_pfn_fops = {
2     .owner = THIS_MODULE,
3     .open = remap_pfn_open,
4     .mmap = remap_pfn_mmap,
5 };

第3行的open函數這裡沒有做什麼實際的工作,只是列印一些log,比如將進程的記憶體佈局信息輸出

第4行,負責處理用戶的mmap請求,這是需要關心的。 先看一下open函數具體列印了那些內容:
 1 static int remap_pfn_open(struct inode *inode, struct file *file)
 2 {
 3     struct mm_struct *mm = current->mm;
 4 
 5     printk("client: %s (%d)\n", current->comm, current->pid);
 6     printk("code  section: [0x%lx   0x%lx]\n", mm->start_code, mm->end_code);
 7     printk("data  section: [0x%lx   0x%lx]\n", mm->start_data, mm->end_data);
 8     printk("brk   section: s: 0x%lx, c: 0x%lx\n", mm->start_brk, mm->brk);
 9     printk("mmap  section: s: 0x%lx\n", mm->mmap_base);
10     printk("stack section: s: 0x%lx\n", mm->start_stack);
11     printk("arg   section: [0x%lx   0x%lx]\n", mm->arg_start, mm->arg_end);
12     printk("env   section: [0x%lx   0x%lx]\n", mm->env_start, mm->env_end);
13 
14     return 0;
15 }
第5行將進程的名字以及pid列印出來 第6行列印進程的代碼段的範圍 第7行列印進程的data段的範圍,其中存放的是已初始化全局變數。而bss段存放的是未初始化全局變數,存放位置緊跟在data段後面,堆區之前 第8行列印進程的堆區的起始地址和當前地址 第9行列印進程的mmap區的基地址,這裡的mmap區是向下增長的。具體mmap區的基地址跟系統允許的當前進程的用戶棧的大小有關,用戶棧的最大size越大,mmap區的基地址就越小。修改用戶棧的最大尺寸需要用到ulimit -s xxx命令,單位是KB,表示用戶棧的最大尺寸,用戶棧的尺寸可以上G,而內核棧卻只有區區的2個頁。 第10行列印進程的用戶棧的起始地址,向下增長 第11行和第12行的暫不關心。   下麵是remap_pfn_mmap的實現:
 1 static int remap_pfn_mmap(struct file *file, struct vm_area_struct *vma)
 2 {
 3     unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
 4     unsigned long pfn_start = (virt_to_phys(kbuff) >> PAGE_SHIFT) + vma->vm_pgoff;
 5     unsigned long virt_start = (unsigned long)kbuff + offset;
 6     unsigned long size = vma->vm_end - vma->vm_start;
 7     int ret = 0;
 8 
 9     printk("phy: 0x%lx, offset: 0x%lx, size: 0x%lx\n", pfn_start << PAGE_SHIFT, offset, size);
10 
11     ret = remap_pfn_range(vma, vma->vm_start, pfn_start, size, vma->vm_page_prot);
12     if (ret)
13         printk("%s: remap_pfn_range failed at [0x%lx  0x%lx]\n",
14             __func__, vma->vm_start, vma->vm_end);
15     else
16         printk("%s: map 0x%lx to 0x%lx, size: 0x%lx\n", __func__, virt_start,
17             vma->vm_start, size);
18 
19     return ret;
20 }

第3行的vma_pgoff表示的是該vma表示的區間在緩衝區中的偏移地址,單位是頁。這個值是用戶調用mmap時傳入的最後一個參數,不過用戶空間的offset的單位是位元組(當然必須是頁對齊),進入內核後,內核會將該值右移PAGE_SHIFT(12),也就是轉換為以頁為單位。因為要在第9行列印這個編譯地址,所以這裡將其再左移PAGE_SHIFT,然後賦值給offset。

第4行計算內核緩衝區中將被映射到用戶空間的地址對應的物理頁幀號。virt_to_phys接受的虛擬地址必須在低端記憶體範圍內,用戶將虛擬地址轉換為物理地址,而vmaloc返回的虛擬地址不在低端記憶體範圍內,所以需要用專門的函數。 第5行計算內核緩衝區中將被映射到用戶空間的地址對應的虛擬地址 第6行計算該vma表示的記憶體區間的大小 第11行調用remap_pfn_range將物理頁幀號pfn_start對應的物理記憶體映射到用戶空間的vm->vm_start處,映射長度為該虛擬記憶體區的長度。由於這裡的內核緩衝區是用kzalloc分配的,保證了物理地址的連續性,所以會將物理頁幀號從pfn_start開始的(size >> PAGE_SHIFT)個連續的物理頁幀依次按序映射到用戶空間。   將驅動編譯成模塊後,insmod到內核。  

二、用戶測試程式

這裡的五個測試程式都很簡單,只是為了證明他們之間確實共用了同一塊記憶體。 user_1.c:
 1 #define PAGE_SIZE (4*1024)
 2 #define BUF_SIZE (16*PAGE_SIZE)
 3 #define OFFSET (0)
 4 
 5 int main(int argc, const char *argv[])
 6 {
 7     int fd;
 8     char *addr = NULL;
 9 
10     fd = open("/dev/remap_pfn", O_RDWR);
11     
12     addr = mmap(NULL, BUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, fd, OFFSET);
13     
14     sprintf(addr, "I am %s\n", argv[0]);
15 
16     while(1)
17         sleep(1);
18     return 0;
19 }

第10和第12行,打開設備節點,然後從內核空間映射64KB的記憶體到用戶空間,首地址存放在addr中,由於後面既要寫入也要共用,所以設置了對應的flags。這裡指定的offset是0,即映射前64KB。

第14行輸出字元串到addr指向的虛擬地址空間   user_2.c:
 1 #define PAGE_SIZE (4*1024)
 2 #define BUF_SIZE (16*PAGE_SIZE)
 3 #define OFFSET (0)
 4 
 5 int main(int argc, const char *argv[])
 6 {
 7     int fd;
 8     char *addr = NULL;
 9 
10     fd = open("/dev/remap_pfn", O_RDWR);
11     
12     addr = mmap(NULL, BUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, fd, OFFSET);
13     
14     printf("%s", addr);
15 
16     while(1)
17         sleep(1);
18 
19     return 0;
20 }

user_2跟user_1實現一般一樣,不同之處是將addr指向的虛擬地址空間的內容列印出來。

  user_3.c:
 1 #define PAGE_SIZE (4*1024)
 2 #define BUF_SIZE (16*PAGE_SIZE)
 3 #define OFFSET (16*PAGE_SIZE)
 4 
 5 int main(int argc, const char *argv[])
 6 {
 7     int fd;
 8     char *addr = NULL;
 9 
10     fd = open("/dev/remap_pfn", O_RDWR);
11 
12     addr = mmap(NULL, BUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, fd, OFFSET);
13 
14     sprintf(addr, "I am %s\n", argv[0]);
15 
16     while(1)
17         sleep(1);
18     return 0;
19 }

第12行的OFFSET設置的是64KB,表示將內核緩衝區的後64KB映射到用戶空間

第14行,向緩衝區中輸入字元串   user_4.c:
 1 #define PAGE_SIZE (4*1024)
 2 #define BUF_SIZE (16*PAGE_SIZE)
 3 #define OFFSET (16*PAGE_SIZE)
 4 
 5 int main(int argc, const char *argv[])
 6 {
 7     int fd;
 8     char *addr = NULL;
 9 
10     fd = open("/dev/remap_pfn", O_RDWR);
11 
12     addr = mmap(NULL, BUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, fd, OFFSET);
13 
14     printf("%s", addr);
15 
16     while(1)
17         sleep(1);
18     return 0;
19 }
第12行的OFFSET設置的是64KB,表示將內核緩衝區的後64KB映射到用戶空間 第14行,輸出緩衝區中內容   user_5.c:
 1 #define PAGE_SIZE (4*1024)
 2 #define BUF_SIZE (32*PAGE_SIZE)
 3 #define OFFSET (0)
 4 
 5 int main(int argc, const char *argv[])
 6 {
 7     int fd;
 8     char *addr = NULL;
 9     int *brk;
10 
11     fd = open("/dev/remap_pfn", O_RDWR);
12 
13     addr = mmap(NULL, BUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, fd, 0);
14     memset(addr, 0x0, BUF_SIZE);
15 
16     printf("Clear Finished\n");
17 
18     while(1)
19         sleep(1);
20     return 0;
21 }
第13行,將內核緩衝區的整個128KB都映射到用戶空間 第14行,清除緩衝區中內容  

三、測試

1、內核空間的虛擬記憶體佈局

在內核的啟動log里可以看到內核空間的虛擬記憶體佈局信息:
 1 [    0.000000] Virtual kernel memory layout:
 2 [    0.000000]     vector  : 0xffff0000 - 0xffff1000   (   4 kB)
 3 [    0.000000]     fixmap  : 0xffc00000 - 0xfff00000   (3072 kB)
 4 [    0.000000]     vmalloc : 0xf0800000 - 0xff800000   ( 240 MB)
 5 [    0.000000]     lowmem  : 0xc0000000 - 0xf0000000   ( 768 MB)
 6 [    0.000000]     pkmap   : 0xbfe00000 - 0xc0000000   (   2 MB)
 7 [    0.000000]     modules : 0xbf000000 - 0xbfe00000   (  14 MB)
 8 [    0.000000]       .text : 0xc0008000 - 0xc0800000   (8160 kB)
 9 [    0.000000]       .init : 0xc0b00000 - 0xc0c00000   (1024 kB)
10 [    0.000000]       .data : 0xc0c00000 - 0xc0c7696c   ( 475 kB)
11 [    0.000000]        .bss : 0xc0c78000 - 0xc0cc9b8c   ( 327 kB)
用kzalloc分配的記憶體會落在第5行表示的虛擬記憶體範圍內 用vmalloc分配的記憶體會落在第4行表示的虛擬記憶體範圍內  

2、用戶虛擬地址空間的佈局

下麵是Linux系統下用戶的虛擬記憶體佈局大致信息:    

3、user_1和user_2

運行user1:
[root@vexpress mnt]# ./user_1

可以看到如下內核log:

 1 [ 2494.835749] client: user_1 (870)
 2 [ 2494.835918] code  section: [0x8000   0x87f4]
 3 [ 2494.836047] data  section: [0x107f4   0x1092c]
 4 [ 2494.836165] brk   section: s: 0x11000, c: 0x11000
 5 [ 2494.836307] mmap  section: s: 0xb6f17000
 6 [ 2494.836441] stack section: s: 0xbe909e20
 7 [ 2494.836569] arg   section: [0xbe909f23   0xbe909f2c]
 8 [ 2494.836689] env   section: [0xbe909f2c   0xbe909ff3]
 9 [ 2494.836943] phy: 0x8eb60000, offset: 0x0, size: 0x10000
10 [ 2494.837176] remap_pfn_mmap: map 0xeeb60000 to 0xb6d75000, size: 0x10000

 

進程號是870,可以分別用下麵的查看一下該進程的地址空間的map信息:

 1 [root@vexpress mnt]# cat /proc/870/maps 
 2 00008000-00009000 r-xp 00000000 00:12 1179664    /mnt/user_1
 3 00010000-00011000 rw-p 00000000 00:12 1179664    /mnt/user_1
 4 b6d75000-b6d85000 rw-s 00000000 00:10 8765       /dev/remap_pfn
 5 b6d85000-b6eb8000 r-xp 00000000 b3:01 143        /lib/libc-2.18.so
 6 b6eb8000-b6ebf000 ---p 00133000 b3:01 143        /lib/libc-2.18.so
 7 b6ebf000-b6ec1000 r--p 00132000 b3:01 143        /lib/libc-2.18.so
 8 b6ec1000-b6ec2000 rw-p 00134000 b3:01 143        /lib/libc-2.18.so
 9 b6ec2000-b6ec5000 rw-p 00000000 00:00 0 
10 b6ec5000-b6ee6000 r-xp 00000000 b3:01 188        /lib/libgcc_s.so.1
11 b6ee6000-b6eed000 ---p 00021000 b3:01 188        /lib/libgcc_s.so.1
12 b6eed000-b6eee000 rw-p 00020000 b3:01 188        /lib/libgcc_s.so.1
13 b6eee000-b6f0e000 r-xp 00000000 b3:01 165        /lib/ld-2.18.so
14 b6f13000-b6f15000 rw-p 00000000 00:00 0 
15 b6f15000-b6f16000 r--p 0001f000 b3:01 165        /lib/ld-2.18.so
16 b6f16000-b6f17000 rw-p 00020000 b3:01 165        /lib/ld-2.18.so
17 be8e9000-be90a000 rw-p 00000000 00:00 0          [stack]
18 bed1c000-bed1d000 r-xp 00000000 00:00 0          [sigpage]
19 bed1d000-bed1e000 r--p 00000000 00:00 0          [vvar]
20 bed1e000-bed1f000 r-xp 00000000 00:00 0          [vdso]
21 ffff0000-ffff1000 r-xp 00000000 00:00 0          [vectors]

上面的每一行都可以表示一個vma的映射信息,其中第4行是需要關心的:

1 b6d75000-b6d85000 rw-s 00000000 00:10 8765      /dev/remap_pfn

含義:

"b6d75000"是vma->vm_start的值,"b6d85000"是vma->vm_end的值,b6d85000減b6d75000是64KB,即給vma表示的虛擬記憶體區域的大小。 "rw-s"表示的是vma->vm_flags,其中's'表示share,'p'表示private "00000000"表示偏移量,也就是vma->vm_pgoff的值 "00:10"表示該設備節點的主次設備號 "8765"表示該設備節點的inode值 "/dev/remap_pfn"表示設備節點的名字。   也可以用pmap查看該進程的虛擬地址空間映射信息:
 1 [root@vexpress mnt]# pmap -x 870
 2 870: {no such process} ./user_1
 3 Address      Kbytes     PSS   Dirty    Swap  Mode  Mapping
 4 00008000       4       4       0       0  r-xp  /mnt/user_1
 5 00010000       4       4       4       0  rw-p  /mnt/user_1
 6 b6d75000      64       0       0       0  rw-s  /dev/remap_pfn
 7 b6d85000    1228     424       0       0  r-xp  /lib/libc-2.18.so
 8 b6eb8000      28       0       0       0  ---p  /lib/libc-2.18.so
 9 b6ebf000       8       8       8       0  r--p  /lib/libc-2.18.so
10 b6ec1000       4       4       4       0  rw-p  /lib/libc-2.18.so
11 b6ec2000      12       8       8       0  rw-p    [ anon ]
12 b6ec5000     132      64       0       0  r-xp  /lib/libgcc_s.so.1
13 b6ee6000      28       0       0       0  ---p  /lib/libgcc_s.so.1
14 b6eed000       4       4       4       0  rw-p  /lib/libgcc_s.so.1
15 b6eee000     128     122       0       0  r-xp  /lib/ld-2.18.so
16 b6f13000       8       8       8       0  rw-p    [ anon ]
17 b6f15000       4       4       4       0  r--p  /lib/ld-2.18.so
18 b6f16000       4       4       4       0  rw-p  /lib/ld-2.18.so
19 be8e9000     132       4       4       0  rw-p  [stack]
20 bed1c000       4       0       0       0  r-xp  [sigpage]
21 bed1d000       4       0       0       0  r--p  [vvar]
22 bed1e000       4       0       0       0  r-xp  [vdso]
23 ffff0000       4       0       0       0  r-xp  [vectors]
24 --------  ------  ------  ------  ------
25 total        1808     662      48       0

 

然後運行user_2:
1 [root@vexpress mnt]# ./user_2
2 I am ./user_1

可以看到user_1寫入的信息,下麵是內核log以及虛擬地址空間映射信息:

 1 [ 2545.832903] client: user_2 (873)
 2 [ 2545.833087] code  section: [0x8000   0x87e0]
 3 [ 2545.833178] data  section: [0x107e0   0x10918]
 4 [ 2545.833262] brk   section: s: 0x11000, c: 0x11000
 5 [ 2545.833346] mmap  section: s: 0xb6fb5000
 6 [ 2545.833423] stack section: s: 0xbea0ee20
 7 [ 2545.833499] arg   section: [0xbea0ef23   0xbea0ef2c]
 8 [ 2545.833590] env   section: [0xbea0ef2c   0xbea0eff3]
 9 [ 2545.833761] phy: 0x8eb60000, offset: 0x0, size: 0x10000
10 [ 2545.833900] remap_pfn_mmap: map 0xeeb60000 to 0xb6e13000, size: 0x10000
11 
12 [root@vexpress mnt]# cat /proc/873/maps 
13 00008000-00009000 r-xp 00000000 00:12 1179665    /mnt/user_2
14 00010000-00011000 rw-p 00000000 00:12 1179665    /mnt/user_2
15 b6e13000-b6e23000 rw-s 00000000 00:10 8765       /dev/remap_pfn
16 b6e23000-b6f56000 r-xp 00000000 b3:01 143        /lib/libc-2.18.so
17 b6f56000-b6f5d000 ---p 00133000 b3:01 143        /lib/libc-2.18.so
18 b6f5d000-b6f5f000 r--p 00132000 b3:01 143        /lib/libc-2.18.so
19 

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

-Advertisement-
Play Games
更多相關文章
  • C# //將格式為yyyyMMdd的時間轉化為yyyy-MM-dd類型 詳例: string beginDate="20140224"; beginDate =DateTime.ParseExact(beginDate,"yyyyMMdd",CultureInfo.CurrentCulture).T ...
  • 這次由於項目的需求:什麼定時發送郵件通知,定時篩選取消客戶下單未支付的訂單 重新撿起定時器,在網上翻來找去找到 Quartz.Net老字型大小了並不表示它就真的老了哦 github:https://github.com/quartznet/quartznet 當然介紹的園子里文章很多跟官方文檔https ...
  • DataTable轉List public static List<T> ToListModel<T>(this DataTable table) where T : new() { var type = typeof(T); var properties = type.GetProperties( ...
  • 3day Python基礎語法 1、運算符:算數運算符、比較運算符、賦值運算符、邏輯運算符 A、算數運算符:a=10,b=3 + 加 a+b - 減 a-b * 乘 a*b / 除 a/b %取模 a%b (取餘數) //取整數 a//b (取商的整數部分) **冪 a**b (a的b次方) B、比 ...
  • 1,首先需要在一臺有MacOS系統,在Apple stroe下載MacOS High Sierra安裝程式; 2,準備一個至少8G容量的U盤; 3,打開 “應用程式 → 實用工具 → 磁碟工具”,將U盤「抹掉」(格式化) 成「Mac OS X 擴展(日誌式)」 格式、GUID 分區圖,並將U盤命名為 ...
  • python ./setup.py install --record install.txt cat install.txt | xargs rm -rf ...
  • 硬體:pixhawk 2.4.8 代碼:2017/12/30 PX4 firemare px4fmuv2 MCU: STM32F427VIT6, STN32F103C8T6. 感測器: MPU6000; L3GD20; LSM303D; MS5611, 省略了一個L3GD20焊盤. CAN口用的是M ...
  • fxml文件使用SceneBuilder打開報錯 解決方法:Window-->Preferences-->JavaFX-->browse 路徑是可執行的JavaFX Scene Builder文件,例如:C:\Program Files (x86)\Oracle\JavaFX Scene Build ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...