當前位置:首頁 » 編程軟體 » 編譯驅動原理

編譯驅動原理

發布時間: 2023-05-21 20:36:33

⑴ 如何編譯linux驅動模塊

第一步:准備源代碼

首先我們還是要來編寫一個符合linux格式的模塊文件,這樣我們才能開始我們的模塊編譯。假設我們有一個源文件mymod.c。它的源碼如下:

mymoles.c
1. #include <linux/mole.h> /* 引入與模塊相關的宏 */
2. #include <linux/init.h> /* 引入mole_init() mole_exit()函數 */
3. #include <linux/moleparam.h> /* 引入mole_param() */
4
5. MODULE_AUTHOR("Yu Qiang");
6. MODULE_LICENSE("GPL");
7
8. static int nbr = 10;
9. mole_param(nbr, int, S_IRUGO);
10.
11. static int __init yuer_init(void)
12.{
13. int i;
14. for(i=0; i<nbr; i++)
15. {
16. printk(KERN_ALERT "Hello, How are you. %d/n", i);
17. }
18. return 0;
19.}
20.
21.static void __exit yuer_exit(void)
22.{
23. printk(KERN_ALERT"I come from yuer's mole, I have been unlad./n");
24.}
25.
26. mole_init(yuer_init);
27. mole_exit(yuer_exit);

我們的源文件就准備的差不多了,這就是一個linux下的模塊的基本結構。第9行是導出我們的符號變數nbr。這樣在你載入這個模塊的時候可以動態修改這個變數的值。稍後將演示。yuer_init()函數將在模塊載入的時候運行,通過輸出的結果可以看到我們的模塊是否載入成功。

第二步:編寫Makefile文件

首先還是來看看我們Makefile的源文件,然後我們再來解釋;

Makefile
obj-m := moles.o #要生成的模塊名
moles-objs:= mymod.o #生成這個模塊名所需要的目標文件

KDIR := /lib/moles/`uname -r`/build
PWD := $(shell pwd)

default:
make -C $(KDIR) M=$(PWD) moles

clean:
rm -rf *.o .* .cmd *.ko *.mod.c .tmp_versions

ARM平台
Makefile

obj-m += mymod.o
KDIR := /home/workspace2/kernel/linux-2.6.25 #如果是用於arm平台,則內核路徑為arm內核的路徑
PWD = $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) moles
clean:
rm -rf *.o

在arm板上插入是
insmod mymod
如果出現以下錯誤
insmod: chdir(/lib/moles): No such file or directory
則運行
mkdir /lib/moles/2.6.25 (與arm內核版本相同)
並將mymod.ko文件復制到該目錄下
cp mymod.ko /lib/moles/2.6.25
然後再執行 (insmod 只在/lib/moles/2.6.25目錄下查找相關驅動模塊)
insmod mymod

現在我來說明一下這個Makefile。請記住是大寫的Makefile而不是小寫的makefile;
obj-m :這個變數是指定你要聲稱哪些模塊模塊的格式為 obj-m := <模塊名>.o
moles-objs :這個變數是說明聲稱模塊moles需要的目標文件 格式要求 <模塊名>-objs := <目標文件>
切記:模塊的名字不能取與目標文件相同的名字。如在這里模塊名不能取成 mymod;
KDIR :這是我們正在運行的操作系統內核編譯目錄。也就是編譯模塊需要的環境
M= :指定我們源文件的位置
PWD :這是當前工作路徑$(shell )是make的一個內置函數。用來執行shell命令。

第三步:編譯模塊

現在我們已經准備好了我們所需要的源文件和相應的Makefile。我們現在就可以編譯了。在終端進入源文件目錄輸入make
運行結果:
make[1]: Entering directory `/usr/src/linux-headers-2.6.24-24-generic'
CC [M] /home/yuqiang/桌面/mymole/mymoles.o
LD [M] /home/yuqiang/桌面/mymole/moles.o
Building moles, stage 2.
MODPOST 1 moles
CC /home/yuqiang/桌面/mymole/moles.mod.o
LD [M] /home/yuqiang/桌面/mymole/moles.ko
make[1]: Leaving directory `/usr/src/linux-headers-2.6.24-24-generic'

第四步:載入/卸載我們的模塊

從上面的編譯中我可以看到。已經有一個moles.ko生成了。這就是我們的模塊了。現在我們就可以來載入了。
首先在終端輸入:sudo insmod moles.ko
現在我們來看看我們的模塊載入成功沒有呢?
在終端輸入:dmesg | tail -12 這是查看內核輸出信息的意思。tail -12 顯示最後12條;
顯示結果如下:
[17945.024417] sd 9:0:0:0: Attached scsi generic sg2 type 0
[18046.790019] usb 5-8: USB disconnect, address 9
[19934.224812] Hello, How are you. 0
[19934.224817] Hello, How are you. 1
[19934.224818] Hello, How are you. 2
[19934.224820] Hello, How are you. 3
[19934.224821] Hello, How are you. 4
[19934.224822] Hello, How are you. 5
[19934.224824] Hello, How are you. 6
[19934.224825] Hello, How are you. 7
[19934.224826] Hello, How are you. 8
[19934.224828] Hello, How are you. 9

看到了吧。我們的模塊的初始化函數yuer_init();已經成功運行了。說明我們的模塊已經載入成功;
現在我們再來卸載模塊試試看。
在終端輸入:sudo rmmod moles
在終端輸入:dmesg | tail -3
[19934.224826] Hello, How are you. 8
[19934.224828] Hello, How are you. 9
[20412.046932] I come from yuer's mole, I have been unlad.

可以從列印的信息中看到,我們的模塊的退出函數已經被執行了。說明我們的模塊已經被成功的卸載了。到目前位置我們就已經算是對模塊的編譯到編譯運行算是有了一個整體上的認識了。對於以後深入的學習還是應該有點幫助的。下面我們將在看看於模塊相關的一些簡單的操作。

第五步:載入模塊時傳遞參數
在終端輸入:sudo insmod mole_name.ko nbr=4
在終端輸入:dmesg | tail -6
顯示結果如下:
[20800.655694] Hello, How are you. 9
[21318.675593] I come from onefile mole, I have been unlad.
[21334.425373] Hello, How are you. 0
[21334.425378] Hello, How are you. 1
[21334.425380] Hello, How are you. 2
[21334.425381] Hello, How are you. 3

這樣我們就可以看到在模塊載入的時候動態設置了我們的一個變數。初始化函數中的循環只執行了4次。
可能你會問我怎麼知道一個模塊可以設置那些變數呢。當然,你可以先不設變數載入一次。然後可以在終端輸入ls /sys/mole/<moles_name>/parameters/來查看。在這里我們是這樣輸入的
在終端輸入:ls /sys/moedle/moles/parameters/
顯示結果:
nbr

如果我們的模塊載入成功了。最後我們還可以通過modinfo來查看我們的模塊信息。如下
在終端輸入:sudo modinfo moles.ko
顯示結果:
filename: moles.ko
license: GPL
author: Yu Qiang
srcversion: 20E9C3C4E02D130E6E92533
depends:
vermagic: 2.6.24-24-generic SMP mod_unload 586
parm: nbr:int

⑵ linux下怎麼編譯安裝驅動

linux 編譯安裝驅動有兩種,動態載入與靜態載入
動態載入
一,編譯,在指點內核樹下編譯,生成.o文件或.ko文件
二,將生成的.o或.ko文件拷到相應目錄,一般是/lib/mole/kernel下面
三,用insmod命令載入,用rmmod命令卸載
靜態載入
靜態載入主要就是編譯內核。就是將編寫好的驅動放進內核相應的目錄下面。然後編譯內核。然後運行編譯好的內核。

⑶ 驅動的原理是什麼

驅動是一種軟體,功能是建立操作系統和硬體之間的橋梁。
主板上敗吵空的介面、控制器這些都是硬體能夠實現操作系統讓它乾的功能。
操作系統給下層硬體發功能指令的時候是不關心底層硬體的具體實現方式的。

驅動的原理就是將底層硬體包裝成操作系統能統一調配的功能集

原理圖:
硬體(電路級實現)<-------- 驅動程序(轉換)<--------- 操作察瞎系統(功能原語)

驅動的目的就是屏蔽各種底層硬體之間的差異,隱藏技術細節,給上層軟體提供一個看上去是一樣的功能集。

不同硬體對操作系統功能的具體實現方式是不一樣的,所以不同硬體有著不同的驅動。
同一廠商,尤其是統一系列的產碰桐品,因為實現方式大多類似,故其驅動能在一定范圍內通用。

純手打,希望有幫助

⑷ Linux驅動程序的工作原理

由於你的問題太長我只好轉載別人的手打的太累不好意思~~~
Linux是Unix***作系統的一種變種,在Linux下編寫驅動程序的原理和

思想完全類似於其他的Unix系統,但它dos或window環境下的驅動程序有很大的

區別.在Linux環境下設計驅動程序,思想簡潔,***作方便,功芤埠芮看?但是

支持函數少,只能依賴kernel中的函數,有些常用的***作要自己來編寫,而且調

試也不方便.本人這幾周來為實驗室自行研製的一塊多媒體卡編制了驅動程序,

獲得了一些經驗,願與Linux fans共享,有不當之處,請予指正.

以下的一些文字主要來源於khg,johnsonm的Write linux device driver,

Brennan's Guide to Inline Assembly,The Linux A-Z,還有清華BBS上的有關

device driver的一些資料. 這些資料有的已經過時,有的還有一些錯誤,我依

據自己的試驗結果進行了修正.

一. Linux device driver 的概念

系統調用是***作系統內核和應用程序之間的介面,設備驅動程序是***作系統

內核和機器硬體之間的介面.設備驅動程序為應用程序屏蔽了硬體的細節,這樣

在應用程序看來,硬體設備只是一個設備文件, 應用程序可以象***作普通文件

一樣對硬體設備進行***作.設備驅動程序是內核的一部分,它完成以下的功能:

1.對設備初始化和釋放.

2.把數據從內核傳送到硬體和從硬體讀取數據.

3.讀取應用程序傳送給設備文件的數據和回送應用程序請求的數據.

4.檢測和處理設備出現的錯誤.

在Linux***作系統下有兩類主要的設備文件類型,一種是字元設備,另一種是

塊設備.字元設備和塊設備的主要區別是:在對字元設備發出讀/寫請求時,實際

的硬體I/O一般就緊接著發生了,塊設備則不然,它利用一塊系統內存作緩沖區,

當用戶進程對設備請求能滿足用戶的要求,就返回請求的數據,如果不能,就調用請求函數來進行實際

的I/O***作.塊設備是主要針對磁碟等慢速設備設計的,以免耗費過多的CPU時間

來等待.

已經提到,用戶進程是通過設備文件來與實際的硬體打交道.每個設備文件都

都有其文件屬性(c/b),表示是字元設備還蔤強檣璞?另外每個文件都有兩個設

備號,第一個是主設備號,標識驅動程序,第二個是從設備號,標識使用同一個

設備驅動程序的不同的硬體設備,比如有兩個軟盤,就可以用從設備號來區分

他們.設備文件的的主設備號必須與設備驅動程序在登記時申請的主設備號

一致,否則用戶進程將無法訪問到驅動程序.

最後必須提到的是,在用戶進程調用驅動程序時,系統進入核心態,這時不再是

搶先式調度.也就是說,系統必須在你的驅動程序的子函數返回後才能進行其他

的工作.如果你的驅動程序陷入死循環,不幸的是你只有重新啟動機器了,然後就

是漫長的fsck.//hehe

(請看下節,實例剖析)

讀/寫時,它首先察看緩沖區的內容,如果緩沖區的數據

如何編寫Linux***作系統下的設備驅動程序

Roy G

二.實例剖析

我們來寫一個最簡單的字元設備驅動程序.雖然它什麼也不做,但是通過它

可以了解Linux的設備驅動程序的工作原理.把下面的C代碼輸入機器,你就會

獲得一個真正的設備驅動程序.不過我的kernel是2.0.34,在低版本的kernel

上可能會出現問題,我還沒測試過.//xixi

#define __NO_VERSION__

#include

#include

char kernel_version [] = UTS_RELEASE;

這一段定義了一些版本信息,雖然用處不是很大,但也必不可少.Johnsonm說所

有的驅動程序的開頭都要包含,但我看倒是未必.

由於用戶進程是通過設備文件同硬體打交道,對設備文件的***作方式不外乎就

是一些系統調用,如 open,read,write,close...., 注意,不是fopen, fread.,

但是如何把系統調用和驅動程序關聯起來呢?這需要了解一個非常關鍵的數據

結構:

struct file_operations {

int (*seek) (struct inode * ,struct file *, off_t ,int);

int (*read) (struct inode * ,struct file *, char ,int);

int (*write) (struct inode * ,struct file *, off_t ,int);

int (*readdir) (struct inode * ,struct file *, struct dirent * ,int);

int (*select) (struct inode * ,struct file *, int ,select_table *);

int (*ioctl) (struct inode * ,struct file *, unsined int ,unsigned long

int (*mmap) (struct inode * ,struct file *, struct vm_area_struct *);

int (*open) (struct inode * ,struct file *);

int (*release) (struct inode * ,struct file *);

int (*fsync) (struct inode * ,struct file *);

int (*fasync) (struct inode * ,struct file *,int);

int (*check_media_change) (struct inode * ,struct file *);

int (*revalidate) (dev_t dev);

}

這個結構的每一個成員的名字都對應著一個系統調用.用戶進程利用系統調用

在對設備文件進行諸如read/write***作時,系統調用通過設備文件的主設備號

找到相應的設備驅動程序,然後讀取這個數據結構相應的函數指針,接著把控制

權交給該函數.這是linux的設備驅動程序工作的基本原理.既然是這樣,則編寫

設備驅動程序的主要工作就是編寫子函數,並填充file_operations的各個域.

相當簡單,不是嗎?

下面就開始寫子程序.

#include

#include

#include

#include

#include

unsigned int test_major = 0;

static int read_test(struct inode *node,struct file *file,

char *buf,int count)

{

int left;

if (verify_area(VERIFY_WRITE,buf,count) == -EFAULT )

return -EFAULT;

for(left = count left > 0 left--)

{

__put_user(1,buf,1);

buf++;

}

return count;

}

這個函數是為read調用准備的.當調用read時,read_test()被調用,它把用戶的

緩沖區全部寫1.

buf 是read調用的一個參數.它是用戶進程空間的一個地址.但是在read_test

被調用時,系統進入核心態.所以不能使用buf這個地址,必須用__put_user(),

這是kernel提供的一個函數,用於向用戶傳送數據.另外還有很多類似功能的

函數.請參考.在向用戶空間拷貝數據之前,必須驗證buf是否可用.

這就用到函數verify_area.

static int write_tibet(struct inode *inode,struct file *file,

const char *buf,int count)

{

return count;

}

static int open_tibet(struct inode *inode,struct file *file )

{

MOD_INC_USE_COUNT;

return 0;

} static void release_tibet(struct inode *inode,struct file *file )

{

MOD_DEC_USE_COUNT;

}

這幾個函數都是空***作.實際調用發生時什麼也不做,他們僅僅為下面的結構

提供函數指針。

struct file_operations test_fops = {

NULL,

read_test,

write_test,

NULL, /* test_readdir */

NULL,

NULL, /* test_ioctl */

NULL, /* test_mmap */

open_test,

release_test, NULL, /* test_fsync */

NULL, /* test_fasync */

/* nothing more, fill with NULLs */

};

設備驅動程序的主體可以說是寫好了。現在要把驅動程序嵌入內核。驅動程序

可以按照兩種方式編譯。一種是編譯進kernel,另一種是編譯成模塊(moles),

如果編譯進內核的話,會增加內核的大小,還要改動內核的源文件,而且不能

動態的卸載,不利於調試,所以推薦使用模塊方式。

int init_mole(void)

{

int result;

result = register_chrdev(0, "test", &test_fops);

if (result < 0) {

printk(KERN_INFO "test: can't get major number ");

return result;

}

if (test_major == 0) test_major = result; /* dynamic */

return 0;

}

在用insmod命令將編譯好的模塊調入內存時,init_mole 函數被調用。在

這里,init_mole只做了一件事,就是向系統的字元設備表登記了一個字元

設備。register_chrdev需要三個參數,參數一是希望獲得的設備號,如果是

零的話,系統將選擇一個沒有被佔用的設備號返回。參數二是設備文件名,

參數三用來登記驅動程序實際執行***作的函數的指針。

如果登記成功,返回設備的主設備號,不成功,返回一個負值。

void cleanup_mole(void)

{

unregister_chrdev(test_major, "test");

}

在用rmmod卸載模塊時,cleanup_mole函數被調用,它釋放字元設備test

在系統字元設備表中佔有的表項。

一個極其簡單的字元設備可以說寫好了,文件名就叫test.c吧。

下面編譯

$ gcc -O2 -DMODULE -D__KERNEL__ -c test.c

得到文件test.o就是一個設備驅動程序。

如果設備驅動程序有多個文件,把每個文件按上面的命令行編譯,然後

ld -r file1.o file2.o -o molename.

驅動程序已經編譯好了,現在把它安裝到系統中去。

$ insmod -f test.o

如果安裝成功,在/proc/devices文件中就可以看到設備test,

並可以看到它的主設備號,。

要卸載的話,運行

$ rmmod test

下一步要創建設備文件。

mknod /dev/test c major minor

c 是指字元設備,major是主設備號,就是在/proc/devices里看到的。

用shell命令

$ cat /proc/devices | awk "\$2=="test" {print \$1}"

就可以獲得主設備號,可以把上面的命令行加入你的shell script中去。

minor是從設備號,設置成0就可以了。

我們現在可以通過設備文件來訪問我們的驅動程序。寫一個小小的測試程序。

#include

#include

#include

#include

main()

{

int testdev;

int i;

char buf[10];

testdev = open("/dev/test",O_RDWR);

if ( testdev == -1 )

{

printf("Cann't open file ");

exit(0);

}

read(testdev,buf,10);

for (i = 0; i < 10;i++)

printf("%d ",buf);

close(testdev);

}

編譯運行,看看是不是列印出全1 ?

以上只是一個簡單的演示。真正實用的驅動程序要復雜的多,要處理如中斷,

DMA,I/O port等問題。這些才是真正的難點。請看下節,實際情況的處理。

如何編寫Linux***作系統下的設備驅動程序

Roy G

三 設備驅動程序中的一些具體問題。

1. I/O Port.

和硬體打交道離不開I/O Port,老的ISA設備經常是佔用實際的I/O埠,

在linux下,***作系統沒有對I/O口屏蔽,也就是說,任何驅動程序都可以

對任意的I/O口***作,這樣就很容易引起混亂。每個驅動程序應該自己避免

誤用埠。

有兩個重要的kernel函數可以保證驅動程序做到這一點。

1)check_region(int io_port, int off_set)

這個函數察看系統的I/O表,看是否有別的驅動程序佔用某一段I/O口。

參數1:io埠的基地址,

參數2:io埠佔用的范圍。

返回值:0 沒有佔用, 非0,已經被佔用。

2)request_region(int io_port, int off_set,char *devname)

如果這段I/O埠沒有被佔用,在我們的驅動程序中就可以使用它。在使用

之前,必須向系統登記,以防止被其他程序佔用。登記後,在/proc/ioports

文件中可以看到你登記的io口。

參數1:io埠的基地址。

參數2:io埠佔用的范圍。

參數3:使用這段io地址的設備名。

在對I/O口登記後,就可以放心地用inb(), outb()之類的函來訪問了。

在一些pci設備中,I/O埠被映射到一段內存中去,要訪問這些埠就相當

於訪問一段內存。經常性的,我們要獲得一塊內存的物理地址。在dos環境下,

(之所以不說是dos***作系統是因為我認為DOS根本就不是一個***作系統,它實

在是太簡單,太不安全了)只要用段:偏移就可以了。在window95中,95ddk

提供了一個vmm 調用 _MapLinearToPhys,用以把線性地址轉化為物理地址。但

在Linux中是怎樣做的呢?

2 內存***作

在設備驅動程序中動態開辟內存,不是用malloc,而是kmalloc,或者用

get_free_pages直接申請頁。釋放內存用的是kfree,或free_pages. 請注意,

kmalloc等函數返回的是物理地址!而malloc等返回的是線性地址!關於

kmalloc返回的是物理地址這一點本人有點不太明白:既然從線性地址到物理

地址的轉換是由386cpu硬體完成的,那樣匯編指令的***作數應該是線性地址,

驅動程序同樣也不能直接使用物理地址而是線性地址。但是事實上kmalloc

返回的確實是物理地址,而且也可以直接通過它訪問實際的RAM,我想這樣可

以由兩種解釋,一種是在核心態禁止分頁,但是這好像不太現實;另一種是

linux的頁目錄和頁表項設計得正好使得物理地址等同於線性地址。我的想法

不知對不對,還請高手指教。

言歸正傳,要注意kmalloc最大隻能開辟128k-16,16個位元組是被頁描述符

結構佔用了。kmalloc用法參見khg.

內存映射的I/O口,寄存器或者是硬體設備的RAM(如顯存)一般佔用F0000000

以上的地址空間。在驅動程序中不能直接訪問,要通過kernel函數vremap獲得

重新映射以後的地址。

另外,很多硬體需要一塊比較大的連續內存用作DMA傳送。這塊內存需要一直

駐留在內存,不能被交換到文件中去。但是kmalloc最多隻能開辟128k的內存。

這可以通過犧牲一些系統內存的方法來解決。

具體做法是:比如說你的機器由32M的內存,在lilo.conf的啟動參數中加上

mem=30M,這樣linux就認為你的機器只有30M的內存,剩下的2M內存在vremap

之後就可以為DMA所用了。

請記住,用vremap映射後的內存,不用時應用unremap釋放,否則會浪費頁表。

3 中斷處理

同處理I/O埠一樣,要使用一個中斷,必須先向系統登記。

int request_irq(unsigned int irq ,

void(*handle)(int,void *,struct pt_regs *),

unsigned int long flags,

const char *device);

irq: 是要申請的中斷。

handle:中斷處理函數指針。

flags:SA_INTERRUPT 請求一個快速中斷,0 正常中斷。

device:設備名。

如果登記成功,返回0,這時在/proc/interrupts文件中可以看你請求的

中斷。

4一些常見的問題。

對硬體***作,有時時序很重要。但是如果用C語言寫一些低級的硬體***作

的話,gcc往往會對你的程序進行優化,這樣時序就錯掉了。如果用匯編寫呢,

gcc同樣會對匯編代碼進行優化,除非你用volatile關鍵字修飾。最保險的

辦法是禁止優化。這當然只能對一部分你自己編寫的代碼。如果對所有的代碼

都不優化,你會發現驅動程序根本無法裝載。這是因為在編譯驅動程序時要

用到gcc的一些擴展特性,而這些擴展特性必須在加了優化選項之後才能體現

出來。

關於kernel的調試工具,我現在還沒有發現有合適的。有誰知道請告訴我,

不勝感激。我一直都在printk列印調試信息,倒也還湊合。

關於設備驅動程序還有很多內容,如等待/喚醒機制,塊設備的編寫等。

我還不是很明白,不敢亂說。

⑸ 高手進階:Linux操作系統驅動編譯與運行

一、手工載入測試
1、insmod
./key_test.ko
載入驅動模塊到內核
2、cat
/proc/moles
|grep
key_test
查看key_test模塊在內核中的地址,不加過濾器可以看到全部載入的模塊。
3、lsmod
顯示模塊,這時可以看到所有的模塊名字,後面跟的是主設備號和次設備號。
4、rmmod
key_test
把模塊從內核里卸載。
二、動態載入
1、把key_test.c源代碼放到內核源代碼的/drives/char/下,因為這是屬字元型驅動,放在這編譯到zImage中。
2、這時我們make
menuconfig
編譯內核是看不到key_test這個選項的。我們把這個選項寫到菜單裡面才行。在內核源代碼的/drives/char/下有一個Kconfig文件,打開
(1)
vi
Kconfig
加幾行到裡面:
config
ConFig_key_test
bool
"key
test"
//前面那個bool換成tristate就是支持模塊化編譯
上面句是在make
menuconfig時會出現key
test這個選項在drive/char子菜單下,bool前面是TAB鍵
------help----------
這句是出現在菜單選項下面的
This
key
test
help.
這句是你的驅動的說明會出現在help裡面
(2)在/drivers/char目錄下的Makefile文件里加上一句:
obj-$(CONFIG_key_test)
+=
key_test.o
上面這句是讓Make時把key_test編譯到內核中.
(3)
make
menuconfig
把key_test選項選取上
(4)
make
zImage
生成zImage文件,重啟動載入這個新編的內核。
3、lsmod就能看到key_test了,但是還不能用,沒有介面,也就是/dev下面沒有
4、mknod
/dev/key_test
c
121
0
這是創建設備到/dev下,使普通程序可以調用了,121是在源代碼里定義的它的主設備號,0是次設備號。
5、cat
/dev/key_test
這是相當於open這個設備了,或者寫一個程序直接調用open、write等函數。
fd=("/dev/key_test",ORW);

⑹ 如何編譯驅動程序

驅動的編譯和上層應用程序的編譯完全不同,作為初學者應該先了解一下,即使你還不懂得怎麼寫驅動程序。
首先安裝DDK,然後隨便找一個例子來測試。在菜單中找到BUILD環境菜單執行,不同的系統要使用不同的BUILD環境。會打開一個DOS窗口,這時CD到那個例子程序,輸入 build –cZ回車就可以了。 驅動程序都是用一個由DDK提供的叫build.exe的工具編譯的。此程序以一個名為SOURCES的文件作為輸入,該文件中包含目標可執行文件的名稱、類型和要創建的可執行文件的路徑,注意這個文件沒有後綴名。
SOURCES的文件格式:
TARGETNAME=drivername ,
- 本參數用於指定生成的設備驅動程序名稱(不需後綴名),所產生的文件
- 為drivername.sys.

TARGETPATH=./lib
- 本參數用於指定生成的設備驅動程序所存放的路徑. 一般採用./lib.

TARGETTYPE=DRIVER
- build能夠生成許多不同的目標對象,設備驅動程序一般選用 DRIVER.

INCLUDES=path1;path2;...
- 本參數是可選的, 用於指定其他的#include文件的搜索路徑.

TARGETLIBS=lib1;lib2;...
- 本參數是可選的, 用於指定其他的lib庫文件的搜索路徑.

SOURCES=file1.c file2.c ...
- 本參數用於指定需被編譯的全部源文件名稱, 後綴名不能省略,文件名之間用空格分開.
SOURCES文件是必需的,如果沒有它則表示沒有任何源文件需要編譯。
如果要換行可以用 『/』 符號,表示對上一行的繼續。

也可以創建DIRS文件,DIRS文件用於指定在當前目錄下必須創建的子目錄。
DIRS文件格式:
DIRS文件的內容由一系列用空格分開的目錄名組成
DIRS = /
subdir1 /
subdir2 /
subdir3
DIRS文件是可選的。

有的時候,會提示找不到依賴的文件(.h,.lib 之類),其實設置好 source 文件的
INCLUDES和TARGETLIBS就可以,我第一次編譯時就碰到這個問題,和VC環境區別較大,但習慣就好。

⑺ linux驅動程序結構框架及工作原理分別是什麼

一、Linux device driver 的概念x0dx0ax0dx0a系統調用是操作系統內核和應用程序之間的介面,設備驅動程序是操作系統內核和機器硬體之間的介面。設備驅動程序為應用程序屏蔽了硬體的細節,這樣在應用程序看來,硬體設備只是一個設備文件,應用程序可以象操作普通文件一樣對硬體設備進行操作。設備驅動程序是內核的一部分,它完成以下的功能:x0dx0ax0dx0a1、對設備初始化和釋放;x0dx0ax0dx0a2、把數據從內核傳送到硬體和從硬體讀取數據;x0dx0ax0dx0a3、讀取應用程序傳送給設備文件的數據和回送應用程序請求的數據;x0dx0ax0dx0a4、檢測和處理設備出現的錯誤。x0dx0ax0dx0a在Linux操作系統下有三類主要的設備文件類型,一是字元設備,二是塊設備,三是網路設備。字元設備和塊設備的主要區別是:在對字元設備發出讀/寫請求時,實際的硬體I/O一般就緊接著發生了,塊設備則不然,它利用一塊系統內存作緩沖區,當用戶進程對設備請求能滿足用戶的要求,就返回請求的數據,如果不能,就調用請求函數來進行實際的I/O操作。塊設備是主要針對磁碟等慢速設備設計的,以免耗費過多的CPU時間來等待。x0dx0ax0dx0a已經提到,用戶進程是通過設備文件來與實際的硬體打交道。每個設備文件都都有其文件屬性(c/b),表示是字元設備還是塊設備?另外每個文件都有兩個設備號,第一個是主設備號,標識驅動程序,第二個是從設備號,標識使用同一個設備驅動程序的不同的硬體設備,比如有兩個軟盤,就可以用從設備號來區分他們。設備文件的的主設備號必須與設備驅動程序在登記時申請的主設備號一致,否則用戶進程將無法訪問到驅動程序。x0dx0ax0dx0a最後必須提到的是,在用戶進程調用驅動程序時,系統進入核心態,這時不再是搶先式調度。也就是說,系統必須在你的驅動程序的子函數返回後才能進行其他的工作。如果你的驅動程序陷入死循環,不幸的是你只有重新啟動機器了,然後就是漫長的fsck。x0dx0ax0dx0a二、實例剖析x0dx0ax0dx0a我們來寫一個最簡單的字元設備驅動程序。雖然它什麼也不做,但是通過它可以了解Linux的設備驅動程序的工作原理。把下面的C代碼輸入機器,你就會獲得一個真正的設備驅動程序。x0dx0ax0dx0a由於用戶進程是通過設備文件同硬體打交道,對設備文件的操作方式不外乎就是一些系統調用,如 open,read,write,close?, 注意,不是fopen, fread,但是如何把系統調用和驅動程序關聯起來呢?這需要了解一個非常關鍵的數據結構:x0dx0ax0dx0aSTruct file_operatiONs {x0dx0ax0dx0aint (*seek) (struct inode * ,struct file *, off_t ,int);x0dx0ax0dx0aint (*read) (struct inode * ,struct file *, char ,int);x0dx0ax0dx0aint (*write) (struct inode * ,struct file *, off_t ,int);x0dx0ax0dx0aint (*readdir) (struct inode * ,struct file *, struct dirent * ,int);x0dx0ax0dx0aint (*select) (struct inode * ,struct file *, int ,select_table *);x0dx0ax0dx0aint (*ioctl) (struct inode * ,struct file *, unsined int ,unsigned long);x0dx0ax0dx0aint (*mmap) (struct inode * ,struct file *, struct vm_area_struct *);x0dx0ax0dx0aint (*open) (struct inode * ,struct file *);x0dx0ax0dx0aint (*release) (struct inode * ,struct file *);x0dx0ax0dx0aint (*fsync) (struct inode * ,struct file *);x0dx0ax0dx0aint (*fasync) (struct inode * ,struct file *,int);x0dx0ax0dx0aint (*check_media_change) (struct inode * ,struct file *);x0dx0ax0dx0aint (*revalidate) (dev_t dev);x0dx0ax0dx0a}x0dx0ax0dx0a這個結構的每一個成員的名字都對應著一個系統調用。用戶進程利用系統調用在對設備文件進行諸如read/write操作時,系統調用通過設備文件的主設備號找到相應的設備驅動程序,然後讀取這個數據結構相應的函數指針,接著把控制權交給該函數。這是linux的設備驅動程序工作的基本原理。既然是這樣,則編寫設備驅動程序的主要工作就是編寫子函數,並填充file_operations的各個域。x0dx0ax0dx0a下面就開始寫子程序。x0dx0ax0dx0a#include 基本的類型定義x0dx0ax0dx0a#include 文件系統使用相關的頭文件x0dx0ax0dx0a#include x0dx0ax0dx0a#include x0dx0ax0dx0a#include x0dx0ax0dx0aunsigned int test_major = 0;x0dx0ax0dx0astatic int read_test(struct inode *inode,struct file *file,char *buf,int count)x0dx0ax0dx0a{x0dx0ax0dx0aint left; 用戶空間和內核空間x0dx0ax0dx0aif (verify_area(VERIFY_WRITE,buf,count) == -EFAULT )x0dx0ax0dx0areturn -EFAULT;x0dx0ax0dx0afor(left = count ; left > 0 ; left--)x0dx0ax0dx0a{x0dx0ax0dx0a__put_user(1,buf,1);x0dx0ax0dx0abuf++;x0dx0ax0dx0a}x0dx0ax0dx0areturn count;x0dx0ax0dx0a}x0dx0ax0dx0a這個函數是為read調用准備的。當調用read時,read_test()被調用,它把用戶的緩沖區全部寫1。buf 是read調用的一個參數。它是用戶進程空間的一個地址。但是在read_test被調用時,系統進入核心態。所以不能使用buf這個地址,必須用__put_user(),這是kernel提供的一個函數,用於向用戶傳送數據。另外還有很多類似功能的函數。請參考,在向用戶空間拷貝數據之前,必須驗證buf是否可用。這就用到函數verify_area。為了驗證BUF是否可以用。x0dx0ax0dx0astatic int write_test(struct inode *inode,struct file *file,const char *buf,int count)x0dx0ax0dx0a{x0dx0ax0dx0areturn count;x0dx0ax0dx0a}x0dx0ax0dx0astatic int open_test(struct inode *inode,struct file *file )x0dx0ax0dx0a{x0dx0ax0dx0aMOD_INC_USE_COUNT; 模塊計數加以,表示當前內核有個設備載入內核當中去x0dx0ax0dx0areturn 0;x0dx0ax0dx0a}x0dx0ax0dx0astatic void release_test(struct inode *inode,struct file *file )x0dx0ax0dx0a{x0dx0ax0dx0aMOD_DEC_USE_COUNT;x0dx0ax0dx0a}x0dx0ax0dx0a這幾個函數都是空操作。實際調用發生時什麼也不做,他們僅僅為下面的結構提供函數指針。x0dx0ax0dx0astruct file_operations test_fops = {?x0dx0ax0dx0aread_test,x0dx0ax0dx0awrite_test,x0dx0ax0dx0aopen_test,x0dx0ax0dx0arelease_test,x0dx0ax0dx0a};x0dx0ax0dx0a設備驅動程序的主體可以說是寫好了。現在要把驅動程序嵌入內核。驅動程序可以按照兩種方式編譯。一種是編譯進kernel,另一種是編譯成模塊(moles),如果編譯進內核的話,會增加內核的大小,還要改動內核的源文件,而且不能動態的卸載,不利於調試,所以推薦使用模塊方式。x0dx0ax0dx0aint init_mole(void)x0dx0ax0dx0a{x0dx0ax0dx0aint result;x0dx0ax0dx0aresult = register_chrdev(0, "test", &test_fops); 對設備操作的整個介面x0dx0ax0dx0aif (result < 0) {x0dx0ax0dx0aprintk(KERN_INFO "test: can't get major number\n");x0dx0ax0dx0areturn result;x0dx0ax0dx0a}x0dx0ax0dx0aif (test_major == 0) test_major = result; /* dynamic */x0dx0ax0dx0areturn 0;x0dx0ax0dx0a}x0dx0ax0dx0a在用insmod命令將編譯好的模塊調入內存時,init_mole 函數被調用。在這里,init_mole只做了一件事,就是向系統的字元設備表登記了一個字元設備。register_chrdev需要三個參數,參數一是希望獲得的設備號,如果是零的話,系統將選擇一個沒有被佔用的設備號返回。參數二是設備文件名,參數三用來登記驅動程序實際執行操作的函數的指針。x0dx0ax0dx0a如果登記成功,返回設備的主設備號,不成功,返回一個負值。x0dx0ax0dx0avoid cleanup_mole(void)x0dx0ax0dx0a{x0dx0ax0dx0aunregister_chrdev(test_major,"test");x0dx0ax0dx0a}x0dx0ax0dx0a在用rmmod卸載模塊時,cleanup_mole函數被調用,它釋放字元設備test在系統字元設備表中佔有的表項。x0dx0ax0dx0a一個極其簡單的字元設備可以說寫好了,文件名就叫test.c吧。x0dx0ax0dx0a下面編譯 :x0dx0ax0dx0a$ gcc -O2 -DMODULE -D__KERNEL__ -c test.c _c表示輸出制定名,自動生成.o文件x0dx0ax0dx0a得到文件test.o就是一個設備驅動程序。x0dx0ax0dx0a如果設備驅動程序有多個文件,把每個文件按上面的命令行編譯,然後x0dx0ax0dx0ald ?-r ?file1.o ?file2.o ?-o ?molename。x0dx0ax0dx0a驅動程序已經編譯好了,現在把它安裝到系統中去。x0dx0ax0dx0a$ insmod ?_f ?test.ox0dx0ax0dx0a如果安裝成功,在/proc/devices文件中就可以看到設備test,並可以看到它的主設備號。要卸載的話,運行 :x0dx0ax0dx0a$ rmmod testx0dx0ax0dx0a下一步要創建設備文件。x0dx0ax0dx0amknod /dev/test c major minorx0dx0ax0dx0ac 是指字元設備,major是主設備號,就是在/proc/devices里看到的。x0dx0ax0dx0a用shell命令x0dx0ax0dx0a$ cat /proc/devicesx0dx0ax0dx0a就可以獲得主設備號,可以把上面的命令行加入你的shell script中去。x0dx0ax0dx0aminor是從設備號,設置成0就可以了。x0dx0ax0dx0a我們現在可以通過設備文件來訪問我們的驅動程序。寫一個小小的測試程序。x0dx0ax0dx0a#include x0dx0ax0dx0a#include x0dx0ax0dx0a#include x0dx0ax0dx0a#include x0dx0ax0dx0amain()x0dx0ax0dx0a{x0dx0ax0dx0aint testdev;x0dx0ax0dx0aint i;x0dx0ax0dx0achar buf[10];x0dx0ax0dx0atestdev = open("/dev/test",O_RDWR);x0dx0ax0dx0aif ( testdev == -1 )x0dx0ax0dx0a{x0dx0ax0dx0aprintf("Cann't open file \n");x0dx0ax0dx0aexit(0);x0dx0ax0dx0a}x0dx0ax0dx0aread(testdev,buf,10);x0dx0ax0dx0afor (i = 0; i < 10;i++)x0dx0ax0dx0aprintf("%d\n",buf[i]);x0dx0ax0dx0aclose(testdev);x0dx0ax0dx0a}x0dx0ax0dx0a編譯運行,看看是不是列印出全1 x0dx0ax0dx0a以上只是一個簡單的演示。真正實用的驅動程序要復雜的多,要處理如中斷,DMA,I/O port等問題。這些才是真正的難點。上述給出了一個簡單的字元設備驅動編寫的框架和原理,更為復雜的編寫需要去認真研究LINUX內核的運行機制和具體的設備運行的機制等等。希望大家好好掌握LINUX設備驅動程序編寫的方法。

⑻ 如何編譯一個linux下的驅動模塊

首先,我們要了解一下模塊是如何別被構造的。模塊的構造過程與用戶空間
的應用程序的構造過程有顯著不同;內核是一個大的、獨立的程序
,
對於它的各
個部分如何組合在一起有詳細的明確的要求。
Linux2.6
內核的構造過程也與以
前版本的內核構造過程不同;
新的構造系統用起來更加簡單,
並且可產生更加正
確的結果
,
但是它看起來和先前的方法有很大不同。內核的構造系統非常復雜
,
我們所看到的只是它的一小部分。
如果讀者想了解更深入的細節,
則應閱讀在內
核源碼中的
Document/kbuild
目錄下的文件。

在構造內核模塊之前,
有一些先決條件首先應該得到滿足。
首先,
讀者要保證你
有適合於你的內核版本的編譯器、模塊工具
,
以及其他必要工具。在內核文檔目
錄下的文件
Documentation/Changes
里列出了需要的工具版本;
在開始構造內
核前,
讀者有必要查看該文件,
並確保已安裝了正確的工具。
如果用錯誤的工具
版本來構造一個內核
(
及其模塊
)
,可能導致許多奇怪的問題。另外也要注意
,
使
用太新版本的編譯器偶爾可能也會導致問題。

一旦做好了上面的准備工作之後
,
其實給自己的模塊創建一個
makefile
則非常
簡單。實際上
,
對於本章前面展示的
" hello world"
例子
,
下面一行就夠了
:
obj-m := hello.o
如果讀者熟悉
make

但是對
Linux2.6
內核構造系統不熟悉的話
,
可能奇怪這個
makefile
如何工作。畢竟上面的這一行不是一個傳統的
makefile
的樣子。問
題的答案當然是內核構造系統處理了餘下的工作。上面的賦值語句
(
它利用了由
GNU make
提供的擴展語法
)
說明有一個模塊要從目標文件
hello.o
構造,而從
該目標文件構造的模塊名稱為
hello.ko.
如果我們想由兩個源文件
(
比如
file1.c

file2.c )
構造出一個名稱為
mole.ko
的模塊
,
則正確的
makefile
可如下編寫
:
obj-m := mole.o
mole-objs := file1.o file2.o
為了讓上面這種類型的
makefile
文件正常工作
,
必須在大的內核構造系統環境
中調用他們。假設讀者的內核源碼數位於
~/kernel-2.6
目錄
,
用來建立你的模
塊的
make
命令
(
在包含模塊源代碼和
makefile
的目錄下鍵入
)
應該是
:
make -C ~/kernel-2.6 M=`pwd` moles
這個命令首先是改變目錄到用
-C
選項指定的位置
(
即內核源代碼目錄
)
,其中保
存有內核的頂層
makefile
文件。這個
M=
選項使
makefile
在構造
moles

標前
,
返回到模塊源碼目錄。
然後,
moles
目標指向
obj-m
變數中設定的模塊,
在上面的例子里,我們將該變數設置成了
mole.o


上面這樣的
make
命令對於多個文件的編譯顯得不是很方便
,
於是內核開發者就
開發了一種
makefile
方式
,
這種方式使得內核樹之外的模塊構造變得更加容易。
代碼清單
1.4
展示了
makefile
的編寫方法:

代碼清單
1.4 makefile
ifeq ($(KERNELRELEASE),)

KERNELDIR ?= /source/linux-2.6.13
PWD := $(shell pwd)

moles:
$(MAKE) -C $(KERNELDIR) M=$(PWD) moles

moles_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) moles_install

clean:
rm -rf *.o *~ core .depend .*. *.ko *.mod.c .tmp_versions

.PHONY: moles moles_install clean

else
obj-m := hello.o
endif
我們再次看到了擴展的
GNU
make
語法在起作用。在一個典型的構造過程中,這

makefile
將被讀取兩次。當從命令行中調用這個
makefile ,
它注意到
KERNELRELEASE
變數尚未設置。我們可以注意到,已安裝的模塊目錄中存在一
個符號連接,
它指向內核的構造樹,
這樣這個
makefile
就可以定位內核的源代
碼目錄。如果讀者時間運行的內核並不是要構造的內核,則可以在命令行提供
KERNELDIR=
選項或者設置
KERNELDIR
環境變數
,
或者修改
makefile
中設置
KERNELDIR
的那一行。在找到內核源碼樹
,
這個
makefile
會調用
default:


,
這個目標使用先前描述過的方法第二次運行
make
命令
(
注意,在這個
makefile

make
命令被參數化成
$(MAKE))
,以便運行內核構造系統。在第二
次讀取
makefile
時,
它設置了
obj-m,
而內核的
makefile
負責真正構造模塊。

這種構造模塊的機制看起來很繁瑣,可是,一旦我們習慣了使用這種機制
,
則會
欣賞內核構造系統帶給我們的便利。需要注意的是
,
上面
makefile
並不完整,
一個真正的
makefile
應包含通常用來清除無用文件的目標
,
安裝模塊的目標等
等。一個完整的例子可以參考例子代碼目錄的
makefile

⑼ 軟體驅動硬體的原理是什麼

驅動程序即添加到操作系統中的一小塊代碼,其中包含有關硬體設備的信息。有了此信息,計算機就可以與設備進行通信。驅動程序是硬體廠商根據操作系統編寫的配置文件,可以說沒有驅動程序,計算機中的硬體就無法工作。操作系統不同,硬體的驅動程序也不同,各個硬體廠商為了保證硬體的兼容性及增強硬體的功能會不斷地升級驅動程序。如:Nvidia 顯卡晶元公司平均每個月會升級顯卡驅動程序2-3次。驅動程序是硬體的一部分,當你安裝新硬體時,驅動程序是一項不可或缺的重要元件。凡是安裝一個原本不屬於你電腦中的硬體設備時,系統就會要求你安裝驅動程序,將新的硬體與電腦系統連接起來。驅動程序扮演溝通的角色,把硬體的功能告訴電腦系統,並且也將系統的指令傳達給硬體,讓它開始工作。 x0dx0a當你在安裝新硬體時總會被要求放入「這種硬體的驅動程序」,很多人這時就開始頭痛。不是找不到驅動程序的碟片,就是找不到文件的位置,或是根本不知道什麼是驅動程序。比如安裝列印機這類的硬體外設,並不是把連接線接上就算完成,如果你這時候開始使用,系統會告訴你,找不到驅動程序。怎麼辦呢?參照說明書也未必就能順利安裝。其實在安裝方面還是有一定的慣例與通則可尋的,這些都可以幫你做到無障礙安裝。 x0dx0a在Windows系統中,需要安裝主板、光碟機、顯卡、音效卡等一套完整的驅動程序。如果你需要外接別的硬體設備,則還要安裝相應的驅動程序,如:外接游戲硬體要安裝手柄、方向盤、搖桿、跳舞毯等的驅動程序,外接列印機要安裝列印機驅動程序,上網或接入區域網要安裝網卡、Moden甚至ISDN、ADSL的驅動程序。說了這么多的驅動程序,你是否有一點頭痛了。下面就介紹Windows系統中各種的不同硬體設備的驅動程序,希望能讓你撥雲見日。 x0dx0a在Windows 9x下,驅動程序按照其提供的硬體支持可以分為:音效卡驅動程序、顯卡驅動程序、滑鼠驅動程序碰稿昌、主板驅動程序、網路設備驅動程序、列印機驅動程序、掃描儀驅動程序等等。為什麼沒有CPU、內存驅動程序呢?因為CPU和內存無需驅動程序便可使用,不僅如此,絕大多數鍵盤、滑鼠、硬碟、軟碟機、顯示器和主板上的標准設備都可以用Windows自帶的標准驅動程序來驅動,當然其它特定功能除外。如果敬碰你需要在Windows系統中的DOS模式下使用光碟機,那麼還需要在DOS模式下安裝光碟機驅動程序。多數顯卡、音效卡、網卡等內置擴展卡和列印機、掃描儀、外置Modem等外設都需要安裝與設備型號相符的驅動程序,否則無法發揮其部分或全部功能。驅動程序一般可通過三種途徑得到,一是購買的硬體附帶有驅動程序;二是Windows系統自帶有大量驅動程序;三是從Internet下載驅動程序。最後一種途徑往往能夠得到最新的驅動程序。 x0dx0a供Windows 9x使用的驅動程序包通常由一些.vxd(或.386)、.drv、.sys、.dll或.exe等文件組成,在安裝過程中,大部分文件都會被拷貝到「Windows\ System」目錄下。 x0dx0aWindows怎樣知道安裝的是什麼設備,以及要拷貝哪些文件呢?答案在於.inf文件。.inf是從Windows 95時代開始引入的一種描述設備安裝信息的文件,它用特定語法的文字來說明要安裝的設備類型、生產廠商、型號、要拷貝笑扒的文件、拷貝到的目標路徑,以及要添加到注冊表中的信息。通過讀取和解釋這些文字,Windows便知道應該如何安裝驅動程序。目前幾乎所有硬體廠商提供的用於Windows 9x下的驅動程序都帶有安裝信息文件。事實上,.inf文件不僅可用於安裝驅動程序,還能用來安裝與硬體並沒有什麼關系的軟體,例如Windows 98支持「Windows更新」功能,更新時下載的系統部件就是利用.inf文件來說明如何安裝該部件的。 x0dx0a在安裝驅動程序時,Windows一般要把.inf文件拷貝一份到「Win-dows\Inf」或「Windows\Inf\Other」目錄下,以備將來使用。Inf目錄下除了有.inf文件外,還有兩個特殊文件Drvdata.bin和Drvidx.bin,以及一些.pnf文件,它們都是Windows為了加快處理速度而自動生成的二進制文件。Drvdata.bin和Drvidx.bin記錄了.inf文件描述的所有硬體設備,也許朋友們會有印象:當我們在安裝某些設備時,經常會看到一個「創建驅動程序信息庫」的窗口,此時Windows便正在生成這兩個二進制文件。 x0dx0aWindows 9x專門提供有「添加新硬體向導」(以下簡稱硬體向導)來幫助使用者安裝硬體驅動程序,使用者的工作就是在必要時告訴硬體向導在哪兒可以找到與硬體型號相匹配的.inf文件,剩下的絕大部分安裝工作都將由硬體安裝向導自己完成。

⑽ 為什麼linux 需要把驅動編譯到內核

因為linux的驅動是內核的一部分,內核啟動時會檢測硬體需要按需載入相應的驅動,如果在編譯內核時沒有為你的選擇的硬體編譯相應的模塊,內核是無法載入相應的驅動的,這時候就需要你自己動手編譯驅動模塊了。

熱點內容
蔬菜解壓游戲大全 發布:2025-07-18 10:00:12 瀏覽:70
linuxand 發布:2025-07-18 09:48:27 瀏覽:725
為什麼安卓的app下載不了 發布:2025-07-18 09:47:45 瀏覽:178
如何用伺服器搭建網路 發布:2025-07-18 09:36:05 瀏覽:451
迷你世界電腦版怎麼改密碼 發布:2025-07-18 09:26:41 瀏覽:51
php創建目錄 發布:2025-07-18 09:26:17 瀏覽:659
為什麼手機游戲分ios和安卓端 發布:2025-07-18 09:22:17 瀏覽:140
android數據顯示 發布:2025-07-18 09:17:27 瀏覽:528
腳本精靈天天酷跑怎麼用 發布:2025-07-18 09:00:04 瀏覽:154
android技術面試 發布:2025-07-18 08:59:55 瀏覽:967