Linux設備管理(四)_從sysfs回到ktype

来源:http://www.cnblogs.com/xiaojiang1025/archive/2016/12/21/6202298.html
-Advertisement-
Play Games

sysfs 是一個基於ramfs的文件系統,在2.6內核開始引入,用來導出內核對象(kernel object)的數據、屬性以及到用戶空間。與同樣用於查看內核數據的proc不同,sysfs只關心具有層次結構的設備信息,比如系統中的匯流排,驅動以及已經載入的模塊等,而諸如PID等信息還是使用proc來管 ...


sysfs是一個基於ramfs的文件系統,在2.6內核開始引入,用來導出內核對象(kernel object)的數據、屬性以及到用戶空間。與同樣用於查看內核數據的proc不同,sysfs只關心具有層次結構的設備信息,比如系統中的匯流排,驅動以及已經載入的模塊等,而諸如PID等信息還是使用proc來管理。本質上,sysfs文件的層次結構就是基於內核中kset與kobject邏輯結構來組織的。從驅動開發的角度,sysfs為我們提供了除了虛擬字元設備的read/write/ioctlproc系統之外的另外一種通過用戶空間訪問內核數據的方式。想要使用sysfs,編譯內核的時候需要定義CONFIG_SYSFS,可以通過mount -t sysfs sysfs /sys命令來掛載sysfs到"/sys"目錄。本文以ubuntu15.04(3.19)為例分析。

sysfs目錄結構

sysfs的佈局體現內核的數據結構,頂層的目錄有

$ls /sys/
block/  bus/  class/  dev/  devices/  firmware/  fs/  hypervisor/  kernel/  module/  power/

每一個目錄都對應內核中的一個kset,每一個kset還會包含一些kobject或其他kset。下麵針對常用目錄做一個簡單的介紹

/sys/block/

塊設備的存放目錄,這是一個過時的介面,按照sysfs的設計理念,所有的設備都存放在"sys/devices/"同時在"sys/bus/"或(和)"sys/class/"存放相應的符號鏈接,所以現在這個目錄只是為了提高相容性的設計,裡面的文件已經被全部替換成了符號鏈接,只有在編譯內核的時候勾選CONFIG_SYSFS_DEPRECATED才會有這個目錄,

sys $ll block/
total 0
lrwxrwxrwx  1 root root 0 12月 20 11:29 dm-0 -> ../devices/virtual/block/dm-0/
lrwxrwxrwx  1 root root 0 12月 20 11:29 dm-1 -> ../devices/virtual/block/dm-1/
...

/sys/bus/

bus包含了系統中所有的匯流排,比如我的系統當前提供的匯流排有:

sys $ls bus/
acpi/   container/  i2c/    media/     mipi-dsi/  pci/  pnp/    sdio/   usb/    platform/     scsi/     spi/   ...

每一種匯流排通常還有兩個子目錄:device和driver,這兩個字目錄分別對應內核中的兩個kset,同時bus本身也對應一個kset,也有自己的kobject和以及(可能)有相應的ktype。我們可以查看相應的kset屬性。

sys $ls bus/platform/
devices/  drivers/  drivers_autoprobe  drivers_probe  uevent
sys $cat bus/platform/drivers_autoprobe 
1

我們可以扒一下3.19的源碼,找到這個屬性

//include/linux/platform_device.h
 22 struct platform_device {
            ...
 26         struct device   dev;
            ...
 38 };
//include/linux/device.h
 731 struct device {
             ...
 744         struct bus_type *bus;           /* type of bus device is on */
             ...
 800 };

 104 struct bus_type {
             ...
 129         struct subsys_private *p;
             ...
 131 };
//drivers/base/base.h
 28 struct subsys_private {
 29         struct kset subsys;
 30         struct kset *devices_kset;
            ...
 38         unsigned int drivers_autoprobe:1;       #Bingo!!!
            ...
 43 };  

同時,根據kset的組織形式,平臺匯流排的設備kset鏈接了掛接在平臺匯流排上的所有設備,所以"platform/devices"下應該可以查看到,要註意的事,為了使一個設備在sysfs中只有一個實例,很多目錄都是使用符號鏈接的形式,下麵顯示的結果也驗證了這種設計。

sys $ll bus/platform/devices/
lrwxrwxrwx 1 root root 0 12月 19 08:17 ACPI0003:00 -> ../../../devices/pci0000:00/0000:00:14.3/PNP0C09:00/ACPI0003:00/  ...

sys $ll bus/platform/drivers/thinkpad_acpi/
lrwxrwxrwx  1 root root    0 12月 20 20:19 thinkpad_acpi -> ../../../../devices/platform/thinkpad_acpi/
--w-------  1 root root 4096 12月 20 20:18 uevent
--w-------  1 root root 4096 12月 20 20:19 unbind
-r--r--r--  1 root root 4096 12月 20 20:19 version
...

sys $cat bus/platform/drivers/thinkpad_acpi/version 
ThinkPad ACPI Extras v0.25

/sys/class/

按照設備功能對系統設備進行分類的結果放在這個目錄,如系統所有輸入設備都會出現在 "/sys/class/input"之下。和sys/bus一樣,sys/class最終的文件都是符號鏈接,這種設備可以保證整個系統中每一個設備都只有一個實例。

sys $l class/
ata_device/   i2c-adapter/    net/     rtc/           spi_master/    gpio/      input/   ...

sys $l class/input/
event0@  event10@  event12@   mouse0@   ...

/sys/dev/

按照設備號對字元設備和塊設備進行分類的結果放在這個目錄,同樣,文件依然是使用符號鏈接的形式鏈接到"sys/devices/"中的相應文件

sys $ls dev/
block/  char/

sys $ls dev/char/
10:1@    10:236@  108:0@   1:3@    ...

/sys/devices/

如前所述,所有的設備文件實例都在"sys/devices/"目錄下,

sys $ls devices/
amd_nb/  breakpoint/  cpu/  ibs_fetch/  ibs_op/  LNXSYSTM:00/  pci0000:00/  platform/  ...

sys $ls devices/platform/serial8250/
driver@  driver_override  modalias  power/  subsystem@  tty/  uevent

sys $cat devices/platform/serial8250/driver_override 
(null)

"sys/class/","sys/bus/","sys/devices"是設備開發中最重要的幾個目錄。他們之間的關係可以用下圖表示。

/sys/fs

這裡按照設計是用於描述系統中所有文件系統,包括文件系統本身和按文件系統分類存放的已掛載點,但目前只有 fuse,gfs2 等少數文件系統支持 sysfs 介面,一些傳統的虛擬文件系統(VFS)層次控制參數仍然在 sysctl (/proc/sys/fs) 介面中中;

/sys/kernel

這裡是內核所有可調整參數的位置,目前只有 uevent_helper, kexec_loaded, mm, 和新式的 slab 分配器等幾項較新的設計在使用它,其它內核可調整參數仍然位於 sysctl (/proc/sys/kernel) 介面中 ;

/sys/module

這裡有系統中所有模塊的信息,不論這些模塊是以內聯(inlined)方式編譯到內核映像文件(vmlinuz)中還是編譯為外部模塊(ko文件),都可能會出現在 /sys/module 中:編譯為外部模塊(ko文件)在載入後會出現對應的/sys/module/

/sys/power

這裡是系統中電源選項,這個目錄下有幾個屬性文件可以用於控制整個機器的電源狀態,如可以向其中寫入控制命令讓機器關機、重啟等。

/sys/slab

(對應 2.6.23 內核,在 2.6.24 以後移至/sys/kernel/slab) 從2.6.23 開始可以選擇 SLAB 記憶體分配器的實現,並且新的 SLUB(Unqueued Slab Allocator)被設置為預設值;如果編譯了此選項,在 /sys 下就會出現 /sys/slab ,裡面有每一個 kmem_cache 結構體的可調整參數。對應於舊的 SLAB 記憶體分配器下的/proc/slabinfo 動態調整介面, 新式的 /sys/kernel/slab/

sysfs與kobject、kset

對於每一個註冊到內核的kobject,都會在sysfs中創建一個目錄!!!一個目錄!!!一個目錄!!!,目錄名就是kobject.name,這個目錄會從屬於kobject.parent對應的目錄,我們就可以實現在sysfs中用樹狀結構來呈現內核中的kobject。最初的sysfs下頂層目錄下的目錄使用subsystem的結構,在某些書中還會見到這個概念,不過現在已經被kset替代了。在 kobject 下還有一些符號鏈接文件,指向其它的 kobject,這些符號鏈接文件用於組織上面所說的 device, driver, bus_type, class, module 之間的關係。我們再來看看kobject結構:

//include/linux/kobject.h
 63 struct kobject {      
 64         const char              *name;
 65         struct list_head        entry;
 66         struct kobject          *parent;
 67         struct kset             *kset;
 68         struct kobj_type        *ktype;
 69         struct kernfs_node      *sd;
 70         struct kref             kref;
            ...
 79 };
//include/linux/kernfs.h
106 struct kernfs_node {
            ...
125         union {
126                 struct kernfs_elem_dir          dir;
127                 struct kernfs_elem_symlink      symlink;
128                 struct kernfs_elem_attr         attr;
129         };
            ...
137 };

這其中的symlink就組成了下麵的符號鏈接,許許多多這樣的符號鏈接就構成了整個sysfs的符號鏈接體系

sys $ll devices/platform/serial8250/
lrwxrwxrwx  1 root root    0 12月 20 16:17 driver -> ../../../bus/platform/drivers/serial8250/
-rw-r--r--  1 root root 4096 12月 20 16:17 driver_override
-rw-r--r--  1 root root 4096 12月 20 16:17 uevent
...

sysfs與ktype

在sysfs中,kobject的屬性(kobject.ktype.attribute)可以以普通文件的形式導出,sysfs還提供了使用文件I/O直接修改內核屬性的機制,這些屬性一般都是ASCII格式的文本文件(ktype.attribute.name)或二進位文件(通常只用在sys/firmware中),為了提高效率,可以將具有同一類型的屬性放置在一個文件中,這樣就可以使用數組進行批量修改,不要在一個文件中使用混合類型,也不要使用多行數據,這些做法會大大降低代碼的可讀性,下麵就是一個屬性的定義,可以看到,屬性中並沒有包含讀寫屬性的函數,但是從面向對象的思想看,內核提供了兩個用於讀寫attribute結構的函數。

//include/linux/sysfs.h
 29 struct attribute {
 30     const char      *name;
 31     umode_t         mode;
 32 #ifdef CONFIG_DEBUG_LOCK_ALLOC       
 33     bool            ignore_lockdep:1; 
 34     struct lock_class_key   *key;     
 35     struct lock_class_key   skey;  
 36 #endif
 37 };

int sysfs_create_file(struct kobject * kobj, const struct attribute * attr);
void sysfs_remove_file(struct kobject * kobj, const struct attribute * attr);

由於一個ktype往往包含很多屬性(default_attr是一個二級指針),當用戶通過sysfs讀寫一個kobject的屬性的時候,會自動回調ktype中的sysfs_ops->show()sysfops->remove(),所以一個典型的做法是,當我們創建了一個繼承自kobject的子類child後,同時還會創建兩個調用了sysfs_create_file()sys_remove_file()的讀寫函數,並將它們註冊到struct sysfs_ops中。比如內核使用的struct device就將相應的方法和屬性都封裝在了一起。

//include/linux/device.h
 512 /* interface for exporting device attributes */
 513 struct device_attribute {      
 514         struct attribute        attr;
 515         ssize_t (*show)(struct device *dev, struct device_attribute *attr,
 516                         char *buf);
 517         ssize_t (*store)(struct device *dev, struct device_attribute *attr,
 518                          const char *buf, size_t count);
 519 };

 560 extern int device_create_file(struct device *device,const struct device_attribute *entry);
 562 extern void device_remove_file(struct device *dev,const struct device_attribute *attr);

此外,內核甚至還提供了輔助定義這個屬性的巨集

//include/linux/device.h
 539 #define DEVICE_ATTR(_name, _mode, _show, _store) \ 
 540         struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)

//include/linux/sysfs.h
 75 #define __ATTR(_name, _mode, _show, _store) {                           \         
 76         .attr = {.name = __stringify(_name),                            \
 77                  .mode = VERIFY_OCTAL_PERMISSIONS(_mode) },             \
 78         .show   = _show,                                                \
 79         .store  = _store,                                               \
 80 }

有了這個巨集,我們就可以直接通過這個介面創建我們自己的對象

static DEVICE_ATTR(foo, S_IWUSR | S_IRUGO, show_foo, store_foo);

我們可以追一下源碼,可以發現,我們使用的自動創建設備文件device_create()就會調用device_create_file()並最終調用sysfs_create_file()

"drivers/base/core.c"
device_create()
   └── device_create_vargs()
            └── device_create_groups_vargs()
                        └── device_add()
                                    └── device_create_file()
                                                ├── "include/linux/sysfs.h"
                                                └── sysfs_create_file()

eg_0:

#define to_dev(obj) container_of(obj, struct device, kobj)
#define to_dev_attr(_attr) container_of(_attr, struct device_attribute, attr)

static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr,
                             char *buf)
{
        struct device_attribute *dev_attr = to_dev_attr(attr);
        struct device *dev = to_dev(kobj);
        ssize_t ret = -EIO;

        if (dev_attr->show)
                ret = dev_attr->show(dev, dev_attr, buf);
        if (ret >= (ssize_t)PAGE_SIZE) {
                print_symbol("dev_attr_show: %s returned bad count\n",
                                (unsigned long)dev_attr->show);
        }
        return ret;
}

讀寫attribute

當一個子系統定義了一個新的屬性,它必須執行一組針對的sysfs操作以便對實現對屬性的讀寫,這些讀寫操作通過回調ktype.sysfs_ops.show()和store()

//include/linux/sysfs.h
184 struct sysfs_ops {  
185         ssize_t (*show)(struct kobject *, struct attribute *, char *);
186         ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
187 };

當進行讀寫的時候,sysfs會分配一個PAGE_SIZE大小的buf並把它作為參數傳入這兩個函數,同時,對於每一次對屬性的讀寫操作,sysfs都會調用這兩個函數,所以,調用read系統調用的時候,show()方法應該填滿整個buf,註意一個屬性應該是一個或一組相似的值,所以這種機制並不會浪費很多系統資源。這種機制允許用戶讀取一部分內容並且可以任意的移動文件位置指針,如果用戶空間將文件指針置為0或以0為偏移量調用了pread()show()會被重新調用並且再填滿一個buf。類似地,調用write()系統調用的時候,sysfs希望第一次傳入的buf是被填滿的,sysfs會在傳入的數據最後自動加NUL,這可以讓諸如sysfs_strqe()一類的函數用起來更安全。當對sysfs執行寫操作時,用戶空間應該首先讀取整個文件的內容,按自己的需求改變其中的一部分並回寫,屬性讀寫操作應該使用同一個buf

tips:

  1. 通過read()/write()傳遞數據不同,這裡的show()/store()里的buf已經是內核空間的了,不需要進行copy_to_user() etc
  2. 寫操作會導致show方法重新執行而忽視當前文件位置指針的位置
  3. buf是PAGE_SIZE大小
  4. show()方法返回列印到buf的實際byte數,這個就是scnprintf()的返回值
  5. 在進行格式化列印到用戶空間的時候,show必須用scnprintf()除非你能保證棧不會溢出
  6. stor應該返回buf中使用的數據的byte數目
  7. show或store應該設置合適的返回值確保全全

eg_1


static ssize_t show_name(struct device *dev, struct device_attribute *attr,
                         char *buf)
{
    return scnprintf(buf, PAGE_SIZE, "%s\n", dev->name);
}

static ssize_t store_name(struct device *dev, struct device_attribute *attr,
                          const char *buf, size_t count)
{
        snprintf(dev->name, sizeof(dev->name), "%.*s",
                 (int)min(count, sizeof(dev->name) - 1), buf);
    return count;
}

static DEVICE_ATTR(name, S_IRUGO, show_name, store_name);

內核已實現介面

內核中已經使用sysfs實現了很多的讀寫函數,下麵是幾個典型的

設備

/* devices */
/* structure */
//include/linux/device.h)
 512 /* interface for exporting device attributes */
 513 struct device_attribute {
 514         struct attribute        attr;
 515         ssize_t (*show)(struct device *dev, struct device_attribute *attr,
 516                         char *buf);
 517         ssize_t (*store)(struct device *dev, struct device_attribute *attr,
 518                          const char *buf, size_t count);
 519 };

/* Declaring */
 539 #define DEVICE_ATTR(_name, _mode, _show, _store) \  
 540         struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)

/* Creation/Removal */
 560 extern int device_create_file(struct device *device,const struct device_attribute *entry);
 562 extern void device_remove_file(struct device *dev,const struct device_attribute *attr);

匯流排驅動

/* bus drivers */
/* Structure */
//include/linux/device.h
  44 struct bus_attribute {  
  45         struct attribute        attr;
  46         ssize_t (*show)(struct bus_type *bus, char *buf);
  47         ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);
  48 };

/* Declaring */
  50 #define BUS_ATTR(_name, _mode, _show, _store)   \    
  51         struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store)

/* Creation/Removal */
  57 extern int __must_check bus_create_file(struct bus_type *,struct bus_attribute *);
  59 extern void bus_remove_file(struct bus_type *, struct bus_attribute *);

設備驅動

/* device drivers */
/* Structure */
//include/linux/device.h

 265 struct driver_attribute {    
 266         struct attribute attr;
 267         ssize_t (*show)(struct device_driver *driver, char *buf);
 268         ssize_t (*store)(struct device_driver *driver, const char *buf,
 269                          size_t count);
 270 };

/* Declaring */
 272 #define DRIVER_ATTR(_name, _mode, _show, _store) \  
 273         struct driver_attribute driver_attr_##_name = __ATTR(_name, _mode, _show, _store)

/* Creation/Removal */
 281 extern int __must_check driver_create_file(struct device_driver *driver,   
 282                                         const struct driver_attribute *attr);
 283 extern void driver_remove_file(struct device_driver *driver,
 284                                const struct driver_attribute *attr);

參考文檔


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

-Advertisement-
Play Games
更多相關文章
  • 概覽屏幕 概覽屏幕 概覽屏幕(也稱為最新動態屏幕、最近任務列表或最近使用的應用)是一個系統級別 UI,其中列出了最近訪問過的 Activity 和任務。 用戶可以瀏覽該列表並選擇要恢復的任務,也可以通過滑動清除任務將其從列表中移除。 對於 Android 5.0 版本(API 級別 21),包含不同 ...
  • 廣度優先搜索 在給定圖G=(V,E)和一個特定的源頂點s的情況下,廣度優先搜索系統地探索G中的邊,以期“發現”可從s 到達的所有頂點,並計算s 到所有這些可達頂點之間的距離(即最少的邊數)。該搜索演算法同時還能生成一棵根為s、且包括所有s 的可達頂點的廣度優先樹。對從s 可達的任意頂點v,廣度優先樹中 ...
  • 一、SQLite保存數據介紹 將資料庫保存在資料庫對於重覆或者結構化數據(比如契約信息)而言是理想之選。SQL資料庫的主要原則之一是架構:資料庫如何組織正式聲明。架構體現於用於創建資料庫的SQL語句。它有助於創建伴隨類,即契約類,其以一種系統性、自記錄的方式明確指定架構佈局。 契約類是用於定義URL ...
  • res/layout中的佈局文件太雜,沒有層次感,受不了的我治好想辦法解決這個問題。 前幾天看博客說可以使用插件分組,可惜我沒找到。知道看到另一篇博客時,才知道這個方法不能用了。 不能用插件,那就手動來吧。(http://blog.csdn.net/u011156012/article/detail ...
  • 使用Android Studio 一、在build.gradle(Module:app)添加代碼 下載,調用插件 1 apply plugin: 'com.android.application' 2 3 android { 4 compileSdkVersion 24 5 buildToolsVe ...
  • 自從Android6.0發佈以來,在許可權上做出了很大的變動,不再是之前的只要在manifest設置就可以任意獲取許可權,而是更加的註重用戶的隱私和體驗,不會再強迫用戶因拒絕不該擁有的許可權而導致的無法安裝的事情,也不會再不征求用戶授權的情況下,就可以任意的訪問用戶隱私,而且即使在授權之後也可以及時的更改 ...
  • 1. 操作系統中的棧和堆 我們先來看看一個由C/C++/OBJC編譯的程式占用記憶體分佈的結構: 棧區(stack):由系統自動分配,一般存放函數參數值、局部變數的值等。由編譯器自動創建與釋放。其操作方式類似於數據結構中的棧,即後進先出、先進後出的原則。 例如:在函數中申明一個局部變數int b;系統 ...
  • 在平攤分析中,執行一系列數據結構操作所需要的時間是通過對執行的所有操作求平均而得出的。 平攤分析可以用來證明在一系列操作中,通過對所有操作求平均之後,即使其中單一的操作具有較大的代價,平均代價還是很小的。平攤分析與平均情況分析的不同之處在於它不牽涉到概率;平攤分析保證在最壞情況下,每個操作具有平均性 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...