I was inspired to write this article by an article on the analysis of Sishny printf . However, there was a missed moment about which path the data travels after it enters the terminal device. In this article I want to correct this defect and analyze the data path in the terminal. We will also understand how Terminal differs from Shell, what Pseudoterminal is, how terminal emulators work and much more.
Let's first understand what Terminal, Shell, Console are, what makes Terminal Emulator different from the usual Terminal and why it is so named. There is already quite a lot of information about this, so you will not hear anything new here. Almost all the information here was taken from the Internet; I will cite the links at the end of the article. Who already knows what all these things mean, can safely skip this section.
A terminal is a combination of a display and a keyboard, that is, a physical device. Before the terminals became this particular combination, they were some kind of device called teleprinter (teletype, teletypewriter or TTY for short), that is, a combination of printer and keyboard. Usually several terminals connected to the same computer. In this way, it was possible for several users to work on the same computer, with each having their own session, independent of the others. The terminal was named so because it was located at the end of the terminal cable (terminal end).
This is Teletype :
And this is the Terminal :
Console (console) - a terminal that is connected directly to a computer. The fact is that most of the terminals were connected implicitly, but at least one was connected directly to the computer. The console was allowed to use a strictly defined circle of people, as it allowed to customize the computer.
If the previous two are physical devices, then this definition refers exclusively to software.
Shell is the command line interpreter. The main purpose is to run other programs. There are a large number of different shells. The most common is Bash (from the English. Bourne Again SHell, which, as Wikipedia suggests, is a pun for “Born again” Shell, that is, “revived” Shell). Other examples: Dash (lightweight Shell, available if you run the binary at / bin / sh), Zsh.
Of course, both terminals and consoles could not but find their reflection in modern times. Therefore, we will look further at such things as Terminal Emulator and Virtual Console .
Terminal Emulator - the good old terminal emulator. A terminal emulator is required for programs that cannot directly interact with the X Window System - Bash, Vim, and others.
Let's first establish the duties of the terminal:
Similarly, our Terminal Emulator does exactly the same thing: it delivers user input to a running program, and also displays the output of the program to the display. In any case, the meaning is preserved - between the user and the running program, there is some layer responsible for input / output. Terminal Terminal examples: gnome-terminal, xterm, konsole.
Please do not confuse Shell and Terminal Emulator!
Terminal Emulator is a GUI application, that is, a window in the X Windows System. A shell is a command line interpreter, that is, just an executor of commands, it does not have a graphical shell. If you say quite correctly, you do not run Bash , you run Terminal Emulator, which runs Bash inside . Terminal Emulator and Bash are absolutely 2 different programs. The first is responsible solely for I / O, the second - for processing commands.
Further in the article, all references to the terminal will refer to the terminal emulator.
Press Ctrl + Alt + FN, where N usually has values ​​from 1 to 6. What you have just seen is called Virtual Console (virtual console) or Virtual Terminal (virtual terminal). Remember what I said earlier about terminals? Many terminals were connected to one computer and each terminal was a separate session, independent of the others. The Virtual Console repeats this idea: there may be several independent sessions inside your computer (however, computer resources are obviously shared).
You can call this entity both Virtual Console and Virtual Terminal, since by definition, the console is a terminal connected directly to a computer, but all virtual terminals are in a sense connected directly to a computer.
Each terminal is assigned its own TTY device (terminal device), which provides console operation. Although you will hardly find teletypes, the TTY abbreviation has reached our days.
A TTY device consists of two fundamental components:
TTY device structure:
There are 3 types of TTY devices:
In this article we will talk about the second type of TTY devices - pseudo terminals.
We begin to consider the discipline of the line TTY devices.
The first important feature of the discipline line is that it is responsible for I / O processing. This includes, for example, processing control characters (see Control Characters ) and output formatting. For example, you enter any text, but suddenly you realize that you were mistaken in writing something and you want to erase it — this is where the discipline of the line comes into play.
Let us examine in detail what exactly happens when we work in Bash running in the terminal. By default, the TTY device works in canonical mode with echo enabled . Echo is the display of the characters you entered on the screen.
When we enter, for example, the character a
, this character is sent to the TTY device, but intercepted by the discipline line of the TTY device. She reads the character in her internal buffer, sees that echo
mode is on and displays the character on the screen. At this time, nothing is yet available for reading in the program to which the terminal device is attached. Let us press the backspace
on the keyboard. The symbol ^?
again intercepts the line discipline, and the last one, realizing that the user wants to erase the last character entered, deletes the given character from its internal buffer and erases this character also from the screen. Now, if we press Enter, the TTY Line Discipline will finally send everything that was written to the internal buffer of the discipline, including LF, to the reading buffer of the terminal device. In this case, the characters CR and LF are displayed in order to move the cursor to a new line - this is the output formatting.
This is how the canonical mode works - it transfers all the entered characters to the device only after pressing Enter
, processes the control characters and formats the output.
TTY Line Editing is the component that is responsible for processing input to the line discipline. It should be said that Line Editing is a general concept and refers to input processing. For example, Bash and Vim have their Line Editing.
We can control the discipline settings of the line of the current TTY device using the stty program. Let's experiment a little.
Open Bash or any other Shell and enter:
stty icanon -echo
Now try typing something - and you won't see your input (don't worry, you can still transfer input to the program). You have just turned off the echo - that is, the display of the entered characters on the screen. Now enter:
stty raw echo
Try typing something. You see how the output is broken. But for more effect, let's go into Dash - enter /bin/sh
. Now try typing special characters ( Ctrl
key + any character on the keyboard) or simply press Enter
. You wonder - what are these strange characters on the screen? The fact is that we, having entered the simplest Shell, besides Line Editing, the discipline itself also turned off the Line Editing Bash, and now we can see the effect of the inclusion of the raw line discipline mode. This mode does not process input at all and does not format output. Why raw mode is needed? For example, for Vim : it opens into the entire terminal window and performs the input processing itself, at least so that the special symbols of the discipline of the line do not overlap with the special symbols of Vim itself.
For even more understanding, let's consider customizing control characters. The stty <control-character> <string>
command stty <control-character> <string>
will help us with this.
Enter in bash:
stty erase 0
Now the erase
control character will be assigned to the 0
character. The backspace
button usually matters ^?
, but now this special character will be transferred to the PTS device read buffer literally - try it yourself. Now you can erase characters using the 0
button on the keyboard, because you yourself asked the tty line discipline to recognize the entered character as the erase
control character. You can stty erase ^\?
setting using the command stty erase ^\?
or just closing the terminal, because we only influenced the current tty device.
More information can be found in man stty .
Every time we open a new terminal on the X Window System, the GNOME Terminal Server spawns a new process and starts the program that is selected by default. Usually, this is some kind of Shell (for example, Bash).
Communication with the running program occurs through the so-called Pseudoterminal (pseudo-terminal, PTY). The pseudo-terminal itself exists in the kernel, but it receives input from user space — from a terminal emulator.
A pseudo-terminal consists of the following two virtual TTY devices :
1) PTY master (PTM) - the leading part of the pseudo-terminal. The GNOME Terminal Server is used to transfer keyboard input to a program running inside the terminal, as well as to read the output of the program and display the output to the display. GNOME Terminal Server, in turn, communicates with the X Window System over the X protocol.
2) PTY slave (PTS) - the pseudo-terminal slave . Used by a program running inside the terminal to read input from the keyboard and display the output to the screen. At least, the program itself thinks so (I will explain what this means, a little further).
Any data recorded in the PTS device is the input of the PTM device, that is, it becomes available for reading on the PTM device. And vice versa: any data recorded in the PTM device is the PTS input of the device. This is how the GNOME Terminal Server and the program running inside the terminal communicate. Each PTM device is associated with its own PTS device.
The process of launching a new terminal looks like this:
1) GNOME Terminal Server creates the master and slave devices by calling the open () function on the special device / dev / ptmx . The open () call returns the file descriptor of the created PTM device — master_fd .
2) GNOME Terminal Server creates a new process by calling the fork()
function. This process will be the new terminal.
3) In the PTS terminal, the device opens on file descriptors 0, 1, 2 (stdin, stdout and stderr, respectively). Now the standard I / O streams of the terminal lead to this device.
4) The terminal starts the desired program by calling the exec()
function. Usually, some kind of Shell is started (for example, Bash). Any program subsequently launched from Bash will have the same file descriptors as Bash itself, that is, the program streams will be sent to the PTS device.
You can see for yourself where the standard output streams of the terminal are directed using the ls -la /proc/self/fd
:
The PTS device is located along the path / dev / pts / N , and we are absolutely not interested in the path to the PTM device. The fact is that the GNOME Terminal Server already has a file descriptor for an open PTM device and does not need a path to it, however in the child process we have to open the PTS device on standard output streams by calling the open()
function, which requires a file path.
Remember, I said that a program using a PTS device only thinks that it communicates directly with the terminal? The fact is that the PTS is also a terminal device (TTY device), but the difference between the PTS device and the actual TTY device is that the PTS device receives input not from the keyboard, but from the master device, and the output goes not to the display, but to master device That is why the pseudo-terminal is so named - the pseudo-terminal only imitates (again?) The terminal. The difference between a terminal emulator and a pseudo-terminal is that a terminal emulator is only a graphical program that allows you to run the terminal directly inside the window interface, but this feature is implemented using a pseudo-terminal.
The fact that the PTS device is a TTY device is very important. That's why:
A PTM device is also a TTY device, but it does not play any role, since it is not used as a controlling terminal. Moreover, the discipline of the PTM line of the device is set to raw mode, so processing is not performed when transferring data from PTS to PTM device. However, the read()
and write()
calls from user space are still first served by discipline lines on both devices. This moment will play an even greater role, as we shall see later.
The process of communication between GNOME Terminal Server and the program running inside the terminal is as follows:
It is necessary to consider in more detail the role played by the discipline of the line when communicating between the two parts of the pseudo-terminal. Here, the line discipline is responsible for processing data from PTM to a PTS device , as well as for delivering data from one part of the pseudo-terminal to another. When we are in the PTS device driver, we use the discipline line of the PTM device, and vice versa.
You might have thought that you could open a file along the path / dev / pts / N and write or read data from it, as from a regular text file? Yes, all devices in Unix-like systems are files thanks to the fundamental principle of Unix, which says that everything is a file. However, no special device files (device file) are text files. Such devices are called virtual devices (virtual device) - that is, they exist exclusively in memory, and not on disk.
You should not try to open these files as ordinary text files. However, you can use these devices through the write()
and read()
operations, the call of which will serve the device driver. Let's try to do it.
Open two terminal windows and type tty
in each. This command will show which TTY device is servicing the current active terminal. Now type echo "Hello, World!" > /dev/pts/N
echo "Hello, World!" > /dev/pts/N
in the first terminal window, where N is the PTS device index of the second window, switch to the second window and you will see your input from the first window. Now you have recorded the data in the PTS device of the second window as if the program running in that terminal did it .
We are getting closer to the final part of the article, but before that we take a look under the hood of Linux - consider a pseudo-terminal device at the kernel level. There will be a lot of code, but I will try to explain each block of code as detailed as possible, reduce unimportant details and go consistently.
Before we start we introduce the so-called "basket of components". As we move along the core, we will add more and more components to it and find a connection between them. I hope this helps to understand the pseudo-terminal device even better. Let's get started
When Linux starts up, it loads the necessary device drivers. Such a driver is also available for our pseudo-terminal. Its registration begins with a call to this function:
static int __init pty_init(void) { legacy_pty_init(); unix98_pty_init(); // <- , return 0; } device_initcall(pty_init); // ,
For all modern systems, the function unix98_pty_init()
will be called:
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");
Here we are interested in 3 things:
tty_set_operatons
for the pty master driver and pty slave devices.ptmx_open
function, which is responsible for creating both parts of the pseudo-terminal when opening the special device / dev / ptmx . Important: / dev / ptmx is not a PTM device, but only an interface for creating a new pseudo-terminal.Let's go in order:
The tty_set_operations () function only sets the function table for the current driver:
void tty_set_operations(struct tty_driver *driver, const struct tty_operations *op) { driver->ops = op; };
The tty_operations structure is a function table that is used to access the functions of the TTY device driver.
I will highlight the most important thing in the pty_unix98_ops
and ptm_unix98_ops
, which are the table of functions for the corresponding parts of the pseudo-terminal:
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, // ... };
Here you can see the pty_write function already familiar with the article about Sishnoy printf - we will return to it a little later.
Let's add this structure to our component basket:
As you can see, the basic methods of both drivers do not differ at all. By the way, notice that there is no function for the read () operation - there is nothing like pty_read()
. The fact is that reading will be served exclusively by the discipline of the line. Thus, we learn about the second important feature of the line discipline - reading data from the TTY device.
Now let's go to 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 , }
We are interested in the tty_init_dev()
function, where the first argument is the device's PTM driver, and the second is the device index. Here we leave the area of ​​responsibility of the PTY driver and go to the file that is only responsible for common TTY devices and does not know anything about our pseudo-terminal.
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; }
First we analyze the alloc_tty_struct()
function:
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; }
The only thing that interests us here is the tty_ldisc_init()
function:
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; }
Which calls 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; }
So, we have considered the call of the function alloc_tty_struct()
, which creates the structure tty_struct together with the discipline of the line - the structure tty_ldisc . Both structures have links to each other. Let's take a closer look at these structures.
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 // ... }
struct tty_ldisc { struct tty_ldisc_ops *ops; // struct tty_struct *tty; // tty_struct . };
It seems to be nothing complicated? Let's add all the structures considered up to this point in our basket and link them in the same way as they are connected in the code:
But we created a tty_struct just for a PTM device. And what about the PTS device? To do this, let’s return to the tty_init_dev()
function and recall what the tty_driver_install_tty()
function is waiting for us next:
/** * 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); }
The comment tells us that this method is responsible for creating various additional structures. The PTS device will be our additional structure. I admit, it was extremely surprising for me, because it’s a hell of a whole device, and not just some kind of additional structure! But we all understand that all devices are just some kind of structure, so go ahead. Well, what is driver-> ops-> install ? To do this, look at the table of functions for the PTM driver again:
static const struct tty_operations ptm_unix98_ops = { .install = pty_unix98_install, // ...
And we pty_unix98_install()
understand that we are interested in the pty_unix98_install()
function:
static int pty_unix98_install(struct tty_driver *driver, struct tty_struct *tty) { return pty_common_install(driver, tty, false); }
Which calls the pty_common_install()
function:
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 .
Is done. , /dev/ptmx . , PTS , , PTM , :
. "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 . , ? . , , — .
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 -> ->
. :
, ! - — , !
Source: https://habr.com/ru/post/460257/
All Articles