Java文件描述符

文件描述符

在Linux中,進(jìn)程是通過(guò)文件描述符(file descriptors,簡(jiǎn)稱(chēng)fd)而不是文件名來(lái)訪問(wèn)文件的熟尉,文件描述符實(shí)際上是一個(gè)整數(shù)弊仪。

內(nèi)核中熙卡,對(duì)應(yīng)于每個(gè)進(jìn)程都有一個(gè)文件描述符表,表示這個(gè)進(jìn)程打開(kāi)的所有文件励饵。文件描述符就是這個(gè)表的索引驳癌。

文件描述表中每一項(xiàng)都是一個(gè)指針,指向一個(gè)用于描述打開(kāi)的文件的數(shù)據(jù)塊———file對(duì)象役听,file對(duì)象中描述了文件的打開(kāi)模式颓鲜,讀寫(xiě)位置等重要信息表窘。

image.png

當(dāng)進(jìn)程打開(kāi)一個(gè)文件時(shí),內(nèi)核就會(huì)創(chuàng)建一個(gè)新的file對(duì)象甜滨。因此乐严,我們?cè)谶M(jìn)程中使用多線(xiàn)程打開(kāi)同一個(gè)文件,每個(gè)線(xiàn)程會(huì)有各自的文件描述符衣摩,每個(gè)線(xiàn)程也會(huì)有保存自己的讀取位置麦备,互不影響。

需要注意的是昭娩,file對(duì)象不是專(zhuān)屬于某個(gè)進(jìn)程的凛篙,不同進(jìn)程的文件描述符表中的指針可以指向相同的file對(duì)象,從而共享這個(gè)打開(kāi)的文件栏渺。比如呛梆,如果在調(diào)用fork之前父進(jìn)程已經(jīng)打開(kāi)文件,則fork后子進(jìn)程有一個(gè)父進(jìn)程描述符表的副本磕诊。父子進(jìn)程共享相同的打開(kāi)文件集合填物,因此共享相同的文件位置。

file對(duì)象有引用計(jì)數(shù)霎终,記錄了引用這個(gè)對(duì)象的文件描述符個(gè)數(shù)滞磺,只有當(dāng)引用計(jì)數(shù)為0時(shí),內(nèi)核才銷(xiāo)毀file對(duì)象莱褒,因此某個(gè)進(jìn)程關(guān)閉文件击困,不影響與之共享同一個(gè)file對(duì)象的進(jìn)程。

每個(gè)file結(jié)構(gòu)體都指向一個(gè)file_operations結(jié)構(gòu)體广凸,這個(gè)結(jié)構(gòu)體的成員都是函數(shù)指針阅茶,指向?qū)崿F(xiàn)各種文件操作的內(nèi)核函數(shù)。比如在用戶(hù)程序中read一個(gè)文件描述符谅海,read通過(guò)系統(tǒng)調(diào)用進(jìn)入內(nèi)核脸哀,然后找到這個(gè)文件描述符所指向的file結(jié)構(gòu)體,找到file結(jié)構(gòu)體所指向的file_operations結(jié)構(gòu)體扭吁,調(diào)用它的read成員所指向的內(nèi)核函數(shù)以完成用戶(hù)請(qǐng)求撞蜂。在用戶(hù)程序中調(diào)用lseek、read侥袜、write蝌诡、ioctl、open等函數(shù)系馆,最終都由內(nèi)核調(diào)用file_operations的各成員所指向的內(nèi)核函數(shù)完成用戶(hù)請(qǐng)求送漠。file_operations結(jié)構(gòu)體中的release成員用于完成用戶(hù)程序的close請(qǐng)求,之所以叫release而不叫close是因?yàn)樗灰欢ㄕ娴年P(guān)閉文件由蘑,而是減少引用計(jì)數(shù)闽寡,只有引用計(jì)數(shù)減到0才關(guān)閉文件。

file對(duì)象中包含一個(gè)指針尼酿,指向dentry對(duì)象爷狈。“dentry”是directory entry(目錄項(xiàng))的縮寫(xiě)裳擎,dentry對(duì)象代表一個(gè)獨(dú)立的文件路徑涎永,如果一個(gè)文件路徑被打開(kāi)多次,那么會(huì)建立多個(gè)file對(duì)象鹿响,但它們都指向同一個(gè)dentry對(duì)象羡微。為了減少讀盤(pán)次數(shù),內(nèi)核緩存了目錄的樹(shù)狀結(jié)構(gòu)惶我,稱(chēng)為dentry cache妈倔,其中每個(gè)節(jié)點(diǎn)是一個(gè)dentry結(jié)構(gòu)體。

每個(gè)dentry結(jié)構(gòu)體都有一個(gè)指針指向inode結(jié)構(gòu)體绸贡。inode結(jié)構(gòu)體保存著從磁盤(pán)inode讀上來(lái)的信息盯蝴。在上圖的例子中,有兩個(gè)dentry听怕,分別表示/home/akaedu/a和/home/akaedu/b捧挺,它們都指向同一個(gè)inode,說(shuō)明這兩個(gè)文件互為硬鏈接尿瞭。inode結(jié)構(gòu)體中保存著從磁盤(pán)分區(qū)的inode讀上來(lái)信息闽烙,例如所有者、文件大小声搁、文件類(lèi)型和權(quán)限位等鸣峭。

每個(gè)進(jìn)程剛剛啟動(dòng)的時(shí)候,文件描述符0是標(biāo)準(zhǔn)輸入酥艳,1是標(biāo)準(zhǔn)輸出摊溶,2是標(biāo)準(zhǔn)錯(cuò)誤。如果此時(shí)去打開(kāi)一個(gè)新的文件充石,它的文件描述符會(huì)是3莫换。

java中的FileDescriptor

在java中,有著與文件描述符對(duì)應(yīng)的一個(gè)類(lèi)對(duì)象:FileDescriptor骤铃。我們看一下FileDescriptor與Channel的關(guān)系:

FileInputStream.getChannel():

public FileChannel getChannel() {
        synchronized (this) {
            if (channel == null) {
                channel = FileChannelImpl.open(fd, path, true, false, this);

                /*
                 * Increment fd's use count. Invoking the channel's close()
                 * method will result in decrementing the use count set for
                 * the channel.
                 */
                fd.incrementAndGetUseCount();
            }
            return channel;
        }
}

其中的FileChannelImpl.open(fd, path, true, false, this)參數(shù)fd就是FileDescriptor實(shí)例拉岁。

看一下他是怎么產(chǎn)生的:

public FileInputStream(File file) throws FileNotFoundException {
        String name = (file != null ? file.getPath() : null);
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkRead(name);
        }
        if (name == null) {
            throw new NullPointerException();
        }
        if (file.isInvalid()) {
            throw new FileNotFoundException("Invalid file path");
        }
        fd = new FileDescriptor();
        fd.incrementAndGetUseCount();
        this.path = name;
        open(name);
}

static {
    initIDs();
}

注意到initIDs()這個(gè)靜態(tài)方法:

jfieldID fis_fd; /* id for jobject 'fd' in java.io.FileInputStream */

JNIEXPORT void JNICALL
Java_java_io_FileInputStream_initIDs(JNIEnv *env, jclass fdClass) {
    fis_fd = (*env)->GetFieldID(env, fdClass, "fd", "Ljava/io/FileDescriptor;");
}

FileInputStream類(lèi)加載階段,fis_fd就被初始化了惰爬,fid_fd相當(dāng)于是FileInputStream.fd字段的一個(gè)內(nèi)存偏移量喊暖,便于在必要時(shí)操作內(nèi)存給它賦值。

看一下FileDescriptor的實(shí)例化過(guò)程:

public /**/ FileDescriptor() {
        fd = -1;
        handle = -1;
        useCount = new AtomicInteger();
}

static {
    initIDs();
}

FileDescriptor也有一個(gè)initIDs撕瞧,他和FileInputStream.initIDs的方法類(lèi)似陵叽,把設(shè)置IO_fd_fdIDFileDescriptor.fd字段的內(nèi)存偏移量狞尔。

/* field id for jint 'fd' in java.io.FileDescriptor */
jfieldID IO_fd_fdID;
/**************************************************************
 * static methods to store field ID's in initializers
 */
JNIEXPORT void JNICALL
Java_java_io_FileDescriptor_initIDs(JNIEnv *env, jclass fdClass) {
    IO_fd_fdID = (*env)->GetFieldID(env, fdClass, "fd", "I");
}

接下來(lái)再看FileInputStream構(gòu)造函數(shù)中的open(name)方法,字面上看巩掺,這個(gè)方法打開(kāi)了一個(gè)文件偏序,他也是一個(gè)本地方法,open方法直接調(diào)用了fileOpen方法胖替,fileOpen方法如下:

void fileOpen(JNIEnv *env, jobject this, jstring path, jfieldID fid, int flags)
{
    WITH_PLATFORM_STRING(env, path, ps) {
        FD fd;
#if defined(__linux__) || defined(_ALLBSD_SOURCE)
        /* Remove trailing slashes, since the kernel won't */
        char *p = (char *)ps + strlen(ps) - 1;
        while ((p > ps) && (*p == '/'))
            *p-- = '\0';
#endif
        // 打開(kāi)一個(gè)文件并獲取到文件描述符
        fd = handleOpen(ps, flags, 0666);
        if (fd != -1) {
            SET_FD(this, fd, fid);
        } else {
            throwFileNotFoundException(env, path);
        }
    } END_PLATFORM_STRING(env, ps);
}

其中的handleOpen函數(shù)打開(kāi)了一個(gè)文件描述符研儒,相當(dāng)于和文件建立了聯(lián)系,并且將返回的文件描述符描述符賦值給了局部變量fd,然后調(diào)用了SET_FD宏:

#define SET_FD(this, fd, fid) \
    if ((*env)->GetObjectField(env, (this), (fid)) != NULL) \
        (*env)->SetIntField(env, (*env)->GetObjectField(env, (this), (fid)),IO_fd_fdID, (fd))

注意到IO_fd_fdID独令,他是FileDescriptor.fd字段的內(nèi)存偏移量端朵。這個(gè)方法相當(dāng)于設(shè)置FileDescriptor.fd的值等于文件描述符fd。

需要注意的是燃箭,F(xiàn)ileDescriptor有兩個(gè)字段:handle和fd冲呢,上面的代碼表示我們只設(shè)置了fd字段為文件描述符,沒(méi)有提到handle字段遍膜,這是因?yàn)椋?/p>

在 win32 的實(shí)現(xiàn)中將 創(chuàng)建好的 文件句柄 設(shè)置到 handle 字段碗硬,在 linux 版本中則使用的是 FileDescriptor 的 fd 字段。

由此瓢颅,可知 handle 和 fd 是共存的但并不同時(shí)在使用恩尾,在 win32 平臺(tái)上使用 handle 字段,在 linux 平臺(tái)上使用 fd 字段挽懦。

所以翰意,F(xiàn)ileInputStream打開(kāi)文件的過(guò)程總結(jié)如下:

  • 創(chuàng)建 FileDescriptor 對(duì)象

每一個(gè) FileInputStream 有一個(gè) FileDescriptor,代表這個(gè)流底層的文件的fd

  • 調(diào)用 native 方法 open, 打開(kāi)文件

  • 內(nèi)部調(diào)用 handleOpen 打開(kāi)文件信柿,返回文件描述符 fd

初始化 FileDescriptor 對(duì)象

  • 將 文件描述符 fd 設(shè)置到冀偶,F(xiàn)ileDescriptor 對(duì)象的 fd 中

再談java文件讀取

java-NIO-Buffer這篇文章中我們提到了FileInputStream.read方法,再來(lái)回顧一下:

JNIEXPORT jint JNICALL  
Java_java_io_FileInputStream_readBytes(JNIEnv *env, jobject this,  
        jbyteArray bytes, jint off, jint len) {//除了前兩個(gè)參數(shù)渔嚷,后三個(gè)就是readBytes方法傳遞進(jìn)來(lái)的进鸠,字節(jié)數(shù)組、起始位置形病、長(zhǎng)度三個(gè)參數(shù)  
return readBytes(env, this, bytes, off, len, fis_fd);  
}

jint
readBytes(JNIEnv *env, jobject this, jbyteArray bytes,
          jint off, jint len, jfieldID fid)
{
    jint nread;
    char stackBuf[BUF_SIZE];
    char *buf = NULL;
    FD fd;
 
    if (IS_NULL(bytes)) {
        JNU_ThrowNullPointerException(env, NULL);
        return -1;
    }
 
    if (outOfBounds(env, off, len, bytes)) {
        JNU_ThrowByName(env, "java/lang/IndexOutOfBoundsException", NULL);
        return -1;
    }
 
    if (len == 0) {
        return 0;
    } else if (len > BUF_SIZE) {
        buf = malloc(len);// buf的分配
        if (buf == NULL) {
            JNU_ThrowOutOfMemoryError(env, NULL);
            return 0;
        }
    } else {
        buf = stackBuf;
    }
 
    fd = GET_FD(this, fid);
    if (fd == -1) {
        JNU_ThrowIOException(env, "Stream Closed");
        nread = -1;
    } else {
        nread = IO_Read(fd, buf, len);// buf是使用malloc分配的直接緩沖區(qū)客年,也就是堆外內(nèi)存
        if (nread > 0) {
            (*env)->SetByteArrayRegion(env, bytes, off, nread, (jbyte *)buf);// 將直接緩沖區(qū)的內(nèi)容copy到bytes數(shù)組中
        } else if (nread == JVM_IO_ERR) {
            JNU_ThrowIOExceptionWithLastError(env, "Read error");
        } else if (nread == JVM_IO_INTR) {
            JNU_ThrowByName(env, "java/io/InterruptedIOException", NULL);
        } else { /* EOF */
            nread = -1;
        }
    }
 
    if (buf != stackBuf) {
        free(buf);
    }
    return nread;
}

上述代碼中的fis_fd是不是很眼熟?他就是FileInputStream.fd字段的內(nèi)存偏移量漠吻。注意到fd = GET_FD(this, fid);這個(gè)方法量瓜,獲取到其對(duì)應(yīng)的文件描述符,然后使用該文件描述符讀取文件內(nèi)容途乃,填充緩沖區(qū)绍傲。由此可見(jiàn),java底層讀取文件都是通過(guò)文件描述符來(lái)進(jìn)行的耍共。比如:

文章開(kāi)始提到每個(gè)進(jìn)程剛剛啟動(dòng)的時(shí)候烫饼,文件描述符0是標(biāo)準(zhǔn)輸入猎塞,1是標(biāo)準(zhǔn)輸出,2是標(biāo)準(zhǔn)錯(cuò)誤枫弟。如果此時(shí)去打開(kāi)一個(gè)新的文件邢享,它的文件描述符會(huì)是3鹏往,F(xiàn)ileDescriptor中的fd為0淡诗,1,2時(shí)也表示同樣的意義伊履。

FileOutputStream fileOutputStream = new FileOutputStream(FileDescriptor.out);
fileOutputStream.write('hello world');// 控制臺(tái)打印 hello world韩容,因?yàn)閒ileOutputStream使用了標(biāo)準(zhǔn)輸出的文件描述符

參考

linux 文件描述符表 打開(kāi)文件表 inode vnode

linux中文件描述符fd和文件指針flip的理解

JNI探秘--FileDescriptor、FileInputStream 解惑

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末唐瀑,一起剝皮案震驚了整個(gè)濱河市群凶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌哄辣,老刑警劉巖请梢,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異力穗,居然都是意外死亡毅弧,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)当窗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)够坐,“玉大人,你說(shuō)我怎么就攤上這事崖面≡” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵巫员,是天一觀的道長(zhǎng)月匣。 經(jīng)常有香客問(wèn)我,道長(zhǎng)篙挽,這世上最難降的妖魔是什么目养? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮财异,結(jié)果婚禮上倘零,老公的妹妹穿的比我還像新娘。我一直安慰自己戳寸,他們只是感情好呈驶,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著疫鹊,像睡著了一般袖瞻。 火紅的嫁衣襯著肌膚如雪司致。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,749評(píng)論 1 289
  • 那天聋迎,我揣著相機(jī)與錄音脂矫,去河邊找鬼。 笑死霉晕,一個(gè)胖子當(dāng)著我的面吹牛庭再,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播牺堰,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼拄轻,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了伟葫?” 一聲冷哼從身側(cè)響起恨搓,我...
    開(kāi)封第一講書(shū)人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎筏养,沒(méi)想到半個(gè)月后斧抱,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡渐溶,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年辉浦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掌猛。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡盏浙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出荔茬,到底是詐尸還是另有隱情废膘,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布慕蔚,位于F島的核電站丐黄,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏孔飒。R本人自食惡果不足惜灌闺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望坏瞄。 院中可真熱鬧桂对,春花似錦、人聲如沸鸠匀。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至宅此,卻和暖如春机错,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背父腕。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工弱匪, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人璧亮。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓萧诫,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親杜顺。 傳聞我的和親對(duì)象是個(gè)殘疾皇子财搁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法蘸炸,類(lèi)相關(guān)的語(yǔ)法躬络,內(nèi)部類(lèi)的語(yǔ)法,繼承相關(guān)的語(yǔ)法搭儒,異常的語(yǔ)法穷当,線(xiàn)程的語(yǔ)...
    子非魚(yú)_t_閱讀 31,597評(píng)論 18 399
  • 第一章 Nginx簡(jiǎn)介 Nginx是什么 沒(méi)有聽(tīng)過(guò)Nginx?那么一定聽(tīng)過(guò)它的“同行”Apache吧淹禾!Ngi...
    JokerW閱讀 32,646評(píng)論 24 1,002
  • 一.管道機(jī)制(pipe) 1.Linux的fork操作 在計(jì)算機(jī)領(lǐng)域中馁菜,尤其是Unix及類(lèi)Unix系統(tǒng)操作系統(tǒng)中,...
    Geeks_Liu閱讀 3,685評(píng)論 1 9
  • 1三個(gè)相關(guān)數(shù)據(jù)結(jié)構(gòu). 關(guān)于socket的創(chuàng)建铃岔,首先需要分析socket這個(gè)結(jié)構(gòu)體汪疮,這是整個(gè)的核心。 104 str...
    ice_camel閱讀 2,806評(píng)論 1 8
  • 虛擬文件系統(tǒng) 虛擬文件系統(tǒng)(VFS)作為內(nèi)核子系統(tǒng)毁习,為用戶(hù)空間程序提供了文件和文件系統(tǒng)相關(guān)的接口智嚷。系統(tǒng)中所有的文件...
    大雄good閱讀 866評(píng)論 0 4