Apa yang sebenarnya dilakukan dengan membuka file?

266

Di semua bahasa pemrograman (setidaknya yang saya gunakan), Anda harus membuka file sebelum dapat membaca atau menulis.

Tapi apa sebenarnya operasi terbuka ini?

Halaman manual untuk fungsi-fungsi tipikal sebenarnya tidak memberi tahu Anda apa pun selain 'membuka file untuk membaca / menulis':

http://www.cplusplus.com/reference/cstdio/fopen/

https://docs.python.org/3/library/functions.html#open

Jelas, melalui penggunaan fungsi Anda dapat mengatakan itu melibatkan pembuatan beberapa jenis objek yang memfasilitasi mengakses file.

Cara lain untuk meletakkan ini adalah, jika saya mengimplementasikan suatu openfungsi, apa yang perlu dilakukan di Linux?

jramm
sumber
13
Mengedit pertanyaan ini untuk fokus pada Cdan Linux; karena apa yang dilakukan Linux dan Windows berbeda. Kalau tidak, itu agak terlalu luas. Juga, setiap bahasa tingkat yang lebih tinggi pada akhirnya akan memanggil baik C API untuk sistem atau kompilasi ke C untuk mengeksekusi, sehingga meninggalkan pada tingkat "C" meletakkannya di Least Common Denominator.
George Stocker
1
Belum lagi bahwa tidak semua bahasa pemrograman memiliki fasilitas ini, atau itu adalah fasilitas yang sangat tergantung pada lingkungan. Memang jarang hari ini, tentu saja, tetapi sampai hari ini penanganan file adalah bagian yang sepenuhnya opsional dari ANSI Forth, dan bahkan tidak ada dalam beberapa implementasi di masa lalu.

Jawaban:

184

Di hampir setiap bahasa tingkat tinggi, fungsi yang membuka file adalah pembungkus panggilan sistem kernel yang sesuai. Ini dapat melakukan hal-hal mewah lainnya juga, tetapi dalam sistem operasi kontemporer, membuka file harus selalu melalui kernel.

Inilah sebabnya argumen fopenfungsi perpustakaan, atau Python opensangat mirip dengan argumen open(2)panggilan sistem.

Selain membuka file, fungsi-fungsi ini biasanya mengatur buffer yang akibatnya akan digunakan dengan operasi baca / tulis. Tujuan dari buffer ini adalah untuk memastikan bahwa setiap kali Anda ingin membaca N byte, panggilan pustaka yang sesuai akan mengembalikan N byte, terlepas dari apakah panggilan ke panggilan sistem yang mendasarinya kurang.

Saya sebenarnya tidak tertarik untuk mengimplementasikan fungsi saya sendiri; hanya dalam memahami apa yang sedang terjadi ... 'di luar bahasa' jika Anda suka.

Dalam sistem operasi mirip Unix, panggilan yang berhasil untuk openmengembalikan "deskriptor file" yang hanya merupakan bilangan bulat dalam konteks proses pengguna. Deskriptor ini diteruskan ke panggilan apa pun yang berinteraksi dengan file yang dibuka, dan setelah memanggilnya close, deskriptor menjadi tidak valid.

Penting untuk dicatat bahwa panggilan untuk openbertindak seperti titik validasi di mana berbagai pemeriksaan dilakukan. Jika tidak semua kondisi terpenuhi, panggilan gagal dengan mengembalikan -1bukan deskriptor, dan jenis kesalahan ditunjukkan dalam errno. Pemeriksaan penting adalah:

  • Apakah file itu ada;
  • Apakah proses panggilan itu istimewa untuk membuka file ini dalam mode yang ditentukan. Ini ditentukan dengan mencocokkan izin file, ID pemilik dan ID grup dengan ID masing-masing dari proses panggilan.

Dalam konteks kernel, harus ada semacam pemetaan antara deskriptor file proses dan file yang dibuka secara fisik. Struktur data internal yang dipetakan ke deskriptor dapat berisi buffer lain yang berhubungan dengan perangkat berbasis blok, atau pointer internal yang menunjuk ke posisi baca / tulis saat ini.

Buyuciev Blagovest
sumber
2
Perlu dicatat bahwa di OS seperti Unix, deskriptor file struktur in-kernel dipetakan, disebut "deskripsi file terbuka". Jadi proses FD dipetakan ke kernel OFD. Ini penting untuk memahami dokumentasi. Misalnya, lihat man dup2dan periksa kehalusan antara deskriptor file terbuka (yaitu FD yang kebetulan terbuka) dan deskripsi file terbuka (OFD).
rodrigo
1
Ya, izin diperiksa pada waktu buka. Anda dapat pergi dan membaca sumber untuk implementasi kernel "terbuka": lxr.free-electrons.com/source/fs/open.c meskipun itu mendelegasikan sebagian besar pekerjaan ke driver sistem file tertentu.
pjc50
1
(pada sistem ext2 ini akan melibatkan membaca entri direktori untuk mengidentifikasi inode mana yang memiliki metadata, kemudian memuat inode itu ke dalam cache inode. Perhatikan bahwa mungkin ada sistem pseudofiles seperti "/ proc" dan "/ sys" yang dapat melakukan hal-hal yang sewenang-wenang ketika Anda membuka file)
pjc50
1
Perhatikan bahwa pemeriksaan pada file terbuka - bahwa file itu ada, bahwa Anda memiliki izin - dalam praktiknya, tidak memadai. File dapat hilang, atau izinnya dapat berubah, di bawah kaki Anda. Beberapa sistem file berupaya mencegah hal ini, tetapi selama OS Anda mendukung penyimpanan jaringan, mustahil untuk mencegahnya (OS dapat "panik" jika sistem file lokal berperilaku tidak wajar dan masuk akal: sistem yang melakukannya ketika jaringan berbagi tidak OS yang layak). Pemeriksaan tersebut juga dilakukan pada saat file terbuka, tetapi harus (secara efektif) dilakukan pada semua akses file lainnya juga.
Yakk - Adam Nevraumont
2
Tidak lupa evaluasi dan / atau penciptaan kunci. Ini dapat dibagikan, atau eksklusif dan dapat mempengaruhi seluruh file, atau hanya sebagian saja.
Thinkeye
83

Saya sarankan Anda melihat panduan ini melalui versi sederhana dari open()panggilan sistem . Ini menggunakan potongan kode berikut, yang mewakili apa yang terjadi di balik layar ketika Anda membuka file.

0  int sys_open(const char *filename, int flags, int mode) {
1      char *tmp = getname(filename);
2      int fd = get_unused_fd();
3      struct file *f = filp_open(tmp, flags, mode);
4      fd_install(fd, f);
5      putname(tmp);
6      return fd;
7  }

Secara singkat, inilah yang dilakukan oleh kode itu, baris demi baris:

  1. Alokasikan blok memori yang dikontrol kernel dan salin nama file ke dalamnya dari memori yang dikontrol pengguna.
  2. Pilih deskriptor file yang tidak digunakan, yang dapat Anda anggap sebagai indeks integer ke dalam daftar file yang dapat dibuka yang sedang dibuka. Setiap proses memiliki daftar sendiri, meskipun dikelola oleh kernel; kode Anda tidak dapat mengaksesnya secara langsung. Entri dalam daftar berisi informasi apa pun yang akan digunakan oleh sistem file yang mendasarinya untuk menarik byte dari disk, seperti nomor inode, izin proses, bendera terbuka, dan sebagainya.
  3. The filp_openfungsi memiliki implementasi

    struct file *filp_open(const char *filename, int flags, int mode) {
            struct nameidata nd;
            open_namei(filename, flags, mode, &nd);
            return dentry_open(nd.dentry, nd.mnt, flags);
    }
    

    yang melakukan dua hal:

    1. Gunakan filesystem untuk mencari inode (atau lebih umum, apa pun jenis pengenal internal yang menggunakan filesystem) yang sesuai dengan nama file atau jalur yang dilewati.
    2. Buat struct filedengan informasi penting tentang inode dan kembalikan. Struct ini menjadi entri dalam daftar file yang terbuka yang saya sebutkan sebelumnya.
  4. Simpan ("instal") struct yang dikembalikan ke daftar proses file yang terbuka.

  5. Bebaskan blok alokasi memori yang dikontrol kernel.
  6. Mengembalikan file descriptor, yang kemudian dapat diteruskan ke fungsi operasi file seperti read(), write(), dan close(). Masing-masing akan menyerahkan kontrol ke kernel, yang dapat menggunakan deskriptor file untuk mencari pointer file yang sesuai dalam daftar proses, dan menggunakan informasi dalam pointer file untuk benar-benar melakukan pembacaan, penulisan, atau penutupan.

Jika Anda merasa ambisius, Anda dapat membandingkan contoh yang disederhanakan ini dengan implementasi open()pemanggilan sistem di kernel Linux, sebuah fungsi yang disebut do_sys_open(). Anda seharusnya tidak kesulitan menemukan kesamaan.


Tentu saja, ini hanya "lapisan teratas" dari apa yang terjadi ketika Anda memanggil open()- atau lebih tepatnya, ini adalah kode kernel level tertinggi yang dipanggil dalam proses membuka file. Bahasa pemrograman tingkat tinggi mungkin menambahkan lapisan tambahan di atasnya. Ada banyak hal yang terjadi di level bawah. (Terima kasih kepada Ruslan dan pjc50 untuk penjelasannya.) Secara kasar, dari atas ke bawah:

  • open_namei()dan dentry_open()aktifkan kode sistem file, yang juga merupakan bagian dari kernel, untuk mengakses metadata dan konten untuk file dan direktori. The filesystem membaca byte mentah dari disk dan menafsirkan pola-pola byte sebagai pohon file dan direktori.
  • Filesystem menggunakan lapisan blok perangkat , lagi-lagi bagian dari kernel, untuk memperoleh byte mentah dari drive. (Fakta menyenangkan: Linux memungkinkan Anda mengakses data mentah dari lapisan perangkat blok menggunakan /dev/sdadan sejenisnya.)
  • Lapisan perangkat blok memanggil driver perangkat penyimpanan, yang juga merupakan kode kernel, untuk menerjemahkan dari instruksi tingkat menengah seperti "baca sektor X" ke instruksi input / output individual dalam kode mesin. Ada beberapa jenis driver perangkat penyimpanan, termasuk IDE , (S) ATA , SCSI , Firewire , dan sebagainya, sesuai dengan standar komunikasi berbeda yang dapat digunakan oleh drive. (Perhatikan bahwa penamaannya berantakan.)
  • Instruksi I / O menggunakan kemampuan built-in dari chip prosesor dan pengontrol motherboard untuk mengirim dan menerima sinyal listrik pada kabel yang menuju drive fisik. Ini adalah perangkat keras, bukan perangkat lunak.
  • Di ujung lain kabel, firmware disk (kode kontrol tertanam) menginterpretasikan sinyal listrik untuk memutar piring dan menggerakkan kepala (HDD), atau membaca sel ROM flash (SSD), atau apa pun yang diperlukan untuk mengakses data pada jenis perangkat penyimpanan.

Ini mungkin juga agak salah karena caching . :-P Serius, ada banyak detail yang saya tinggalkan - seseorang (bukan saya) bisa menulis banyak buku yang menggambarkan bagaimana seluruh proses ini bekerja. Tapi itu seharusnya memberi Anda ide.

David Z
sumber
67

Setiap sistem file atau sistem operasi yang ingin Anda bicarakan baik-baik saja oleh saya. Bagus!


Pada ZX Spectrum, menginisialisasi LOADperintah akan membuat sistem menjadi loop ketat, membaca garis Audio In.

Awal-data ditunjukkan oleh nada konstan, dan setelah itu rangkaian pulsa panjang / pendek mengikuti, di mana pulsa pendek untuk biner 0dan yang lebih panjang untuk biner 1( https://en.wikipedia.org/ wiki / ZX_Spectrum_software ). Loop beban ketat mengumpulkan bit sampai mengisi satu byte (8 bit), menyimpan ini ke dalam memori, meningkatkan penunjuk memori, kemudian loop kembali untuk memindai bit lebih banyak.

Biasanya, hal pertama yang dibaca loader adalah header format pendek, yang menunjukkan setidaknya jumlah byte yang diharapkan, dan mungkin informasi tambahan seperti nama file, jenis file, dan alamat pemuatan. Setelah membaca tajuk singkat ini, program dapat memutuskan apakah akan melanjutkan memuat sebagian besar data, atau keluar dari rutinitas memuat dan menampilkan pesan yang sesuai untuk pengguna.

Status End-of-file dapat dikenali dengan menerima byte sebanyak yang diharapkan (baik jumlah byte tetap, bawaan dalam perangkat lunak, atau nomor variabel seperti yang ditunjukkan dalam header). Kesalahan terjadi jika loop pemuatan tidak menerima pulsa dalam rentang frekuensi yang diharapkan untuk jangka waktu tertentu.


Sedikit latar belakang tentang jawaban ini

Prosedur ini menjelaskan memuat data dari kaset audio biasa - maka kebutuhan untuk memindai Audio Masuk (itu terhubung dengan plug standar ke perekam kaset). Sebuah LOADperintah secara teknis sama dengan openfile - tapi itu secara fisik terikat benar-benar memuat file. Ini karena tape recorder tidak dikontrol oleh komputer, dan Anda tidak dapat (berhasil) membuka file tetapi tidak memuatnya.

"Loop ketat" disebutkan karena (1) CPU, Z80-A (jika memori berfungsi), benar-benar lambat: 3,5 MHz, dan (2) Spectrum tidak memiliki jam internal! Itu berarti bahwa ia harus secara akurat menghitung T-state (waktu instruksi) untuk setiap. tunggal. petunjuk. di dalam lingkaran itu, hanya untuk mempertahankan waktu bip yang akurat.
Untungnya, kecepatan CPU yang rendah memiliki keuntungan berbeda sehingga Anda dapat menghitung jumlah siklus pada selembar kertas, dan dengan demikian waktu dunia nyata yang akan mereka ambil.

usr2564301
sumber
10
@ BillWoodger: baik ya. Tapi itu pertanyaan yang wajar (maksudku milikmu). Saya memilih untuk menutup sebagai "terlalu luas", dan jawaban saya dimaksudkan untuk menggambarkan seberapa luas sebenarnya pertanyaan itu.
usr2564301
8
Saya pikir Anda memberi jawaban terlalu banyak. ZX Spectrum memiliki perintah OPEN, dan itu sama sekali berbeda dari LOAD. Dan lebih sulit untuk dipahami.
rodrigo 3-15
3
Saya juga tidak setuju untuk menutup pertanyaan, tapi saya sangat suka jawaban Anda.
Enzo Ferber
23
Meskipun saya mengedit pertanyaan saya untuk membatasi ke OS linux / windows dalam upaya untuk tetap terbuka, jawaban ini sepenuhnya valid dan berguna. Seperti yang dinyatakan dalam pertanyaan saya, saya tidak ingin menerapkan sesuatu atau membuat orang lain melakukan pekerjaan saya, saya ingin belajar. Untuk mempelajari Anda harus mengajukan pertanyaan 'besar'. Jika kami terus-menerus menutup pertanyaan tentang SO karena 'terlalu luas', itu berisiko menjadi tempat untuk membuat orang menulis kode Anda untuk Anda tanpa memberikan penjelasan apa, di mana atau mengapa. Saya lebih suka menyimpannya sebagai tempat saya bisa belajar.
jramm
14
Jawaban ini tampaknya membuktikan bahwa penafsiran Anda tentang pertanyaan itu terlalu luas, dan bukannya pertanyaan itu sendiri terlalu luas.
jwg
17

Itu tergantung pada sistem operasi apa yang sebenarnya terjadi ketika Anda membuka file. Di bawah ini saya jelaskan apa yang terjadi di Linux karena memberi Anda gambaran tentang apa yang terjadi ketika Anda membuka file dan Anda dapat memeriksa kode sumber jika Anda tertarik untuk lebih detail. Saya tidak mencakup izin karena akan membuat jawaban ini terlalu lama.

Di Linux setiap file dikenali oleh struktur yang disebut inode. Setiap struktur memiliki nomor unik dan setiap file hanya mendapat satu nomor inode. Struktur ini menyimpan data meta untuk file, misalnya ukuran file, izin file, stempel waktu dan penunjuk ke blok disk, namun, bukan nama file itu sendiri. Setiap file (dan direktori) berisi entri nama file dan nomor inode untuk pencarian. Saat Anda membuka file, dengan asumsi Anda memiliki izin yang relevan, deskriptor file dibuat menggunakan nomor inode unik yang terkait dengan nama file. Karena banyak proses / aplikasi dapat menunjuk ke file yang sama, inode memiliki bidang tautan yang mempertahankan jumlah total tautan ke file tersebut. Jika suatu file ada dalam suatu direktori, jumlah tautannya adalah satu, jika memiliki tautan keras, jumlah tautannya akan menjadi dua dan jika suatu file dibuka oleh suatu proses, jumlah tautan akan bertambah 1.

Alex
sumber
6
Apa hubungannya ini dengan pertanyaan aktual?
Bill Woodger 3-15
1
Ini menjelaskan apa yang terjadi pada level rendah ketika Anda membuka file di Linux. Saya setuju pertanyaannya agak luas, jadi ini mungkin bukan jawaban yang dicari jramm.
Alex
1
Jadi sekali lagi, tidak memeriksa izin?
Bill Woodger 3-15
11

Pembukuan, kebanyakan. Ini termasuk berbagai pemeriksaan seperti "Apakah file itu ada?" dan "Apakah saya memiliki izin untuk membuka file ini untuk menulis?".

Tapi itu semua hal-hal kernel - kecuali jika Anda menerapkan OS mainan Anda sendiri, tidak ada banyak untuk menyelidiki (jika Anda bersenang-senang - itu adalah pengalaman belajar yang hebat). Tentu saja, Anda masih harus mempelajari semua kode kesalahan yang mungkin Anda dapat terima saat membuka file, sehingga Anda dapat menanganinya dengan benar - tetapi itu biasanya abstraksi kecil yang bagus.

Bagian terpenting pada level kode adalah memberi Anda pegangan untuk membuka file, yang Anda gunakan untuk semua operasi lain yang Anda lakukan dengan file. Tidak bisakah Anda menggunakan nama file alih-alih pegangan sewenang-wenang ini? Ya, tentu saja - tetapi menggunakan pegangan memberi Anda beberapa keuntungan:

  • Sistem dapat melacak semua file yang saat ini terbuka, dan mencegahnya agar tidak dihapus (misalnya).
  • OS modern dibangun di sekitar pegangan - ada banyak hal berguna yang dapat Anda lakukan dengan pegangan, dan semua jenis pegangan yang berbeda berperilaku hampir identik. Misalnya, ketika operasi I / O asinkron selesai pada pegangan file Windows, pegangan ditandai - ini memungkinkan Anda untuk memblokir pegangan sampai diberi sinyal, atau untuk menyelesaikan operasi sepenuhnya secara tidak sinkron. Menunggu pada pegangan file persis sama dengan menunggu pada pegangan benang (ditandai misalnya ketika utas berakhir), pegangan proses (sekali lagi, ditandai ketika proses berakhir), atau soket (ketika beberapa operasi asinkron selesai). Sama pentingnya, pegangan dimiliki oleh proses mereka masing-masing, jadi ketika suatu proses dihentikan secara tak terduga (atau aplikasi ditulis dengan buruk), OS tahu apa yang pegangannya dapat lepaskan.
  • Sebagian besar operasi bersifat posisional - Anda readdari posisi terakhir di file Anda. Dengan menggunakan pegangan untuk mengidentifikasi "pembukaan" file tertentu, Anda dapat memiliki beberapa pegangan bersamaan untuk file yang sama, masing-masing membaca dari tempat mereka sendiri. Di satu sisi, pegangan bertindak sebagai jendela yang dapat dipindahkan ke dalam file (dan cara untuk mengeluarkan permintaan I / O asinkron, yang sangat berguna).
  • Pegangannya jauh lebih kecil dari nama file. Pegangan biasanya ukuran pointer, biasanya 4 atau 8 byte. Di sisi lain, nama file dapat memiliki ratusan byte.
  • Pegangan memungkinkan OS untuk memindahkan file, meskipun aplikasi telah membukanya - pegangannya masih valid, dan masih menunjuk ke file yang sama, meskipun nama file telah berubah.

Ada juga beberapa trik lain yang dapat Anda lakukan (misalnya, berbagi pegangan antara proses untuk memiliki saluran komunikasi tanpa menggunakan file fisik; pada sistem unix, file juga digunakan untuk perangkat dan berbagai saluran virtual lainnya, jadi ini tidak sepenuhnya diperlukan ), tetapi mereka tidak benar-benar terikat pada openoperasi itu sendiri, jadi saya tidak akan menyelidiki itu.

Luaan
sumber
7

Pada intinya ketika membuka untuk membaca tidak ada hal-hal mewah yang sebenarnya perlu terjadi. Yang perlu dilakukan adalah memeriksa file yang ada dan aplikasi memiliki cukup hak istimewa untuk membacanya dan membuat pegangan di mana Anda dapat mengeluarkan perintah baca ke file.

Ada pada perintah-perintah itulah pembacaan aktual akan dikirim.

OS akan sering memulai membaca dengan memulai operasi membaca untuk mengisi buffer yang terkait dengan pegangan. Kemudian ketika Anda benar-benar membaca itu dapat mengembalikan isi buffer segera daripada harus menunggu di IO disk.

Untuk membuka file baru untuk menulis, OS perlu menambahkan entri di direktori untuk file baru (saat ini kosong). Dan lagi pegangan dibuat di mana Anda dapat mengeluarkan perintah tulis.

orang aneh
sumber
5

Pada dasarnya, panggilan untuk membuka perlu menemukan file, dan kemudian merekam apa pun yang diperlukan agar operasi I / O nanti dapat menemukannya lagi. Itu agak kabur, tetapi itu akan berlaku pada semua sistem operasi yang dapat saya pikirkan segera. Spesifikasinya bervariasi dari platform ke platform. Sudah banyak jawaban di sini yang berbicara tentang sistem operasi desktop modern. Saya telah melakukan sedikit pemrograman pada CP / M, jadi saya akan menawarkan pengetahuan saya tentang cara kerjanya pada CP / M (MS-DOS mungkin bekerja dengan cara yang sama, tetapi untuk alasan keamanan, biasanya tidak dilakukan seperti ini hari ini ).

Pada CP / M Anda memiliki sesuatu yang disebut FCB (seperti yang Anda sebutkan C, Anda bisa menyebutnya sebagai struct; ini benar-benar adalah area bersebelahan 35 byte dalam RAM yang berisi berbagai bidang). FCB memiliki bidang untuk menulis nama file dan integer (4-bit) yang mengidentifikasi drive disk. Kemudian, ketika Anda memanggil Open File kernel, Anda melewatkan sebuah pointer ke struct ini dengan menempatkannya di salah satu register CPU. Beberapa waktu kemudian, sistem operasi kembali dengan struct yang sedikit berubah. Apa pun I / O yang Anda lakukan untuk file ini, Anda meneruskan pointer ke struct ini ke panggilan sistem.

Apa yang dilakukan CP / M dengan FCB ini? Ini cadangan bidang tertentu untuk penggunaannya sendiri, dan menggunakannya untuk melacak file, jadi Anda sebaiknya tidak pernah menyentuhnya dari dalam program Anda. Operasi Open File mencari melalui tabel di awal disk untuk file dengan nama yang sama dengan apa yang ada di FCB (karakter wildcard '?' Cocok dengan karakter apa pun). Jika menemukan file, itu menyalin beberapa informasi ke FCB, termasuk lokasi fisik file pada disk, sehingga panggilan I / O berikutnya akhirnya memanggil BIOS yang dapat melewati lokasi ini ke driver disk. Pada level ini, spesifikasinya bervariasi.

OmarL
sumber
-7

Secara sederhana, ketika Anda membuka file, Anda sebenarnya meminta sistem operasi untuk memuat file yang diinginkan (menyalin isi file) dari penyimpanan sekunder untuk ram untuk diproses. Dan alasan di balik ini (Memuat file) adalah karena Anda tidak dapat memproses file langsung dari Hard-disk karena kecepatannya yang sangat lambat dibandingkan dengan Ram.

Perintah terbuka akan menghasilkan panggilan sistem yang pada gilirannya menyalin isi file dari penyimpanan sekunder (Hard-disk) ke penyimpanan Utama (Ram).

Dan kami 'Tutup' file karena konten yang dimodifikasi dari file harus tercermin ke file asli yang ada di hard-disk. :)

Semoga itu bisa membantu.


sumber