Halo Dunia! Perendaman dalam di Terminal


Saya terinspirasi untuk menulis artikel ini oleh sebuah artikel tentang analisis Sishny printf . Namun, ada yang terlewatkan sesaat ke arah mana data berjalan setelah memasuki perangkat terminal. Pada artikel ini saya ingin memperbaiki cacat ini dan menganalisis jalur data di terminal. Kami juga akan mencari tahu bagaimana Terminal berbeda dari Shell, apa Pseudoterminal itu, bagaimana emulator terminal bekerja, dan banyak lagi.


Dasar-dasarnya


Pertama mari kita pahami apa itu Terminal, Shell, Console, bagaimana Terminal Emulator berbeda dari Terminal biasa, dan mengapa namanya demikian. Cukup banyak informasi yang telah ditulis tentang ini, jadi Anda tidak akan mendengar sesuatu yang baru di sini. Hampir semua informasi di sini diambil dari Internet, saya akan memberikan tautan di akhir artikel. Siapa yang sudah tahu apa arti semua hal ini, dapat melewati bagian ini dengan aman.




Terminal


Terminal adalah kombinasi tampilan dan keyboard, yaitu perangkat fisik. Sebelum terminal menjadi kombinasi khusus ini, mereka adalah jenis perangkat yang disebut teleprinter (teletype, teletypewriter atau TTY), yaitu kombinasi printer dan keyboard. Biasanya, beberapa terminal terhubung ke komputer yang sama. Dengan demikian, dimungkinkan untuk bekerja untuk beberapa pengguna di komputer yang sama, dan masing-masing memiliki sesi sendiri, terlepas dari yang lain. Terminal dinamai demikian karena terletak di ujung kabel terminal.


Ini adalah Teletype :


Teletype

Dan ini Terminal :


Terminal



Konsol


Konsol (konsol) - terminal yang terhubung langsung ke komputer. Faktanya adalah bahwa sebagian besar terminal terhubung secara implisit, tetapi setidaknya satu terminal terhubung langsung ke komputer. Konsol diizinkan untuk menggunakan lingkaran orang yang didefinisikan secara ketat, karena memungkinkan Anda untuk mengkonfigurasi komputer.




Shell


Jika dua sebelumnya adalah perangkat fisik, maka definisi ini merujuk secara eksklusif ke perangkat lunak.


Shell adalah penerjemah baris perintah. Tujuan utamanya adalah menjalankan program lain. Ada sejumlah besar Kerang berbeda. Yang paling umum adalah Bash (dari bahasa Inggris Bourne Again SHell, yang, seperti yang disarankan Wikipedia , adalah permainan kata-kata untuk Shell "Born again", yaitu Shell yang "dihidupkan kembali"). Contoh lain: Dash (Shell ringan, tersedia jika Anda menjalankan biner di / bin / sh), Zsh.




Tentu saja, baik terminal dan konsol tidak bisa tidak menemukan refleksi mereka di zaman modern. Oleh karena itu, lebih lanjut kami akan mempertimbangkan hal-hal seperti Terminal Emulator dan Virtual Console .


Terminal emulator


Terminal Emulator - emulator dari terminal lama yang bagus. Emulator terminal diperlukan untuk program yang tidak dapat berinteraksi langsung dengan Sistem X Window - Bash, Vim, dan lainnya.


Mari kita pertama-tama menetapkan tanggung jawab terminal:


  1. Transfer input pengguna ke komputer
  2. Pengiriman output komputer ke layar

Jadi Terminal Emulator kami melakukan hal yang persis sama: itu memberikan input pengguna ke program yang sedang berjalan, dan juga menampilkan output dari program pada layar. Bagaimanapun, artinya tetap - antara pengguna dan program yang sedang berjalan, ada beberapa jenis lapisan yang bertanggung jawab untuk input / output. Contoh Emulator Terminal: gnome-terminal, xterm, konsole.


Tolong jangan bingung Shell dan Terminal Emulator!
Terminal Emulator adalah aplikasi GUI, yaitu jendela di Sistem X Window. Shell adalah interpreter baris perintah, yaitu, hanya pelaksana perintah, tidak memiliki shell grafis. Berbicara dengan benar, Anda tidak meluncurkan Bash , Anda menjalankan Terminal Emulator, yang meluncurkan Bash di dalamnya . Terminal Emulator dan Bash benar-benar 2 program yang berbeda. Yang pertama bertanggung jawab untuk input / output, yang kedua - untuk memproses perintah.


Lebih lanjut dalam artikel ini, semua referensi ke terminal akan merujuk ke emulator terminal.




Konsol Virtual (Terminal Virtual)


Tekan Ctrl + Alt + FN, di mana N biasanya memiliki nilai dari 1 hingga 6. Apa yang baru saja Anda lihat disebut Konsol Virtual (konsol virtual) atau Terminal Virtual (terminal virtual). Ingat apa yang saya katakan sebelumnya tentang terminal? Banyak terminal terhubung ke satu komputer dan masing-masing terminal adalah sesi yang terpisah, terpisah dari yang lain. Konsol Virtual mengulangi gagasan ini: mungkin ada beberapa sesi independen di dalam komputer Anda (namun, sumber daya komputer masih jelas dibagikan).


Anda dapat memberi nama entitas ini Konsol Virtual dan Terminal Virtual, karena menurut definisi, konsol adalah terminal yang terhubung langsung ke komputer, tetapi semua terminal virtual, dalam arti tertentu, terhubung langsung ke komputer.




Perangkat TTY


Setiap terminal diberi perangkat TTY sendiri (perangkat terminal), yang menyediakan konsol. Meskipun Anda tidak mungkin menemukan teletype, tetapi pengurangan TTY tetap bertahan hingga hari ini.


Perangkat TTY terdiri dari dua komponen mendasar:


  1. Driver perangkat Dia bertanggung jawab untuk mengirimkan input keyboard ke program dan untuk menampilkan output program di layar.
  2. Disiplin Garis TTY (disiplin garis Rusia). Disiplin garis adalah antarmuka akses driver, yang, bagaimanapun, membawa banyak logika ke perangkat TTY. Kita dapat mengatakan bahwa disiplin garis proksi panggilan ke pengemudi. Apa bidang tanggung jawab dari komponen ini, kami akan mencari tahu selama artikel.

Bangun perangkat TTY:



Ada 3 jenis perangkat TTY:


  1. Perangkat konsol - menyediakan operasi Konsol Virtual. Input dan output perangkat ini sepenuhnya dikendalikan oleh kernel.
  2. Perangkat PTY (pseudo-terminal) - menyediakan operasi terminal di antarmuka jendela. Input dan output perangkat ini dikendalikan oleh emulator terminal yang beroperasi di ruang pengguna.
  3. Serial device - berkomunikasi langsung dengan perangkat keras. Biasanya tidak digunakan secara langsung, tetapi ada sebagai level terendah dalam organisasi arsitektur perangkat terminal.

Pada artikel ini, kita akan berbicara secara spesifik tentang tipe kedua perangkat TTY - pseudo-terminal.




Disiplin Garis TTY


Kami mulai memeriksa disiplin garis perangkat TTY.


Fitur penting pertama dari disiplin garis adalah bahwa ia bertanggung jawab untuk memproses I / O. Ini termasuk, misalnya, memproses karakter kontrol (lihat Karakter kontrol ) dan memformat output. Misalnya, Anda memasukkan teks apa pun, tetapi tiba-tiba Anda menyadari bahwa Anda keliru dalam menulis sesuatu dan ingin menghapusnya - di sinilah disiplin garis berperan.


Kami akan menganalisis secara terperinci apa yang sebenarnya terjadi ketika kami bekerja di Bash yang berjalan di terminal. Secara default, perangkat TTY beroperasi dalam mode kanonik dengan gema diaktifkan . Gema adalah tampilan karakter yang Anda masukkan di layar.


Ketika kita memasukkan, misalnya, karakter a , karakter ini dikirim ke perangkat TTY, tetapi dicegat oleh disiplin garis TTY perangkat. Dia membaca karakter ke buffer internal, melihat bahwa mode echo aktif, dan menampilkan karakter di layar. Pada saat ini, tidak ada yang masih tersedia untuk dibaca dalam program di mana perangkat terminal terpasang. Mari kita tekan backspace pada keyboard. Simbol ^? sekali lagi dicegat oleh disiplin garis, dan yang terakhir, menyadari bahwa pengguna ingin menghapus karakter yang dimasukkan terakhir, menghapus karakter ini dari buffer internal dan menghapus karakter ini juga dari layar. Sekarang, jika kita menekan Enter, Disiplin Garis TTY akhirnya mengirim ke buffer baca dari perangkat terminal segala sesuatu yang ditulis sebelumnya ke buffer internal disiplin, termasuk LF. Pada saat yang sama, karakter CR dan LF ditampilkan di layar untuk memindahkan kursor ke baris baru - ini adalah format dari output.


Ini adalah cara kerja mode kanonik - ini mentransfer semua karakter yang dimasukkan ke perangkat hanya setelah menekan Enter , memproses karakter kontrol dan memformat output.


Editing Baris TTY


Pengeditan Baris TTY adalah komponen yang bertanggung jawab untuk memproses input dalam disiplin jalur. Harus dikatakan bahwa Editing Lini adalah konsep umum dan berkaitan dengan pemrosesan input. Misalnya, Bash dan Vim memiliki Penyuntingan Baris sendiri.


Kita dapat mengontrol pengaturan disiplin garis perangkat TTY saat ini menggunakan program stty . Mari kita bereksperimen sedikit.


Open Bash atau Shell lain dan ketik:


 stty icanon -echo 

Sekarang coba ketikkan sesuatu dan Anda tidak akan melihat input Anda (jangan khawatir, Anda masih bisa meneruskan input ke program). Anda baru saja menonaktifkan gema - yaitu, tampilan karakter yang dimasukkan di layar. Sekarang masukkan:


 stty raw echo 

Coba ketikkan sesuatu. Anda lihat bagaimana kesimpulannya dipatahkan. Tetapi untuk efek yang lebih, mari kita masuk ke Dash - type /bin/sh . Sekarang coba masukkan karakter khusus ( Ctrl + karakter apa saja di keyboard) atau cukup tekan Enter . Anda bingung - apa saja karakter aneh ini di layar? Faktanya adalah bahwa, setelah memasuki Shell paling sederhana, selain Pengeditan Garis dari disiplin itu sendiri, kami juga menonaktifkan Pengeditan Garis Bash, dan sekarang kita dapat mengamati dengan kuat dan utama efek dari dimasukkannya mode baku disiplin garis. Mode ini tidak memproses input sama sekali dan tidak memformat output. Mengapa mode mentah diperlukan? Misalnya, untuk Vim : ia membuka ke seluruh jendela terminal dan memproses input itu sendiri, setidaknya sehingga simbol disiplin garis khusus tidak berpotongan dengan simbol khusus Vim itu sendiri.


Untuk lebih memahami, mari kita lihat mengkustomisasi karakter kontrol. Perintah stty <control-character> <string> akan membantu kita dengan ini.
Masukkan di Bash:


 stty erase 0 

Sekarang karakter kontrol erase akan ditetapkan ke karakter 0 . Tombol backspace biasanya penting ^? , tetapi sekarang karakter khusus ini akan dikirim ke buffer baca PTS secara harfiah - coba sendiri. Sekarang Anda dapat menghapus karakter menggunakan tombol 0 pada keyboard, karena Anda sendiri meminta disiplin garis tty untuk mengenali karakter yang dimasukkan sebagai karakter kontrol erase . Anda dapat mengembalikan pengaturan menggunakan perintah stty erase ^\? atau hanya menutup terminal, karena kami hanya memengaruhi perangkat tty saat ini.


Anda dapat menemukan informasi lebih lanjut dalam man stty .




Terminal Emulator dan Pseudoterminal


Setiap kali kita membuka terminal baru di Sistem X Window, Server Terminal GNOME memunculkan proses baru dan meluncurkan program default di dalamnya. Biasanya, ini semacam Shell (misalnya, Bash).


Komunikasi dengan program yang berjalan terjadi melalui apa yang disebut Pseudoterminal (pseudo-terminal, PTY). Pseudo-terminal itu sendiri ada di kernel, namun, ia menerima input dari ruang pengguna - dari emulator terminal.


Terminal pseudo terdiri dari dua perangkat TTY virtual berikut:
1) PTY master (PTM) - bagian terkemuka dari pseudo-terminal. Digunakan oleh Terminal Terminal GNOME untuk mentransfer input keyboard ke program yang berjalan di dalam terminal, serta untuk membaca output program dan menampilkan output. Server Terminal GNOME, pada gilirannya, berkomunikasi dengan Sistem X Window melalui protokol X.
2) PTY slave (PTS) - slave bagian dari pseudo-terminal. Digunakan oleh program yang berjalan di dalam terminal untuk membaca input keyboard dan menampilkan output di layar. Setidaknya, program itu sendiri berpikir begitu (saya akan menjelaskan apa artinya ini, sedikit lebih jauh).


Data apa pun yang direkam dalam perangkat PTS adalah input dari perangkat PTM, sehingga dapat dibaca pada perangkat PTM. Dan sebaliknya: setiap data yang direkam dalam perangkat PTM adalah input dari perangkat PTS. Ini adalah bagaimana Terminal Terminal GNOME dan program yang berjalan di dalam terminal berkomunikasi. Setiap perangkat PTM dikaitkan dengan perangkat PTS-nya sendiri.


Proses meluncurkan terminal baru terlihat seperti ini:
1) Server Terminal GNOME menciptakan perangkat master dan slave dengan memanggil fungsi open () pada perangkat khusus / dev / ptmx . Panggilan terbuka () mengembalikan deskriptor file dari perangkat PTM yang dibuat - master_fd .
2) Server Terminal GNOME menciptakan proses baru dengan memanggil fork() . Proses ini akan menjadi terminal baru.
3) Di terminal PTS, perangkat terbuka pada deskriptor file 0, 1, 2 (masing-masing stdin, stdout dan stderr). Sekarang, terminal I / O standar mengalir ke perangkat ini.
4) Program yang diinginkan diluncurkan di terminal dengan memanggil fungsi exec() . Beberapa Shell biasanya dimulai (misalnya, Bash). Setiap program yang kemudian diluncurkan dari Bash akan memiliki deskriptor file yang sama dengan Bash itu sendiri, yaitu aliran program akan diarahkan ke perangkat PTS.


Anda dapat melihat sendiri di mana arus keluaran terminal standar diarahkan menggunakan ls -la /proc/self/fd :


Perangkat PTS terletak di jalur / dev / pts / N , dan jalur ke perangkat PTM sama sekali tidak menarik bagi kami. Faktanya adalah bahwa Terminal Terminal GNOME sudah memiliki deskriptor file untuk perangkat PTM terbuka dan tidak memerlukan jalur untuk itu, namun, dalam proses anak, kita harus membuka perangkat PTS pada stream output standar dengan memanggil fungsi open() , yang memerlukan path file.


Ingat, saya mengatakan bahwa program yang menggunakan perangkat PTS hanya berpikir bahwa itu berkomunikasi langsung dengan terminal? Faktanya adalah bahwa PTS juga merupakan perangkat terminal ( perangkat TTY), tetapi perbedaan antara perangkat PTS dan perangkat TTY yang sebenarnya adalah bahwa perangkat PTS menerima input bukan dari keyboard, tetapi dari perangkat master, dan output tidak ke layar, tetapi ke perangkat master. Itulah sebabnya terminal semu diberi nama seperti ini - terminal semu hanya meniru (lagi ??) terminal. Perbedaan antara terminal emulator dan pseudo-terminal adalah bahwa terminal emulator hanyalah sebuah program grafis yang memungkinkan Anda untuk menjalankan terminal langsung di dalam antarmuka jendela, tetapi fitur ini diimplementasikan menggunakan pseudo-terminal.


Fakta bahwa perangkat PTS adalah perangkat TTY sangat penting. Inilah alasannya:


  1. Program tempat perangkat terminal terpasang memiliki semua kemampuan terminal konvensional. Misalnya: nonaktifkan gema, nonaktifkan / aktifkan tampilan kanonik.
  2. Program, mengetahui bahwa perangkat terminal terpasang padanya (dikatakan bahwa program memiliki terminal kontrol), dapat bekerja secara interaktif dan meminta input dari pengguna. Misalnya, minta nama pengguna dan kata sandi.
  3. Ada juga Disiplin Garis TTY, jadi kami memiliki kemampuan untuk memproses karakter kontrol sebelum mereka mencapai program, serta memformat output dari program.

Perangkat PTM juga merupakan perangkat TTY, tetapi ini tidak memainkan peran apa pun, karena tidak digunakan sebagai terminal kontrol. Selain itu, disiplin garis perangkat PTM diatur ke mode mentah, oleh karena itu, pemrosesan tidak dilakukan saat mentransfer data dari PTS ke perangkat PTM. Namun, panggilan untuk read() dan write() dari ruang pengguna masih terlebih dahulu dilayani oleh garis disiplin pada kedua perangkat. Momen ini akan memainkan peran yang bahkan lebih besar, seperti yang akan kita lihat nanti.


Proses komunikasi antara Terminal Terminal GNOME dan program yang berjalan di dalam terminal adalah sebagai berikut:



Penting untuk memeriksa secara lebih rinci peran yang dimainkan disiplin lini dalam berkomunikasi antara kedua bagian dari terminal semu. Di sini, disiplin garis bertanggung jawab untuk pemrosesan data yang lewat dari PTM ke perangkat PTS , serta untuk pengiriman data dari satu bagian terminal pseudo ke yang lain. Ketika kami berada di driver perangkat PTS, kami menggunakan disiplin garis perangkat PTM, dan sebaliknya.




Perangkat virtual


Anda mungkin berpikir bahwa Anda dapat membuka file di sepanjang path / dev / pts / N dan menulis atau membaca data darinya, seperti dari file teks biasa? Ya, semua perangkat pada sistem mirip Unix adalah file, berkat prinsip dasar Unix, yang menyatakan bahwa semuanya adalah file. Namun, tidak ada file perangkat khusus (Bahasa Inggris - file perangkat) adalah file teks. Perangkat tersebut disebut perangkat virtual - yaitu, mereka ada secara eksklusif di memori, bukan pada disk.


Jangan mencoba membuka file-file ini sebagai file teks biasa. Namun, Anda dapat menggunakan perangkat ini melalui operasi write() dan read() , yang panggilannya akan dilayani oleh driver perangkat. Mari kita coba melakukannya.


Buka dua terminal windows dan masukkan tty di setiap perintah. Perintah ini akan menunjukkan perangkat TTY mana yang melayani terminal yang sedang aktif. Sekarang masukkan echo "Hello, World!" > /dev/pts/N echo "Hello, World!" > /dev/pts/N di jendela terminal pertama, di mana N adalah indeks PTS dari perangkat jendela kedua, beralih ke jendela kedua dan Anda akan melihat input Anda dari jendela pertama. Sekarang Anda telah menulis data ke perangkat PTS dari jendela kedua seolah-olah itu dilakukan oleh program yang berjalan di terminal itu .





Perangkat terminal semu


Kami semakin dekat dengan bagian akhir artikel, tetapi sebelum itu kita melihat "di balik tudung" Linux - pertimbangkan perangkat terminal pseudo di tingkat kernel. Akan ada banyak kode, tetapi saya akan mencoba menjelaskan setiap blok kode yang diberikan sedetail mungkin, mengurangi detail yang tidak penting dan masuk berurutan.


Sebelum Anda mulai, kami memperkenalkan apa yang disebut "keranjang komponen". Saat kita bergerak di sepanjang inti, kita akan menambah lebih banyak komponen ke dalamnya dan menemukan koneksi di antara mereka. Saya harap ini membantu Anda lebih memahami perangkat pseudo-terminal. Mari kita mulai.


Ketika Linux dimulai, ia memuat driver perangkat yang diperlukan. Terminal semu kami juga memiliki driver seperti itu. Pendaftarannya dimulai dengan panggilan ke fungsi ini:


 static int __init pty_init(void) { legacy_pty_init(); unix98_pty_init(); // <- ,    return 0; } device_initcall(pty_init); // ,       

Untuk semua sistem modern, fungsi unix98_pty_init() akan dipanggil:


 static void __init unix98_pty_init(void) { ptm_driver = tty_alloc_driver(NR_UNIX98_PTY_MAX, TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_DEVPTS_MEM | TTY_DRIVER_DYNAMIC_ALLOC); if (IS_ERR(ptm_driver)) panic("Couldn't allocate Unix98 ptm driver"); pts_driver = tty_alloc_driver(NR_UNIX98_PTY_MAX, TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_DEVPTS_MEM | TTY_DRIVER_DYNAMIC_ALLOC); if (IS_ERR(pts_driver)) panic("Couldn't allocate Unix98 pts driver"); ptm_driver->driver_name = "pty_master"; ptm_driver->name = "ptm"; ptm_driver->major = UNIX98_PTY_MASTER_MAJOR; ptm_driver->minor_start = 0; ptm_driver->type = TTY_DRIVER_TYPE_PTY; ptm_driver->subtype = PTY_TYPE_MASTER; ptm_driver->init_termios = tty_std_termios; ptm_driver->init_termios.c_iflag = 0; ptm_driver->init_termios.c_oflag = 0; ptm_driver->init_termios.c_cflag = B38400 | CS8 | CREAD; ptm_driver->init_termios.c_lflag = 0; ptm_driver->init_termios.c_ispeed = 38400; ptm_driver->init_termios.c_ospeed = 38400; ptm_driver->other = pts_driver; tty_set_operations(ptm_driver, &ptm_unix98_ops); pts_driver->driver_name = "pty_slave"; pts_driver->name = "pts"; pts_driver->major = UNIX98_PTY_SLAVE_MAJOR; pts_driver->minor_start = 0; pts_driver->type = TTY_DRIVER_TYPE_PTY; pts_driver->subtype = PTY_TYPE_SLAVE; pts_driver->init_termios = tty_std_termios; pts_driver->init_termios.c_cflag = B38400 | CS8 | CREAD; pts_driver->init_termios.c_ispeed = 38400; pts_driver->init_termios.c_ospeed = 38400; pts_driver->other = ptm_driver; tty_set_operations(pts_driver, &pty_unix98_ops); if (tty_register_driver(ptm_driver)) panic("Couldn't register Unix98 ptm driver"); if (tty_register_driver(pts_driver)) panic("Couldn't register Unix98 pts driver"); /* Now create the /dev/ptmx special device */ tty_default_fops(&ptmx_fops); ptmx_fops.open = ptmx_open; cdev_init(&ptmx_cdev, &ptmx_fops); if (cdev_add(&ptmx_cdev, MKDEV(TTYAUX_MAJOR, 2), 1) || register_chrdev_region(MKDEV(TTYAUX_MAJOR, 2), 1, "/dev/ptmx") < 0) panic("Couldn't register /dev/ptmx driver"); device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 2), NULL, "ptmx"); 

Di sini kami tertarik pada 3 hal:


  1. Panggilan tty_set_operatons untuk driver master pty dan perangkat pty slave.
  2. Fungsi ptmx_open , yang bertanggung jawab untuk membuat kedua bagian pseudo-terminal ketika membuka perangkat khusus / dev / ptmx . Penting: / dev / ptmx bukan perangkat PTM, tetapi hanya sebuah antarmuka untuk membuat pseudo-terminal baru.
  3. Daftarkan driver perangkat PTM dan PTS.

Mari kita mulai:


1. operasi tty_set_


Fungsi tty_set_operations () hanya menyiapkan tabel fungsi untuk driver saat ini:


 void tty_set_operations(struct tty_driver *driver, const struct tty_operations *op) { driver->ops = op; }; 

Struktur tty_operations adalah tabel fungsi yang digunakan untuk mengakses fungsi driver TTY perangkat.


Saya akan memilih hal yang paling penting dalam struktur pty_unix98_ops dan ptm_unix98_ops , yang merupakan tabel fungsi untuk bagian-bagian terminal pseudo yang sesuai:


 static const struct tty_operations ptm_unix98_ops = { .install = pty_unix98_install, .remove = pty_unix98_remove, .open = pty_open, .close = pty_close, .write = pty_write, // ... }; static const struct tty_operations pty_unix98_ops = { .install = pty_unix98_install, .remove = pty_unix98_remove, .open = pty_open, .close = pty_close, .write = pty_write, // ... }; 

Di sini Anda dapat mengamati fungsi pty_write, pty_write sudah akrab dari artikel di Sishny printf - kami akan kembali lagi nanti.


Mari tambahkan struktur ini ke keranjang komponen kami:


Seperti yang Anda lihat, metode utama kedua driver sama sekali tidak berbeda. Omong-omong, perhatikan bahwa tidak ada fungsi untuk operasi read () - tidak ada yang seperti pty_read() . Faktanya adalah bahwa membaca hanya akan dilayani oleh disiplin garis. Dengan demikian, kita belajar tentang fitur penting kedua dari disiplin garis - membaca data dari perangkat TTY.




2. ptmx_open


Sekarang mari kita beralih ke ptmx_open () :


 static int ptmx_open(struct inode *inode, struct file *filp) { struct tty_struct *tty; //    -   ! fsi = devpts_acquire(filp); //     devpts index = devpts_new_index(fsi); //       /dev/pts // ... tty = tty_init_dev(ptm_driver, index); // ... devpts_pty_new(fsi, index, tty->link); //     /dev/pts retval = ptm_driver->ops->open(tty, filp); //  PTM ,   } 

Kami tertarik pada fungsi tty_init_dev() , di mana argumen pertama adalah driver perangkat PTM, dan yang kedua adalah indeks perangkat. Di sini kita meninggalkan zona tanggung jawab driver PTY dan pergi ke file, yang hanya bertanggung jawab untuk perangkat TTY umum dan tidak tahu apa-apa tentang pseudo-terminal kami.


 struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx) { struct tty_struct *tty; tty = alloc_tty_struct(driver, idx); retval = tty_driver_install_tty(driver, tty); /* * Structures all installed ... call the ldisc open routines. */ retval = tty_ldisc_setup(tty, tty->link); //  ,       return tty; } 

Pertama, kami akan alloc_tty_struct() :


 struct tty_struct *alloc_tty_struct(struct tty_driver *driver, int idx) { struct tty_struct *tty; tty = kzalloc(sizeof(*tty), GFP_KERNEL); //  tty_struct tty_ldisc_init(tty) //      tty_struct tty->driver = driver; //       tty_struct tty->ops = driver->ops; //        tty_struct.     tty->index = idx; //   tty  return tty; } 

Satu-satunya hal yang menarik bagi kami di sini adalah fungsi tty_ldisc_init() :


 int tty_ldisc_init(struct tty_struct *tty) { struct tty_ldisc *ld = tty_ldisc_get(tty, N_TTY); if (IS_ERR(ld)) return PTR_ERR(ld); tty->ldisc = ld; //        tty_struct return 0; } 

Yang memanggil tty_ldisc_get() :


 static struct tty_ldisc *tty_ldisc_get(struct tty_struct *tty, int disc) { struct tty_ldisc *ld; //    struct tty_ldisc_ops *ldops; //     ldops = get_ldops(disc); //      .   ,       .   - N_TTY ld = kmalloc(sizeof(struct tty_ldisc), GFP_KERNEL | __GFP_NOFAIL); ld->ops = ldops; //       ld->tty = tty; //    tty_struct   .          return ld; } 

Jadi, kami memeriksa panggilan ke fungsi alloc_tty_struct() , yang membuat struktur tty_struct bersama dengan disiplin garis - struktur tty_ldisc . Kedua struktur memiliki hubungan satu sama lain. Mari kita lihat lebih dekat struktur-struktur ini.


  • tty_struct adalah struktur untuk mengakses driver perangkat TTY dan beberapa bidang lainnya. Ini terlihat seperti ini:

 struct tty_struct { struct tty_driver *driver; //  TTY  const struct tty_operations *ops; //  .    ,   driver->ops,       int index; //   struct tty_ldisc *ldisc; //     struct tty_struct *link; //     PTY // ... } 

  • tty_ldisc adalah struktur untuk disiplin garis TTY perangkat. Ini terdiri dari hanya dua bidang dan terlihat sebagai berikut:

 struct tty_ldisc { struct tty_ldisc_ops *ops; //    struct tty_struct *tty; //   tty_struct  .       }; 

Sepertinya tidak ada yang rumit? Mari kita tambahkan semua struktur yang dianggap sampai saat ini ke keranjang kami dan tautkan dengan cara yang sama seperti mereka terhubung dalam kode:
Bangun tty_struct


Tapi kami membuat tty_struct hanya untuk perangkat PTM. Bagaimana dengan perangkat PTS? Untuk melakukan ini, kita kembali ke fungsi tty_init_dev() dan ingat bahwa kita kemudian diharapkan memanggil fungsi tty_driver_install_tty() :


 /** * This method is responsible * for ensuring any need additional structures are allocated and configured. */ static int tty_driver_install_tty(struct tty_driver *driver, struct tty_struct *tty) { return driver->ops->install ? driver->ops->install(driver, tty) : tty_standard_install(driver, tty); } 

Komentar tersebut memberi tahu kita bahwa metode ini bertanggung jawab untuk membuat berbagai struktur tambahan. Perangkat PTS dan akan menjadi struktur tambahan kami. Saya akui, itu sangat mengejutkan bagi saya, karena itu adalah, neraka, seluruh perangkat, dan bukan hanya semacam struktur tambahan! Tapi kita semua mengerti bahwa semua perangkat hanyalah semacam struktur, jadi teruskan saja. Ok, apa driver-> ops-> instal di sini ? Untuk melakukan ini, lihat kembali tabel fungsi untuk driver PTM:


 static const struct tty_operations ptm_unix98_ops = { .install = pty_unix98_install, // ... 

Dan kami memahami bahwa kami tertarik dengan fungsi pty_unix98_install() :


 static int pty_unix98_install(struct tty_driver *driver, struct tty_struct *tty) { return pty_common_install(driver, tty, false); } 

Yang memanggil fungsi pty_common_install() :


 static int pty_common_install(struct tty_driver *driver, struct tty_struct *tty, bool legacy) { struct tty_struct *o_tty; // tty_struct    PTY -    PTS  //    ,       install.   ,   PTM     tty_struct,        if (driver->subtype != PTY_TYPE_MASTER) return -EIO; o_tty = alloc_tty_struct(driver->other, idx); tty->link = o_tty; o_tty->link = tty; } 

, PTS tty_struct , PTS . . tty_struct PTS .





, TTY ( - ?).
— , PTM, PTS :


 static const struct file_operations tty_fops = { .llseek = no_llseek, .read = tty_read, .write = tty_write, .poll = tty_poll, .unlocked_ioctl = tty_ioctl, .compat_ioctl = tty_compat_ioctl, .open = tty_open, .release = tty_release, .fasync = tty_fasync, .show_fdinfo = tty_show_fdinfo, }; 

, TTY .




. , /dev/ptmx . , PTS , , PTM , :





Halo Dunia!


. "Hello, World!", .


 #include <stdio.h> void main() { printf("Hello, World!\n"); } 

, "Hello, World!" . , , , . , . stdout /dev/null — . , Linux.


Unix write() , read() , close() , write() /dev/pts/0 __vfs_write() :


 ssize_t __vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos) { ssize_t ret; //... ret = file->f_op->write(file, buf, count, pos); //... return ret; } 

write() . , :


 static const struct file_operations tty_fops = { // ... .write = tty_write, // ... 

tty_write() :


 static ssize_t tty_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { struct tty_struct *tty = file_tty(file); struct tty_ldisc *ld; ssize_t ret; ld = tty_ldisc_ref_wait(tty); ret = do_tty_write(ld->ops->write, tty, file, buf, count); tty_ldisc_deref(ld); return ret; } 

tty_struct TTY , write() . :


 static struct tty_ldisc_ops n_tty_ops = { .write = n_tty_write, // ... }; 

n_tty_write() :


 /** * n_tty_write - write function for tty * @tty: tty device * @file: file object * @buf: userspace buffer pointer * @nr: size of I/O */ static ssize_t n_tty_write(struct tty_struct *tty, struct file *file, const unsigned char *buf, size_t nr) { const unsigned char *b = buf; // b - ,       "Hello, World!".          int c; //    //     PTS ,  write()    0,  ,     while (nr > 0) { c = tty->ops->write(tty, b, nr); //  write()       TTY  if (!c) break; b += c; //     nr -= c; //      :  -  -  -  } } 

, "Hello, World!" write() PTS . :


 static const struct tty_operations pty_unix98_ops = { .write = pty_write, // ... } 

pty_write() :


 static int pty_write(struct tty_struct *tty, const unsigned char *buf, int c) { struct tty_struct *to = tty->link; //      PTY.    -  PTM  if (c > 0) { //    PTM  c = tty_insert_flip_string(to->port, buf, c); //     ,       if (c) { tty_flip_buffer_push(to->port); tty_wakeup(tty); } } return c; } 

:


  __vfs_write() -> // 1- :   tty_write() -> do_tty_write() -> n_tty_write() -> // 2- :   pty_write() // 3- :  

. , PTM . , .


, flip buffer . Flip buffer — , . tty driver , . , . , , . , , . - flip buffer — (, - , flip).


, . tty_insert_flip_string() tty_insert_flip_string_fixed_flag() , PTM :


 int tty_insert_flip_string_fixed_flag(struct tty_port *port, const unsigned char *chars, char flag, size_t size) { int copied = 0; do { int goal = min_t(size_t, size - copied, TTY_BUFFER_PAGE); //      int space = __tty_buffer_request_room(port, goal, flags); //     struct tty_buffer *tb = port->buf.tail; //       if (unlikely(space == 0)) break; memcpy(char_buf_ptr(tb, tb->used), chars, space); //      tb->used += space; copied += space; chars += space; /* There is a small chance that we need to split the data over several buffers. If this is the case we must loop */ } while (unlikely(size > copied)); return copied; } 

, flip buffer , , . , — PTM , .


, "Hello, World!" PTM . GNOME Terminal Server poll() ( I/O) master . , ? Bagaimanapun caranya. , , — .


tty_flip_buffer_push() ( pty_write):


 /** * tty_flip_buffer_push - terminal * @port: tty port to push * * Queue a push of the terminal flip buffers to the line discipline. * Can be called from IRQ/atomic context. * * In the event of the queue being busy for flipping the work will be * held off and retried later. */ void tty_flip_buffer_push(struct tty_port *port) { tty_schedule_flip(port); } 

tty_schedule_flip() , , :


 /** * tty_schedule_flip - push characters to ldisc * @port: tty port to push from * * Takes any pending buffers and transfers their ownership to the * ldisc side of the queue. It then schedules those characters for * processing by the line discipline. */ void tty_schedule_flip(struct tty_port *port) { struct tty_bufhead *buf = &port->buf; /* paired w/ acquire in flush_to_ldisc(); ensures * flush_to_ldisc() sees buffer data. */ smp_store_release(&buf->tail->commit, buf->tail->used); queue_work(system_unbound_wq, &buf->work); } 

, work (, - ) , — , flush_to_ldisc() :


 static void flush_to_ldisc(struct work_struct *work) { struct tty_port *port = container_of(work, struct tty_port, buf.work); //   tty_port PTM . tty_port -       TTY  struct tty_bufhead *buf = &port->buf; struct tty_buffer *head = buf->head; // ... receive_buf(port, head); // ... } 

receive_buf() __receive_buf() , :


 static void __receive_buf(struct tty_struct *tty, const unsigned char *cp, char *fp, int count) { struct n_tty_data *ldata = tty->disc_data; bool preops = I_ISTRIP(tty) || (I_IUCLC(tty) && L_IEXTEN(tty)); if (ldata->real_raw) n_tty_receive_buf_real_raw(tty, cp, fp, count); else if (ldata->raw || (L_EXTPROC(tty) && !preops)) n_tty_receive_buf_raw(tty, cp, fp, count); else if (tty->closing && !L_EXTPROC(tty)) n_tty_receive_buf_closing(tty, cp, fp, count); else { if (ldata->lnext) { char flag = TTY_NORMAL; if (fp) flag = *fp++; n_tty_receive_char_lnext(tty, *cp++, flag); count--; } if (!preops && !I_PARMRK(tty)) n_tty_receive_buf_fast(tty, cp, fp, count); else n_tty_receive_buf_standard(tty, cp, fp, count); } if (read_cnt(ldata)) { kill_fasync(&tty->fasync, SIGIO, POLL_IN); wake_up_interruptible_poll(&tty->read_wait, EPOLLIN); } } 

, n_tty_receive_buf ( , _raw) read_buf , TTY . PTM raw , read_buf. , PTM PTS , .


, :


  ... pty_write() -> // 3- :  PTS  tty_insert_flip_string + tty_flip_buffer_push() -> tty_schedule_flip() -> --- //    PTM  flush_to_ldisc() -> // 2- :   PTM  receive_buf() -> n_tty_receive_buf -> n_tty_receive_buf_common -> __receive_buf() 

, PTM — PTS .


: PTM . GNOME Terminal Server "Hello, World!", read() PTM . read() write() — n_tty_read() . , , — read_buf — . GNOME Terminal Server X Server, .


, "Hello, World!" :


  -> PTY slave -> PTY master -> GNOME-TERMINAl-SERVER -> X Server -> ->  



Kesimpulan


. :


  1. TTY
  2. ,

, ! - — , !


Sumber


Source: https://habr.com/ru/post/id460257/


All Articles