我們可以使用mkdir和rmdir來建立和移除目錄.
其文法如下:
#include <sys/stat.h>
int mkdir(const char *path, mode_t mode);
mkdir系統調用可以用來建立目錄,而這是與mkdir程式相等同的.mkdir以path為名字建立一個新的目錄.目錄的權限是由參數mode來指定的,而這也與open系統調用中的O_CREAT的選項是一樣的,而且這也是要受到umask的影響.
rmdir文法如下:
#include <unistd.h>
int rmdir(const char *path);
rmdir系統調用将會删除目錄,但是隻有這個目錄為空時才會操作成功.rmdir程式使用系統調用來完成他的工作.
chdir和getcwd
程式在目錄中浏覽的方式與使用者在檔案系統中浏覽的方式相類似。與我們在Shell中使用cd指令切換目錄相類似,一個程式也可以使用chdir系統調用。
int chdir(const char *path);
程式可以通過調用getcwd函數來确定目前的工作目錄。
char *getcwd(char *buf, size_t size);
getcwd函數将目前目錄的名字寫入指定的緩沖區buf中。如果目錄的名字超過了作為參數傳遞的緩沖區的尺寸size(ERANGE錯誤),則會傳回null。如果成功則會傳回buf。
浏覽目錄
Linux 系統上的一個通常問題就是目錄浏覽,也就是說,決定檔案所在的特定的目錄。在Shell程式中,這是很簡單的,隻需要Shell擴充通配符表達式即可。在 過去,不同的Unix變種允許程式員通路底層的檔案系統結構。我們仍然可以像打開普通檔案一樣打開一個目錄,然後直接讀取目錄實體,但是不同的檔案系統結 構以及實作使得這種方法不可移植。現在已經開發了一個标準的庫函數套件,使得目錄浏覽更為簡單。
目錄函數在頭檔案dirent.h中進行 了聲明。他們使用一個結構,DIR,作為目錄操作的基礎。指向這個結構的指針,稱之為一個目錄流(DIR *),其作用與通常檔案操作的檔案流(FILE *)相類似。在dirent結構中傳回的目錄實體本身也在dirent.h中進行聲明,因而我們絕不應直接修改DIR結構的域。
我們将會了解下面這些函數:
opendir,closedir
readdir
telldir
seekdir
opendir
opendir函數會打開一個目錄,并且建立一個目錄流。如果成功,他會傳回一個指向用來讀取目錄實體的DIR結構。
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
如果失敗,則會傳回一個null指針。注意,一個目錄流使用低層的檔案描述符來通路目錄本身,是以如果打開太多檔案時opendir會失敗。
readdir 函數會傳回一個指向目錄流dirp中下一個目錄實體詳細資訊的結構。成功調用readdir函數會傳回下一個目錄實體。如果發生錯誤,或是到達目錄結尾 處,readdir會傳回NULL。POSIX系統在傳回NULL時并不會改變errno的值,隻有當發生錯誤時才會改變errno的值。
struct dirent *readdir(DIR *dirp);
注意,如果同時有其他的程序正在建立或是删除目錄中的檔案,readdir浏覽并不會列出目錄中的所有檔案。
dirent結構包含目錄實體的詳細資訊,包括下面一些實體:
ino_t d_ino:檔案的i節點
char d_name[]:檔案名字
要确定目錄中一個檔案的更為詳細的資訊,我們需要調用stat函數,我們在前面已經讨論了這個函數。
telldir函數傳回一個目錄流中記錄目前位置的值。然後我們可以調用seekdir将目錄浏覽設定為目前位置。
long int telldir(DIR *dirp);
seekdir函數設定dirp指定的目錄流的目錄實體指針。用來設定位置的loc值應是由前面的telldir函數得到的。
void seekdir(DIR *dirp, long int loc);
closedir
closedir函數關閉一個目錄流,并且釋放與其相關的資源。如果成功則會傳回0,否則傳回一個錯誤。
int closedir(DIR *dirp);
在下一個程式printdir.c中,我們集中了各種檔案操作函數來建立一個簡單的目錄清單。目錄中的每一個檔案列在其所在行。每一個子目錄在其名字後跟一個短劃線,而其中的檔案空餘四個空格顯示。
程式可以切換進入子目錄,這樣他所查找的檔案就可以有可用的名字,也就是說,他們可以直接傳遞給opendir。這個程式在過深的嵌套目錄中會失敗,因為在打開的目錄流數量上存在限制。
當然,我們可以通過指令行參數來指定開始點,進而使得程式變得更為通用。我們可以檢視ls或是find的Linux源代碼來檢視更為通用的目錄方法。
一個目錄浏覽程式
1 我們相關的頭檔案,以及列印目前目錄的printdir函數開始。對于子目錄,可以使用depth參數來進行重用。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void printdir(char *dir, int depth)
{
DIR *dp;
struct dirent *entry;
struct stat statbuf;
if((dp = opendir(dir)) == NULL) {
fprintf(stderr,”cannot open directory: %s\n”, dir);
return;
}
chdir(dir);
while((entry = readdir(dp)) != NULL) {
lstat(entry->d_name,&statbuf);
if(S_ISDIR(statbuf.st_mode)) {
/* Found a directory, but ignore . and .. */
if(strcmp(“.”,entry->d_name) == 0 ||
strcmp(“..”,entry->d_name) == 0)
continue;
printf(“%*s%s/\n”,depth,””,entry->d_name);
/* Recurse at a new indent level */
printdir(entry->d_name,depth+4);
}
else printf(“%*s%s\n”,depth,””,entry->d_name);
chdir(“..”);
closedir(dp);
}
2 現在我們開始主函數
int main()
printf(“Directory scan of /home:\n”);
printdir(“/home”,0);
printf(“done.\n”);
exit(0);
程式的處理結果輸出如下所示:
$ printdir
Directory scan of /h
neil/
.Xdefaults
.Xmodmap
.Xresources
.bash_history
.bashrc
.kde/
share/
apps/
konqueror/
dirtree/
public_html.desktop
toolbar/
bookmarks.xml
konq_history
kdisplay/
color-schemes/
BLP3/
Gnu_Public_License
chapter04/
argopt.c
args.c
chapter03/
file.out
mmap.c
printdir
done.
工作原理
大 多數的動作位于printdir函數中,是以那将是我們檢視的地方。通過使用opendir來确定指定的目錄是否存在來進行一些初始的檢測, printdir在指定的目錄上調用chdir函數。當由readdir傳回實體不為空時,程式會進行檢測确定其是否為一個目錄。如果不是,則使用 depth縮進來列印檔案實體。
如果這個實體是一個目錄,我們就需要進行一些遞歸操作。在.與..實體(目前目錄與父目錄)被忽略以後, printdir函數會調用其自身,并且再次進行同樣的處理操作。那麼如何跳出這個循環呢?一旦while循環結束,chdir("..")函數調用将返 回目錄樹,進而以先的清單操作就會繼續。調用closedir(dp)可以確定打開目錄流的數目不會高于其所需要的數目。
為了簡略的介紹一下第四章将會讨論的Linux環境,我們來看一下使得程式更為通用的一個方法。程式有限制,是因為他指定為/home/neil目錄。通過下面的對main函數的更改,我們可以将其變為一個更為有用的目錄浏覽器:
int main(int argc, char* argv[])
char *topdir = “.”;
if (argc >= 2)
topdir=argv[1];
printf(“Directory scan of %s\n”,topdir);
printdir(topdir,0);
我們改變了三行代碼并且添加了五行,現在他就是一個帶有目錄名可選參數的通用程式了,其預設為目前目錄。我們可以用下面的指令來運作:
$ printdir2 /usr/local | more
輸出将會進行分頁,進而使用者可以在輸出之中進行分頁浏覽。這樣,使用者就會具有了一個友善小巧的通用目錄樹浏覽器。還可以有一些小的修改,我們可以添加空白使用資料,顯示的限制深度,等待。
錯誤
正 如我們所看到的,這一章所描述的許多系統函數與調用都會因為各種原因而失敗。當出現這種情況時,他們通過設定外部變量errno來訓示其失敗原因。許多不 同的庫使用這個變量作為報告問題的标準方式。但是在程式指出問題之後,程式必須立即測試errno變量的值,因為他會被下一個函數調用所覆寫,盡管這個函 數本身并沒有失敗。
錯誤值與其相關的意義列在頭檔案errno.h中。他們包括:
EPERM:操作不允許
ENOENT:沒有這個檔案或目錄
EINTR:系統調用中斷
EIO:I/O錯誤
EBUSY:裝置或資源忙
EEXIST:檔案存在
EINVAL:參數不可用
EMFILE:太多的打開檔案
ENODEV:沒有這個裝置
EISDIR:是一個目錄
ENOTDIR:不是一個目錄
當發生錯誤時,有一對十分有用的函數來報告錯誤:strerror與perror。
strerror
strerror函數将一個錯誤号映射為一個描述錯誤類型的字元串。這對于記錄錯誤條件十分有用。
其文法如下:
char *strerror(int errnum);
perror
perror函數也将所報告的errno映射為一個字元串,并且列印到标準錯誤流。他以一個指定的字元串s(如果不為空)開始,然後是一個冒号與空格。
void perror(const char *s);
例如:
perror(“program”);
會在标準錯誤輸出上得到下面的輸出:
program: Too many open files
/proc檔案系統
在這一章的前面,我們提到過Linux将大多數内容看作檔案,并且在檔案系統中存在硬體裝置檔案。/dev檔案被用來以一種使用低層系統調用的特殊方式來通路硬體。
控制硬體的軟體驅動通常可以以特定的方式進行配置,或者是可以報告資訊。例如,一個磁盤控制器可以配置使用特定的DMA模式。一個網卡可以報告其是否是高速,多路連接配接。
與裝置驅動進行通信的程式在過去較為常見。例如,hdparm可以配置一些磁盤參數,而ifconfig可以報告其網絡參數。在近些年,更為流行的方式是提供一個更為友善的方法來通路驅動資訊,而且事實上,在Linux核心的各種元素中已經包含了這些通信。
Linux提供了一個特殊的檔案系統,procfs,通常其以目錄/proc的形式出現。他包含許多特殊的檔案,可以允許高層通路驅動器與核心資訊。程式隻有要正确的通路權限就可以讀取或是寫入這些檔案來得到資訊或是設定參數。
/proc中出現的檔案會因系統的不同而不同,而更多的包含在Linux發行版本中作為更多驅動器與程式支援的procfs檔案系統。在這裡,我們将會看一些其中比較常見的檔案,并且簡要的讨論其用法。
在編寫這一章所使用的計算機上浏覽/proc目錄會得到下面的一些實體資訊:
1/ 1377/ 1771/ 951/ cpuinfo modules
10/ 1401/ 1777/ 961/ devices mounts@
1007/ 1414/ 1778/ 966/ dma mtrr
1023/ 1457/ 2/ 968/ driver/ net/
1053/ 1460/ 3/ 969/ execdomains partitions
1056/ 1463/ 385/ 970/ fb pci
1059/ 1465/ 388/ 971/ filesystems scsi/
1061/ 1476/ 4/ 974/ fs/ self@
1071/ 1477/ 424/ 975/ ide/ slabinfo
1077/ 1479/ 4775/ 976/ interrupts splash
1079/ 1480/ 4850/ 977/ iomem stat
1080/ 1482/ 496/ 978/ ioports swaps
1082/ 1484/ 5/ 979/ irq/ sys/
1086/ 1486/ 535/ 980/ isapnp sysvipc/
1090/ 1491/ 6/ 982/ kcore tty/
1093/ 1494/ 625/ 983/ kmsg uptime
1095/ 1495/ 7/ 999/ ksyms version
1096/ 1496/ 75/ apm loadavg video/
1100/ 1502/ 8/ asound/ locks
1101/ 1503/ 884/ buddyinfo lvm/
1104/ 1545/ 905/ bus/ mdstat
1118/ 1546/ 917/ cmdline meminfo
1119/ 1770/ 932/ config.gz misc
在許多情況下,這些檔案可以被讀取并給出狀态資訊。例如,/proc/cpuinfo會給出處理器的詳細資訊:
$ cat /proc/cpuinfo
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 6
model name : Celeron (Mendocino)
stepping : 0
cpu MHz : 451.028
cache size : 128 KB
fdiv_bug : no
hlt_bug : no
f00f_bug : no
coma_bug : no
fpu : yes
fpu_exception : yes
cpuid level : 2
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 sep mtrr pge mca cmov pat
pse36 mmx fxsr
bogomips : 897.84
相似的,/proc/meminfo與/proc/version會分别給出有關記憶體使用與核心版本的相關資訊:
$ cat /proc/meminfo
total: used: free: shared: buffers: cached:
Mem: 527392768 240873472 286519296 0 8331264 134004736
Swap: 139788288 0 139788288
MemTotal: 515032 kB
MemFree: 279804 kB
MemShared: 0 kB
Buffers: 8136 kB
Cached: 130864 kB
SwapCached: 0 kB
Active: 101208 kB
Inactive: 106056 kB
HighTotal: 0 kB
HighFree: 0 kB
LowTotal: 515032 kB
LowFree: 279804 kB
SwapTotal: 136512 kB
SwapFree: 136512 kB
BigFree: 0 kB
$ cat /proc/version
Linux version 2.4.19-4GB ([email protected]) (gcc version 3.2) #1 Wed Nov 27
00:56:40 UTC 2002
更多的特定核心函數的資訊可以在/proc的子目錄中查找到。例如,我們可以由/proc/net/sockstat得到網絡套接口的使用資料:
$ cat /proc/net/sockstat
sockets: used 246
TCP: inuse 20 orphan 0 tw 0 alloc 22 mem 11
UDP: inuse 3
RAW: inuse 0
FRAG: inuse 0 memory 0
/proc中的一些實體可以進行讀寫。例如,所有運作程式同時可以打開的檔案數目是一個Linux核心參數。目前的值可以由/proc/sys/fs/file-max來讀取:
$ cat /proc/sys/fs/file-max
52428
在這裡其值為52428。如果我們需要增加這上值,我們可以通過寫入同樣的檔案來做到。如果我們正運作一個特殊的程式套件,那麼也許我們就需要這樣做,例如使用許多表的資料庫系統,這需要同時打開許多檔案。
注意:寫入/proc檔案需要超級通路權限。當寫入/proc檔案時我們要小心,如果我們寫入了不正确的值就會引起嚴重的問題。
如果我們将系統檔案處理的限制增加到60000,我們可以簡單的向file-max寫入這個新的限制數目:
# echo 60000 >/proc/sys/fs/file-max
現在我們可以重新讀取這個檔案,我們就會看到新值:
60000
/proc下具有數字名字的子目錄用來提供通路關于運作程式的資訊。我們将會第11章了解到更多的關于程式如何執行為程序的相關資訊。
但是現在,我們隻需要注意第一個程序具有唯一的一個辨別符:在1到32000之間的一個數值。ps指令提供了目前運作程序的一個清單。例如,在編寫這一章時:
neil@beast:~/BLP3/chapter03> ps
PID TTY TIME CMD
1104 pts/1 00:00:00 bash
1503 pts/2 00:00:01 bash
1771 pts/4 00:00:00 bash
4991 pts/2 00:00:01 emacs
4994 pts/2 00:00:00 ps
neil@beast:~/BLP3/chapter03>
在這裡我們可以看到一些運作bash shell的終端會話,一個運作Emacs檔案編輯器的編輯會話。我們可以通過檢視/proc了解更多的關于Emacs會話的詳細資訊。
在這裡Emacs程序的辨別為4991,是以我們需要檢視/proc/4991來得到更為詳細的資訊:
$ ls -l /proc/4991
total 0
-r--r--r-- 1 neil users 0 2003-02-09 12:45 cmdline
lrwxrwxrwx 1 neil users 0 2003-02-09 12:45 cwd -> /home/neil/BLP3/chapter03
-r-------- 1 neil users 0 2003-02-09 12:45 environ
lrwxrwxrwx 1 neil users 0 2003-02-09 12:45 exe -> /usr/bin/emacs
dr-x------ 2 neil users 0 2003-02-09 12:45 fd
-rw------- 1 neil users 0 2003-02-09 12:45 mapped_base
-r--r--r-- 1 neil users 0 2003-02-09 12:45 maps
-rw------- 1 neil users 0 2003-02-09 12:45 mem
-r--r--r-- 1 neil users 0 2003-02-09 12:45 mounts
lrwxrwxrwx 1 neil users 0 2003-02-09 12:45 root -> /
-r--r--r-- 1 neil users 0 2003-02-09 12:45 stat
-r--r--r-- 1 neil users 0 2003-02-09 12:45 statm
-r--r--r-- 1 neil users 0 2003-02-09 12:45 status
在這裡,我們可以看到各種特殊檔案來告訴我們這個程序中所發生的事情。
我 們可以看到程式/usr/bin/emacs正在運作,而其目前工作目錄為/home/neil/BLP3/chpter03。也可以讀取這個目錄中的其 他檔案來檢視其啟動所需要的指令以及其所具有的shell環境。cmdline與environ檔案作為一系列的非終端字元串來提供這些資訊,是以我們查 看他們時需要小心。我們将會在第四章更為深入的讨論Linux環境。
$ od -c /proc/4991/cmdline
0000000 e m a c s \0 d r a f t 2 . t x t
0000020 \0
0000021
在這裡,我們可看到Emacs是由指令行emacs draft2.txt來啟動的。
fd 子目錄提供了關于程序打開的正使用的檔案描述符的資訊。這個資訊十分有用,可以用來确定一個程式一次打開了多少個檔案。每一個打開的檔案描述符有一個實 體;其名字與檔案描述号相比對。在我們這個例子中,我們可以看到Emacs打開描述符0,1,2,這正是我們所期望的。這是标準輸入,輸出,以及錯誤描述 符。盡管此時他正編輯一個檔案,他并沒有由這個程序保持打開,是以,并不會顯示在這裡。
$ ls /proc/4991/fd
0 1 2
進階主題:fcntl與mmap
在這裡,我們半會讨論一對我們也許會跳過的主題,因為他們很少被用到。正如我們所說的,我們将其放在這裡作為引用,因為他們可以為一些技巧問題提供一些簡單的解決辦法。
fcntl
fcntl系統調用提供了更多的方法來處理底層檔案描述符。
#include <fcntl.h>
int fcntl(int fildes, int cmd);
int fcntl(int fildes, int cmd, long arg);
我們可以使用fcntl系統調用在的打開的檔案描述符上執行各種操作,包括複制,擷取與設定檔案描述符标記,擷取與設定檔案狀态标記,以及管理檔案鎖。
各種操作是由fcntl.h中定義的指令參數cmd的不同值為選擇的。依據于所選擇的指令,系統調用需要第三個參數,arg:
fcntl(fildes,F_DUPFD,newfd):這個調用會傳回一個新的檔案描述符,這個檔案描述符的值等于或大于newfd的值。新的檔案描述符是描述符fildes的一個拷貝。依據打開的檔案數目以及newfd的值,這與dup(fildes)同樣高效。
fcntl(fildes,F_GETFD):這個調用會傳回在fcntl.h中定義的檔案描述符标記。這些包括FD_CLOEXEC,這會确定在成功的調用了系統調用的exec家族中的一個後是否關閉。
fcntl(fildes,F_SETFD,flags):這個調用用來調用檔案描述符标記,通常隻是FD_CLOEXEC。
fcntl (fildes,F_GETFL)與fcntl(fildes,F_SETFL,flags):這些函數分别用來擷取與設定檔案狀态标記與通路模式。我們 可以通過使用fcntl.h中定義的O_ACCMODE掩碼來得到通路模式。其他的标記包括傳遞給使用O_CREAT的open的第三個參數。注意,我們 不能設定所有的标記。通常而言,我們不能使用fcntl來設定檔案權限。
我們可以借助fcntl實作咨詢檔案鎖。檢視手冊頁第2節我們可以得到更多的資訊,或是第七章,我們将會那裡讨論檔案鎖。
mmap
Unix提供了一個非常有用的程式可以允許程式共享内容,而一個好消息就是這已經包含在2.0以及以後的Linux核心中。mmap函數可以設定一段兩個或是多個程式可以讀寫的記憶體。一個程式所優的更改可以為另一個程式看到。
我們可以使用同樣的程式來操作檔案。我們可以使得一個磁盤檔案的實體内容看起像是記憶體中的一個數組。如果檔案由C結構可以描述的記錄組成,我們可以使用結構數組通路來更新檔案。
這是通過具有特殊通路集合的虛拟記憶體段的使用來做到的。讀取與寫入段會引起作業系統讀取或是寫入磁盤檔案的相應部分。
mmap函數建立一個指針指向一個與檔案内容相關的記憶體區域,這個檔案内容可以由一個打開的檔案描述符來通路。
#include <sys/mman.h>
void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off);
我們可以通過傳遞off參數來修改由共享段通路的檔案資料的起始處。可以通路的資料量(例如,記憶體段的長度)是通過len參數來設定的。
我們可以使用addr參數來請求一個特定的記憶體位址。如果為空,結果指針是自動配置設定的。這是推薦的用法,否則很難移植;系統的變量位址空間是變化的。
prot參數用來設定記憶體段的通路權限。這是下面這些常量的按位或:
PROT_READ:段可讀
PROT_WRITE:段可寫
PROT_EXEC:段可執行
PROT_NONE:段不可通路
flags參數控制程式所做的到段的更改是如何反射的:
MAP_PRIVATE 段是私有的,改變是局部的
MAP_SHARED 段的改變反映到檔案
MAP_FIXED 段必須在指定的位址addr
msync函數使得記憶體段的部分或是全部改變寫回(或是讀取)所映射的檔案:
int msync(void *addr, size_t len, int flags);
要更新的段部分是由傳遞的起始位址,addr以及長度,len是來決定的。flags參數控制如何執行這些更新。
MS_ASYNC 執行異步寫
MS_SYNC 執行同步寫
MS_INVALIDATE 由檔案讀取資料
munmap函數釋放記憶體段。
int munmap(void *addr, size_t len);
下面的程式,mmap_eg.c,顯示了一個使用mmap與數組格式通路進行更新的結構檔案。2.0以前的Linux并不能完全支援mmap的使用。這個程式可以在Sun Solaris以及其他的系統正确工作。
1 我們由定義ERCORD結構與建立NRECOREDS版本開始,每一個記錄他們的數字。這些添加到檔案records.dat中。
typedef struct {
int integer;
char string[24];
} RECORD;
#define NRECORDS (100)
RECORD record, *mapped;
int i, f;
FILE *fp;
fp = fopen(“records.dat”,”w+”);
for(i=0; i<NRECORDS; i++) {
record.integer = i;
sprintf(record.string,”RECORD-%d”,i);
fwrite(&record,sizeof(record),1,fp);
fclose(fp);
2 我們現在改變記錄43到143的整數值,并且寫第43個記錄字元串處:
fp = fopen(“records.dat”,”r+”);
fseek(fp,43*sizeof(record),SEEK_SET);
fread(&record,sizeof(record),1,fp);
record.integer = 143;
sprintf(record.string,”RECORD-%d”,record.integer);
fwrite(&record,sizeof(record),1,fp);
fclose(fp);
3 為了将整數值改變為243(并且更新記錄字元串),我們将這些記錄映射到記憶體,并且通路第43個記錄,在這裡使用記憶體映射:
f = open(“records.dat”,O_RDWR);
mapped = (RECORD *)mmap(0, NRECORDS*sizeof(record),
PROT_READ|PROT_WRITE, MAP_SHARED, f, 0);
mapped[43].integer = 243;
sprintf(mapped[43].string,”RECORD-%d”,mapped[43].integer);
msync((void *)mapped, NRECORDS*sizeof(record), MS_ASYNC);
munmap((void *)mapped, NRECORDS*sizeof(record));
close(f);
exit(0);
在第13章,我們将會看到另一個共享記憶體程式:System V共享記憶體。
總結
在這一章,我們了解了Linux如何提供直接通路檔案與裝置。我們也了解了庫函數是如何建構在這些底層的函數上來為程式問題提供靈活的解決方案的。結果,我們可以隻用幾行代碼就可以編寫出一個相當強大的目錄浏覽例程。
我們現在已經了解了關于檔案與目錄操作的足夠知識,可以使用更為結構化的基于檔案的解決方案将我們在第2章結束時所編寫的CD程式轉換為C程式。然而,此時,我們并不能為程式添加新的功能,是以我們會在了解了如何處理螢幕與鍵盤之後再進行重寫,而這将是下兩章的内容。
本文轉自nxlhero 51CTO部落格,原文連結:http://blog.51cto.com/nxlhero/213811,如需轉載請自行聯系原作者