深入理解Linux字元設備驅動

来源:http://www.cnblogs.com/yueqian-scut/archive/2016/04/09/5371583.html
-Advertisement-
Play Games

文章從上層應用訪問字元設備驅動開始,一步步地深入分析Linux字元設備的軟體層次、組成框架和交互、如何編寫驅動、設備文件的創建和mdev原理,對Linux字元設備驅動有全面的講解。本文整合之前發表的《Linux字元設備驅動剖析》和《 Linux 設備文件的創建和mdev》兩篇文章,基於linux字元 ...


  文章從上層應用訪問字元設備驅動開始,一步步地深入分析Linux字元設備的軟體層次、組成框架和交互、如何編寫驅動、設備文件的創建和mdev原理,對Linux字元設備驅動有全面的講解。本文整合之前發表的《Linux字元設備驅動剖析》和《 Linux 設備文件的創建和mdev》兩篇文章,基於linux字元設備驅動的所有相關知識給讀者一個完整的呈現。

一、從最簡單的應用程式入手

  1.很簡單,open設備文件,read、write、ioctl,最後close退出。如下:

二、/dev目錄與文件系統

  2.     /dev是根文件系統下的一個目錄文件,/代表根目錄,其掛載的是根文件系統的yaffs格式,通過讀取/根目錄這個文件,就能分析list出其包含的各個目錄,其中就包括dev這個子目錄。即在/根目錄(也是一個文件,其真實存在於flash介質)中有一項這樣的數據:

是否目錄  偏移     大小    名稱 -- --

1    0xYYYY 0Xmmm   dev -- --

  Ls/ 命令即會使用/掛載的yaffs文件系統來讀取出根目錄文件的內容,然後list出dev(是一個目錄)。即這時還不需要去讀取dev這個目錄文件的內容。Cd dev即會分析dev掛載的文件系統的超級塊的信息,superblock,而不再理會在flash中的dev目錄文件的數據。

 

  3.     /dev在根文件系統構建的時候會掛載為tmpfs. Tmpfs是一個基於虛擬記憶體的文件系統,主要使用RAM和SWAP(Ramfs只是使用物理記憶體)。即以後讀寫dev這個目錄的操作都轉到tmpfs的操作,確切地講都是針對RAM的操作,而不再是通過yaffs文件系統的讀寫函數去訪問flash介質。Tmpfs基於RAM,所以在掉電後回消失。因此/dev目錄下的設備文件都是每次linux啟動後創建的。

  掛載過程:/etc/init.d/rcS

                      Mount –a 會讀取/etc/fstab的內容來掛載,其內容如下:

  4.     /dev/NULL和/dev/console是在製作根文件系統的時候靜態創建的,其他設備文件都是系統載入根文件系統和各種驅動初始化過程中自動創建的,當然也可以通過命令行手動mknod設備文件。

 

三、設備文件的創建

  5.     /dev目錄下的設備文件基本上都是通過mdev來動態創建的。mdev是一個用戶態的應用程式,位於busybox工具箱中。其創建過程包括:

  1)  驅動初始化或者匯流排匹配後會調用驅動的probe介面,該介面會調用device_create(設備類, 設備號, 設備名);在/sys/class/設備類目錄生成唯一的設備屬性文件(包括設備號和設備名等信息),並且發送uvent事件(KOBJ_ADD和環境變數,如路徑等信息)到用戶空間(通過socket方式)。

  2)  mdev是一個work_thread線程,收到事件後會分析出/sys/class/設備類的對應文件,最終調用mknod動態來創建設備文件,而這個設備文件內容主要是設備號(這個設備文件對應的inode會記錄文件的屬性是一個設備(其他屬性還包括目錄,一般文件,符號鏈接等))。應用程式open(device_name,…)最重要的一步就是通過文件系統介面來獲得該設備文件的內容—設備號。

  6.     如果初始化過程中沒有調用device_create介面來創建設備文件,則需要手動通過命令行調用mknod介面來創建設備文件,方可在應用程式中訪問。

  7.     mknod介面分析,通過系統調用後對應調用sys_mknod,其是vfs層的介面。

  Sys_mknod(設備名, 設備號)

  vfs通過逐一路徑link_path_walk,分析出dev掛載了tmpfs,所以調用tmpfs->mknod

  shmem_mknod(structinode *dir, struct dentry *dentry, int mode, dev_t dev)

       inode = shmem_get_inode(dir->i_sb,dir, mode, dev, VM_NORESERVE);

              inode = new_inode(sb);

              switch (mode & S_IFMT) {

              default:

                     inode->i_op =&shmem_special_inode_operations;

                     init_special_inode(inode,mode, dev);//以下是函數展開

                           

                     break;

              case S_IFREG://file

              case S_IFDIR://DIR

              case S_IFLNK://dentry填入inode信息,這時對應的dentry和inode都已經存在於記憶體中。

       d_instantiate(dentry, inode);

  可見,tmpfs的目錄和文件都是像ramfs一樣一般都存在於記憶體中。通過ls命令來獲取目錄的信息則由dentry數據結構的內容來獲取,而文件的信息由inode數據結構的內容來提供。Inode包括設備文件的設備號i_rdev,文件屬性(i_mode: S_ISCHR),inode操作集i_fop(對於設備文件來說就是如何open這個inode)。

 

四、open設備文件

  9. open設備文件的最終目的是為了獲取到該設備驅動的file_operations操作集,而該介面集是struct file的成員,open返回file數據結構指針:

struct file {

       conststruct file_operations   *f_op;

       unsignedint             f_flags;//可讀,可寫等

       …

};

  以下是led設備驅動的操作介面。open("/dev/LED",O_RDWR)就是為了獲得led_fops。

static conststruct file_operations led_fops = {

    .owner          =THIS_MODULE,

    .open            =led_open,

    .write     = led_write,

};

  10. 仔細看應用程式int fd =open("/dev/LED",O_RDWR),open的返回值是int,並不是file,其實是為了操作系統和安全考慮。fd位於應用層,而file位於內核層,它們都同屬進程相關概念。在Linux中,同一個文件(對應於唯一的inode)可以被不同的進程打開多次,而每次打開都會獲得file數據結構。而每個進程都會維護一個已經打開的file數組,fd就是對應file結構的數組下標。因此,file和fd在進程範圍內是一一對應的關係。

  11. open介面分析,通過系統調用後對應調用sys_open,其是vfs層的介面

  Sys_open(/dev/led)

  SYSCALL_DEFINE3(open,const char __user *, filename, int, flags, int, mode)

  do_sys_open(AT_FDCWD,/dev/tty, flags, mode);

               

  //path_init返回時nd->dentry即為搜索路徑文件名的起點

  //link_path_walk一步步建立打開路徑的各個目錄的dentry和inode

 

       其中inode->i_fop在mknod的init_special_inode調用中被賦值為def_chr_fops。以下該變數的定義,因此, open(inode, f)即調用到chrdev_open。其可以看出是字元設備所對應的文件系統介面,我們姑且稱其為字元設備文件系統。

conststruct file_operations def_chr_fops = {

       .open = chrdev_open,

};

  繼續分析chrdev_open:

Kobj_lookup(cdev_map,inode->i_rdev, &idx)即是通過設備的設備號(inode->i_rdev)在cdev_map中查找設備對應的操作集file_operations.關於如何查找,我們在理解字元設備驅動如何註冊自己的file_operations後再回頭來分析這個問題。

 

五、字元設備驅動的註冊

  12. 字元設備對應cdev數據結構:

struct cdev {

   struct kobject kobj;         // 每個 cdev 都是一個 kobject

   struct module*owner;       // 指向實現驅動的模塊

   const structfile_operations *ops;   // 操縱這個字元設備文件的方法

   struct list_headlist;       //對應的字元設備文件的inode->i_devices 的鏈表頭

   dev_t dev;                 // 起始設備編號

   unsigned intcount;       // 設備範圍號大小

};

  13. led設備驅動初始化和設備驅動註冊

  1)    cdev_init是初始化cdev結構體,並將led_fops填入該結構。

  2)    cdev_add

  3)    cdev_map是一個全家指針變數,類型如下:

  4)    kobj_map使用hash散列表來存儲cdev數據結構。通過註冊設備的主設備號major來獲得cdev_map->probes數組的索引值i(i = major % 255),然後把一個類型為struct probe的節點對象加入到probes[i]所管理的鏈表中,probes[i]->data即是cdev數據結構,而probes[i]->dev和range代表字元設備號和範圍。

 

六、再述open設備文件

  14. 通過第五步的字元設備的註冊過程,應該對Kobj_lookup查找led_ops是很容易理解的。至此,已經獲得led設備驅動的led_ops。接著立刻調用file->f_ops->open即調用了led_open,在該函數中會對led用到的GPIO進行ioremap並設置GPIO方向、上下拉等硬體初始化。

  15. 最後,chrdev_open一步步返回,最後到

  do_sys_open的struct file *f = do_filp_open(dfd, tmp, flags, mode, 0);返回。

Fd_install(fd, f)即是在當前進程中將存有led_ops的file指針填入進程的file數組中,下標是fd。最後將fd返回給用戶空間。而用戶空間只要傳入fd即可找到對應的file數據結構。

 

七、設備操作

  16. 這裡以設備寫為例,主要是控制led的亮和滅。

  write(fd,val,1)系統調用後對應sys_write,其對應所有的文件寫,包括目錄、一般文件和設備文件,一般文件有位置偏移的概念,即讀寫之後,當前位置會發生變化,所以如要跳著讀寫,就需要fseek。對於字元設備文件,沒有位置的概念。所以我們重點跟蹤vfs_write的過程。

  1) fget_light在當前進程中通過fd來獲得file指針

  2) vfs_write

       3) 對於led設備,file->f_op->write即是led_write。

        在該介面中實現對led設備的控制。

 

八、再論字元設備驅動的初始化

       綜上所述,字元設備的初始化包括兩個主要環節:

1) 字元設備驅動的註冊,即通過cdev_add向系統註冊cdev數據結構,提供file_operations操作集和設備號等信息,最終file_operations存放在全局指針變數cdev_map指向的Hash表中,其可以通過設備號索引並遍歷得到。

2) 通過device_create(設備類, 設備號, 設備名)在sys/class/設備類中創建設備屬性文件併發送uevent事件,而mdev利用該信息自動調用mknod在/dev目錄下創建對應的設備文件,以便應用程式訪問。

 

  那麼如何通過通過device_create來創建設備文件呢,mdev的原理又是什麼呢?我們接著分析。

 

九、設備類相關知識

  設備類是虛擬的,並沒有直接對應的物理實物,只是為了更好地管理同一類設備導出到用戶空間而產生的目錄和文件。整個過程涉及到sysfs文件系統,該文件系統是為了展示linux設備驅動模型而構建的文件系統,是基於ramfs,linux根目錄中的/sysfs即掛載了sysfs文件系統。

  Struct kobject數據結構是sysfs的基礎,kobject在sysfs中代表一個目錄,而linux的驅動(struct driver)、設備(struct device)、設備類(struct class)均是從kobject進行派生的,因此他們在sysfs中都對應於一個目錄。而數據結構中附屬的struct device_attribute、driver_attribute、class_attribute等屬性數據結構在sysfs中則代表一個普通的文件。

       Struct kset是struct kobject的容器,即Struct kset可以成為同一類struct kobject的父親,而其自身也有kobject成員,因此其又可能和其他kobject成為上一級kset的子成員。

 

十、兩種創建設備文件的方式

       在設備驅動中cdev_add將struct file_operations和設備號註冊到系統後,為了能夠自動產生驅動對應的設備文件,需要調用class_create和device_create,並通過uevent機制調用mdev(嵌入式linux由busybox提供)來調用mknod創建設備文件。當然也可以不調用這兩個介面,那就手工通過命令行mknod來創建設備文件。

 

十一、設備類和設備相關數據結構

       1. include/linux/kobject.h

struct kobject {

constchar           *name;//名稱

structlist_head   entry;//kobject鏈表

structkobject            *parent;//即所屬kset的kobject

structkset           *kset;//所屬kset

structkobj_type *ktype;//屬性操作介面

};

 

struct kset {

struct list_head list;//管理同屬於kset的kobject

struct kobject kobj;//可以成為上一級父kset的子目錄

const struct kset_uevent_ops *uevent_ops;//uevent處理介面

};

假設Kobject A代表一個目錄,kset B代表幾個目錄(包括A)的共同的父目錄。

則A.kset=B; A.parent=B.kobj.

 

 

2.include/linux/device.h

struct class {//設備類

const char           *name;//設備類名稱

struct module            *owner;//創建設備類的module

structclass_attribute              *class_attrs;//設備類屬性

struct device_attribute            *dev_attrs;//設備屬性

struct kobject                   *dev_kobj;//kobject再sysfs中代表一個目錄

….

struct class_private *p;//設備類得以註冊到系統的連接件

};

 

3.drivers/base/base.h

struct class_private {

//該設備類同樣是一個kset ,包含下麵的class_devices;同時在class_subsys填充父kset

       struct kset class_subsys;

structklist class_devices;//設備類包含的設備(kobject)

structclass *class;//指向設備類數據結構,即要創建的本級目錄信息

};

 

4.include/linux/device.h

structdevice {//設備

structdevice              *parent;//sysfs/devices/中的父設備

structdevice_private *p;//設備得以註冊到系統的連接件

structkobject kobj;//設備目錄

constchar           *init_name;//設備名稱

structbus_type   *bus;//設備所屬匯流排

       structdevice_driver *driver;    //設備使用的驅動

structklist_node       knode_class;//連接到設備類的klist

structclass         *class;//所屬設備類

       conststruct attribute_group **groups;

        …

        }

 

5. drivers/base/base.h

struct device_private {

structklist  klist_children;//連接子設備

structklist_node  knode_parent;//加入到父設備鏈表

structklist_node  knode_driver;//加入到驅動的設備鏈表

structklist_node  knode_bus;//加入到匯流排的鏈表

structdevice *device;//對應設備結構

};

 

6. 解釋

  class_private是class的私有結構,class通過class_private註冊到系統中;device_private是device的私有結構,device通過device_private註冊到系統中。註冊到系統中也是將相應的數據結構加入到系統已經存在的鏈表中,但是這些鏈接的細節並不希望暴露給用戶,也沒有必要暴露出來,所以才有private的結構。而class和device則通過sysfs向用戶層提供信息。

 

十二、創建設備類目錄文件

       1. 在驅動通過cdev_add將struct file_operations介面集和設備註冊到系統後,即利用class_create介面來創建設備類目錄文件。

led_class = class_create(THIS_MODULE,"led_class");

        __class_create(owner, name,&__key);

               cls->name = name;//設備類名

               cls->owner= owner;//所屬module

                      retval =__class_register(cls, key);

                             structclass_private *cp;

                     //將類的名字led_class賦值給對應的kset

                             kobject_set_name(&cp->class_subsys.kobj,"%s", cls->name);        

// 填充class_subsys所屬的父kset:ket:sysfs/class.

                             cp->class_subsys.kobj.kset= class_kset;

                      //填充class屬性操作介面

                             cp->class_subsys.kobj.ktype= &class_ktype;

                      cp->class = cls;//通過cp可以找到class

                             cls->p = cp;//通過class可以找到cp

                      //創建led_class設備類目錄

                             kset_register(&cp->class_subsys);

                      //在led_class目錄創建class屬性文件

                             add_class_attrs(class_get(cls));

 

2. 繼續展開kset_register

        kset_register(&cp->class_subsys);

               kobject_add_internal(&k->kobj);

               // parent即class_kset.kobj, 即/sysfs/class對應的目錄

                      parent =kobject_get(kobj->parent);

                                    create_dir(kobj);

                                           //創建一個led _class設備類目錄

                                           sysfs_create_dir(kobj);

                      該介面是sysfs文件系統介面,代表創建一個目錄,不再展開。

 

3. 上述提到的class_kset 在class_init被創建

class_kset= kset_create_and_add("class", NULL, NULL);

第三個傳參為NULL,代表預設在/sysfs/創建class目錄。

 

十三、創建設備目錄和設備屬性文件

        1.利用class_create介面來創建設備類目錄文件後,再利用device_create介面來創建具體設備目錄和設備屬性文件。

        led_device =device_create(led_class, NULL, led_devno, NULL, "led");

               device_create_vargs

                      dev->devt = devt;//設備號

                     dev->class= class;//設備類led_class

                            dev->parent =parent;//父設備,這裡是NULL

                             kobject_set_name_vargs(&dev->kobj,fmt, args)//設備名”led”

                             device_register(dev)註冊設備

        2. 繼續展開device_register(dev)

               device_initialize(dev);

                      dev->kobj.kset= devices_kset;//設備所屬/sysfs/devices/

               device_add(dev)

                      device_private_init(dev)//初始化device_private

                      dev_set_name(dev,"%s", dev->init_name);//賦值dev->kobject的名稱

                      setup_parent(dev,parent);//建立device和父設備的kobject的聯繫

        //kobject_add在/sysfs/devices/目錄下創建設備目錄led,kobject_add是和kset_register相似的介面,只不過前者針對kobject,後者針對kset。

                      kobject_add(&dev->kobj,dev->kobj.parent, NULL);           

                             kobject_add_varg

                                    kobj->parent= parent;

                                    kobject_add_internal(kobj)

                                           create_dir(kobj);//創建設備目錄

               //在剛創建的/sysfs/devices/led目錄下創建uevent屬性文件,名稱是”uevent”

                      device_create_file(dev,&uevent_attr);

               //在剛創建的/sysfs/devices/led目錄下創建dev屬性文件,名稱是”dev”,該屬性文件的內容就是設備號

                      device_create_file(dev,&devt_attr);

               //在/sysfs/class/led_class/目錄下建立led設備的符號連接,所以打開/sysfs/class/led_class/led/目錄也能看到dev屬性文件,讀出設備號。

                      device_add_class_symlinks(dev);

               //創建device屬性文件,包括設備所屬匯流排的屬性和attribute_group屬性

device_add_attrs()

               bus_add_device(dev)//將設備加入匯流排

              //觸發uevent機制,並通過調用mdev來創建設備文件。

              kobject_uevent(&dev->kobj,KOBJ_ADD);

        //匹配設備和匯流排的驅動,匹配成功就調用驅動的probe介面,不再展開

               bus_probe_device(dev);

 

        3. 展開kobject_uevent(&dev->kobj, KOBJ_ADD);

               kobject_uevent_env(kobj,action, NULL);

                      kset= top_kobj->kset;                               

                             uevent_ops = kset->uevent_ops; //即device_uevent_ops

                             //subsystem即設備所屬的設備類的名稱”led_class”

                             subsystem= uevent_ops->name(kset, kobj);

                             //devpath即/sysfs/devices/led/

                             devpath= kobject_get_path(kobj, GFP_KERNEL);

                             //添加各種環境變數

                             add_uevent_var(env,"ACTION=%s", action_string);

                             add_uevent_var(env,"DEVPATH=%s", devpath);

                             add_uevent_var(env,"SUBSYSTEM=%s", subsystem);

                             uevent_ops->uevent(kset,kobj, env);

                                    add_uevent_var(env,"MAJOR=%u", MAJOR(dev->devt));

                                           add_uevent_var(env,"MINOR=%u", MINOR(dev->devt));

                                           add_uevent_var(env,"DEVNAME=%s", name);

                                           add_uevent_var(env,"DEVTYPE=%s", dev->type->name);

                                           //還會增加匯流排相關的一些屬性環境變數等等。

                             #ifdefined(CONFIG_NET)//如果是PC的linux會通過socket的方式嚮應用層發送uevent事件消息,但在嵌入式linux中不啟用該機制。

                             #endif

                            

                             argv [0] = uevent_helper;//即/sbin/mdev

                      argv [1] = (char *)subsystem;//”led_class”

                             argv [2] = NULL;

                             add_uevent_var(env,"HOME=/");

                      add_uevent_var(env,

                                           "PATH=/sbin:/bin:/usr/sbin:/usr/bin");

                      call_usermodehelper(argv[0], argv,

                                  env->envp, UMH_WAIT_EXEC);

 

4. 上述提到的devices_kset在devices_init被創建

               devices_kset= kset_create_and_add("devices", &device_uevent_ops, NULL);

               第三個傳參為NULL,代表預設在/sysfs/創建devices目錄

5.  上述設備屬性文件

staticstruct device_attribute devt_attr =

               __ATTR(dev, S_IRUGO, show_dev, NULL);

 

        static ssize_t show_dev(struct device*dev, struct device_attribute *attr,

               char *buf){{

              returnprint_dev_t(buf, dev->devt); //即返回設備的設備號

}

       6.devices設備目錄響應uevent事件的操作

staticconst struct kset_uevent_ops device_uevent_ops = {

              .filter =  dev_uevent_filter,

              .name =              dev_uevent_name,

              .uevent =     dev_uevent,

};

 

7.call_usermodehelper是從內核空間調用用戶空間程式的介面。

8. 對於嵌入式系統來說,busybox採用的是mdev,在系統啟動腳本rcS 中會使用命令

echo /sbin/mdev >/proc/sys/kernel/hotplug

uevent_helper[]數組即讀入/proc/sys/kernel/hotplug文件的內容,即 “/sbin/mdev” 

 

十四、創建設備文件

       輪到mdev出場了,以上描述都是在sysfs文件系統中創建目錄或者文件,而應用程式訪問的設備文件則需要創建在/dev/目錄下。該項工作由mdev完成。

       Mdev的原理是解釋/etc/mdev.conf文件定義的命名設備文件的規則,併在該規則下根據環境變數的要求來創建設備文件。Mdev.conf由用戶層指定,因此更具靈活性。本文無意展開對mdev配置腳本的分析。

       Busybox/util-linux/mdev.c

       int mdev_main(int argc UNUSED_PARAM, char**argv)

              xchdir("/dev");

              if (argv[1] &&strcmp(argv[1], "-s")//系統啟動時mdev –s才會執行這個分支

              else

              action= getenv("ACTION");

              env_path= getenv("DEVPATH");

                     G.subsystem= getenv("SUBSYSTEM");     

                     snprintf(temp, PATH_MAX,"/sys%s", env_path);//到/sysfs/devices/led目錄

                     make_device(temp,/*delete:*/ 0);

                            strcpy(dev_maj_min,"/dev");

                            //讀出dev屬性文件,得到設備號

                            open_read_close(path,dev_maj_min + 1, 64);

                            ….

                            mknod(node_name,rule->mode | type, makedev(major, minor))

       最終我們會跟蹤到mknod在/dev/目錄下創建了設備文件。

 

我們追求:

1.從上電第一行代碼、系統第一行代碼、模塊第一行代碼、應用第一行代碼,深入講解嵌入式軟體生命周期。
2 深刻理解硬體體系,以面向對象思維剖析各種匯流排和驅動框架。
3 聚焦軟體層次設計和框架設計
4 知其然,知其所以然

 

更多的嵌入式linux和android、物聯網、汽車自動駕駛等領域原創技術分享請關註微信公眾號:


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

-Advertisement-
Play Games
更多相關文章
  • 代理模式是常用的設計模式,其特征是代理類與委托類具有相同的介面,在具體實現上,有靜態代理和動態代理之分。代理類與委托類之間通常會存在關聯關係,一個代理類的對象與一個委托類的對象關聯,代理類的對象本身並不真正實現服務,而是通過調用委托類的對象的相關方法,來提供特定的服務,也就是說代理類主要負責為委托類 ...
  • 偶然知道原來有些網站的驗證碼圖片都是隨機生成的,後來聽人講了一下,就做了這個小例子 生成圖片,繪製背景,數字,干擾線用到了java.awt包,主要使用BufferedImage來生成圖片,然後使用Graphics對象來繪製內容。 代碼如下: 這是生成圖片,在網頁顯示中,用img標簽就可以,通過ser ...
  • 在C語言學習005:不能修改的字元串中我們知道字元串是存儲在常量區域的,將它賦值給數組實際是將常量區的字元串副本拷貝到棧記憶體中,如果將這個數組賦值給指針,我們可以改變數組中的元素,就像下麵那樣 但是現在我們不想讓指針可以修改字元串數組的中的元素,而又可以得到字元串中的元素,那麼我麽需要再拷貝一份字元 ...
  • 一.關鍵字和標示符 1.關鍵字:c規定的有特殊含義的單詞(也就是系統起的名字),全部是小寫,有32個; 由關鍵字引出數據類型和流程類型 1.分類: (1)數據類型:整型用int標示,字元型用char表示,實型(浮點型)用float或者double表示 (2)流程類型:順序結構,選擇結構,迴圈結構 其 ...
  • malloc和free都包含在<stdlib.h>頭文件中 局部變數由於存儲在棧中,一旦離開函數,變數就會被釋放,當我們需要將數據持久使用,就需要將數據保存到堆中,而在堆中申請記憶體空間就需要malloc方法;malloc方法在堆中建立一片記憶體空間,然後返回一個指針,這個指針是void*類型,保存了這 ...
  • 題目1111:單詞替換 輸入一個字元串,以回車結束(字元串長度<=100)。該字元串由若幹個單片語成,單詞之間用一個空格隔開,所有單詞區分大小寫。現需要將其中的某個單詞替換成另一個單詞,並輸出替換之後的字元串。 多組數據。每組數據輸入包括3行, 第1行是包含多個單詞的字元串 s, 第2行是待替換的單 ...
  • 非Lazy版本的普通單例實現: Lazy版本的單例實現: 對比分析: 使用Lazy<T>來初始化,使得代碼看起來更為簡潔易懂。其實非Lazy<T>版本的單例實現從本質上說就是一個簡單的對象Lazy的實現。 一般對於一些占用大的記憶體的對象,常常使用Lazy方式來初始化達到優化的目的。 ...
  • 版權聲明 版權聲明:原創文章 禁止轉載 請通過右側公告中的“聯繫郵箱([email protected])”聯繫我 勿用於學術性引用。 勿用於商業出版、商業印刷、商業引用以及其他商業用途。 本文不定期修正完善。 本文鏈接:http://www.cnblogs.com/wlsandwho/p/ ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...