Terminal driver

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
Whatever5k

Terminal driver

Post by Whatever5k »

I'm wondering what the terminal driver is supposed to do...
What are the goals it has to achieve? For example, when a user does a gets(), what does the terminal driver do? And how does it differ from the keyboard driver?
Any idea/experience?
dronkit

Re:Terminal driver

Post by dronkit »

The terminal driver is responsible for buffering, job control, controlling terminals ioctl's and modes like cooked- or raw-mode, etc.

the keyboard driver is responsible for reading keys and save them into a buffer. The terminal driver then decides what to do with that input.

When a user do a gets(), the terminal, if in cooked mode, will store keys from the user until an EOL. After this EOL the terminal driver returns the full line of text to the shell.
Whatever5k

Re:Terminal driver

Post by Whatever5k »

Ok, and do I have to implement this with message handling?
dronkit

Re:Terminal driver

Post by dronkit »

It all depends. Is your kernel message-based? ;) is your terminal driver a regular "task"?
Whatever5k

Re:Terminal driver

Post by Whatever5k »

No, it is supposed to be a monolothic kernel, not a Microkernel...
How would this have to be implemented, then?

btw, a kernel doesn't have a gets() function, does it? The gets() function is in the user library, right? And gets() just does a system call like read?
dronkit

Re:Terminal driver

Post by dronkit »

No, it is supposed to be a monolothic kernel, not a Microkernel...
How would this have to be implemented, then?
Terminal drivers typically maintain two buffers in kernel space, one for input
and one for output. These are maintained as queues so that the correct order
is maintained.

When the device interrupts to say that data is ready to read, the data is placed
on the incoming queue.

Received data is placed into the raw input queue by the driver and is consumed by a caller only when application processes request data.

Device drivers simply add received data to the raw input queue or consume and transmit data from the output queue. The driver decides when (and if) output transmission is to be suspended, how (and if) received data is echoed, etc

In raw mode, the driver performs no editing on received characters. This reduces the processing done on each character to a minimum and provides the highest performance interface for reading data.

In raw mode, each character is received into the raw input
buffer by the interrupt handler. When an application requests data from the device, it can specify under what conditions an input request is to be satisfied. Until the conditions are satisfied, the interrupt handler won't signal the driver to run, and the driver won't return any data to the application. The normal case of a simple read by an application would block until at least one character was available.

In edited mode, the driver performs line-editing operations on each received character. Only when a line is "completely entered" -- typically when a carriage return (CR) is received -- will the line of data be made
available to application processes. This mode of operation
is often referred to as canonical or sometimes "cooked" mode.

The cooked mode provides a set of editing capabilities, including full support for moving over the line with cursor keys and for changing, inserting, or deleting characters.

Maybe this will help you http://www.piratehaven.org/~bapper/298c ... otes7.html
btw, a kernel doesn't have a gets() function, does it? The gets() function is in the user library, right? And gets() just does a system call like read?
Exactly. What really matters here is your terminal driver and your libc.
If you call read(), you have to pass along a file descriptor. These file descriptor will reference to your correct driver (in this case, stdin).
Whatever5k

Re:Terminal driver

Post by Whatever5k »

I've been busy up to now, so sorry for this late posting...

Ok, I think I got more than the half of the picture - my problem was/is, that I don't really know how to implement it...
So, if I press a key, my keyboard driver puts the ASCII code into a queue. That's fine. And when a user process does a getch(), the terminal driver gets the character from this input queue and gives it to the user.
But how is this done? The way from "keyboard driver -> terminal driver" is clear, but the way from "user -> terminal driver" isn't. For example, getch() function does nothing else than a READ (systemcall). And the read() function is not in the user library, but in the kernel library, isn't it? But what is the terminal driver? A function that is called on every READ? Or a task that is always running?
Do you get my problem?
dronkit

Re:Terminal driver

Post by dronkit »

Code: Select all

Ok, I think I got more than the half of the picture - my problem was/is, that I don't really know how to implement it...
So, if I press a key, my keyboard driver puts the ASCII code into a queue. That's fine. 
Yes, in a ukernel a keyboard interrupt generates a msg which is passed to
the terminal task. In a monolithic kernel each interrupt should buffer
that event and eventually move the data to the correspondent
terminal input buffer.

Code: Select all

And when a user process does a getch(), the terminal driver gets the character from this input queue and gives it to the user.
But how is this done? The way from "keyboard driver -> terminal driver" is clear, but the way from "user -> terminal driver" isn't. For example, getch() function does nothing else than a READ (systemcall). And the read() function is not in the user library, but in the kernel library, isn't it? But what is the terminal driver? A function that is called on every READ? Or a task that is always running?
Do you get my problem?
Well, that is an alternative. The whole thing can be complicated for a
generic terminal or console driver because of the different BPS for each
device... For example the FreeBSD terminal driver first tries to know
the current console bps in order to know how to process the events...

FreeBSD uses what it calls "silos". they are really buffers. For each input character (a keyboard interrupt, for example) this silos are filled and the
terminal driver is called directly only when a special key appears (^c for example).

I'll assume you're writing just a keyboard-monitor driver ;)

Now, this is how bsd does it...
The tty driver will have an input queue and an output queue. On a monolithic kernel you should copy the input to the input queue and then the terminal driver will have to act accordingly (cooked mode, raw mode, flush, etc). A call to getch() can efectually call read() to stdin and the stdin driver will know it has to read from the current driver input queue.

In FreeBSD each driver has a generic way of getting input. All calls
to read from a character-special device, result in a call to the device driver's d_read entry with a device number, a uio structure describing the data to be read, and a flag specifying whether the I/O is nonblocking. Terminal device drivers use the device number to locate the tty structure for the device, then call the line discilpine l_read entry to process the system call.

You also have two input queues. One for canonical mode and one for raw mode. Finally, ttread() checks for data in the appropiate queue (canonical or raw) and if no data are present, returns error if the terminal is using nonblocking I/O. Otherwise it sleeps on the address of the raw queue. When ttread() is awakened, it restarts processing from the beginning because the terminal state or process group might have changred while it was asleep.

When characters are present in the queue for which ttread() is awaiting, they are removed from the queue one at a time with getc() and are copied out to the user's buffer with ureadc(). This is because of you reading in kernel space and the getc() caller in user-space.

In canonical mode, certain characters receive special processing as they are removed from the queue: If there were no previous characters, the EOF results in the read returning zero characters, and that is interpreted by user programs as an effective EOF...

Hope it helps ;)
Whatever5k

Re:Terminal driver

Post by Whatever5k »

Ok, I got the whole picture now...
But do you have an implementation of all that. How does a user function call a system function? Did you implement this in your Operating System?
dronkit

Re:Terminal driver

Post by dronkit »

Ok, I got the whole picture now...
But do you have an implementation of all that. How does a user function call a system function?
Available implementations: minix, linux, bsd, skyos

In most cases, if a user wants to call an os function it will use a trap gate (int 21h on dos, int 80h in linux/bsd/unix, int 48h in roadrunnerOS).

you give access to kernel functions via a system call. A system call generally is called by mov'ing the right value on %eax and then issuing an int instruction (like int 21h or int 80h to call the OS)

the way libc functions are called depends on the design of your os. if you use shared libraries, ld should know, map and resolve every symbol
you use from your user programs. static linking is much simpler because the code is already there.

Sometimes, however, the kernel will need to write to user-space (that is, to a lower privilege level ring) to return data because user-space can't write/read to/from kernel-space directly. So be carefull where you put your buffers ;)
Did you implement this in your Operating System?
Not yet, i'm focusing on a good design for a distributed ukernel and the terminal is not a problem yet ;)
Whatever5k

Re:Terminal driver

Post by Whatever5k »

Ah, ok...so a system call is made through a call gate in the IDT - will have to look this up further...
Post Reply