Okay, but why is it a signal at all? In the end, isn't it just escape sequences sent back and forth in-band? Wouldn't it be simpler to just let the program read and write those rather than involving a separate system call?
My understanding is that the original SunOS windowing system was deeply connected to the kernel (for instance, windows were devices). The original purpose of the signal (in SunOS) appears to have been telling graphical programs that their window had 'changed' and needed to be repainted (I don't know if this was simply from resizing or if it was any damage), which makes a certain amount of sense for a kernel based windowing system (the kernel is right there and you need some way to notify programs). The use of the signal for telling programs in terminal windows that the window had resized appears to have been distinctly secondary, and only the latter can interact with their window through in-band escape sequences.
As with everything tty related, there's a lot of history baked into the architecture. As new features arrive, you have to find some place in that architecture to insert it, perhaps awkwardly, but it beats rewriting the world.
So we start the interactive age with teletypes, keyboards and paper. Nothing beyond printing the characters, ringing the bell, and the basic ACSII chars for moving right (SPACE, TAB), left (BS, CR), and down (VT, FF, LF). The main editor is a line editor called ed.
Move ahead a few years and you have 'glass' ttys, every one of them different. This is the termcap and curses era. The screen size is baked into the termcap entry. Editors are now interactive 'screen' editors, vi and emacs mostly. Pretty soon, some terminals can support multiple font sizes and the LINES and COLUMNS environment variables can override the termcap entries. Terminals are still separate devices connected via RS-232 and all communication is in-band.
Move ahead a few more years and we're now in the era of workstations with bitmapped graphics (and their Mac and PC equivalents). 'Terminals' are now interactive graphics programs that run under the window system (either X11 or proprietary), so they need to translate kbd and mouse events into the ASCII byte stream onto a pty to emulate the old RS-232 convention to talk to the "line dscipline" part of the kernel's tty driver. But now, changing the size of a window is trivial; people do it all the time. They do it in the middle of vi sessions. They do it when they've ^Z'd out of vi and then expect vi to know the new size when they fg back into it. You can't just blast escape codes onto the line as whatever is reading stdin is likely not equipped to deal with it. At the very least, the in-band exchange has to be initiated by the 'computer' side of the pty. So, they added SIGWINCH and it was an out-of-band signal sent from the terminal emulator program through the pty (which, again, is a software abstraction in the os as opposed to an RS-232 line) to tell the other side of the pty that, when it has a chance, it should re-query the size of the terminal. It took a little while to nail down the semantics of the shell notifying backgrounded processes, but it got the job done. But it likely doesn't make a lick of sense to anyone that doesn't remember the good ol' days.
Thanks. The case about background processes makes it click. I would expect communication between the terminal app and the shell on the other end to still use in-band signalling, since it might be over RS-232 or SSH or anything else, but then that process that's currently playing mainframe would want to propagate that information to everything 'behind' it, and a signal makes sense for that.
If you dig into the RFCs, you'll see that ssh and telnet can negotiate whether each end can send/receive out-of-band messages about changes to window size. If they both can, the client side gets a local SIGWINCH, turns that into a notification over the wire, then the server side generates a SIGWINCH on its side that propagates through its pty and onto the programs attached to it.
For an rs-232 attached classic terminal that can change rows/columns, all you've really got is running eval `resize` and updating the environment variables. Most visual programs will check the environment variables on suspend/resume and maybe (it's been a while) on full-repaint (e.g. ^L on vim).
Upon further thought, the child can't inspect a parent's environment variables after forking, so my thinking of ^Z eval `resize`; fg can't be the way it worked. Maybe quit and restart vi was the only way back then. It's a been a while.
Exactly - these should be reported along with any other input event as an escape sequence. There are other comments on this post that link to how this is done.