24.22. Close a Window by Killing Its Process(es)
In the X Window System, there's a process controlling every window. If the window (or its process) is frozen and you can't get rid of it, the easier way is usually to kill (Section 24.12) the process. As Section 24.20 explains, there may be a chain of processes running; the window could come from the parent process (as in the case of an xterm with a shell running inside of it) or it could be the child (such as when a shell script runs an X client like xmessage -- as in the nup script below). Your job is to use ps (Section 24.5) to track down the process(es) behind the window and kill the right one(s). We'll look at two different examples, then look at a shell script that opens a window and, later, closes the window by killing its process.
24.22.1. Example #1: An xterm Window
Let's say you're running vi in an xterm window, and the window seems to be frozen. Start with some detective work: open up another xterm window and run ps alwx or ps -ef. (If you're sure that all the processes in the window are owned by you -- and none were set user ID (Section 1.17) -- you can use run ps lwx, for example.) You want a listing that shows the chain of process IDs, parent-to-child, in that window. The tty (Section 2.7) of the shell inside the xterm will help you find the right one, if you know it. For example, I found vi 0568.sgm running on the tty pts/5, so the shell I want (the parent of vi) must also be on pts/5. From the shell's parent ID, I can find the PID of the xterm that started the shell. (I'll cut some of the columns in this listing to make it easier to read.)
% ps alwx UID PID PPID STAT TTY TIME COMMAND 1000 11287 1 S tty2 0:44 xterm -sb -sl 2000 ... 1000 11289 11287 S pts/5 0:04 bash2 ... 1000 2621 11289 S pts/5 0:00 vi 0568.sgm
NOTE: A Unix system cycles its PIDs. A child process may have a lower PID than its parent! (Here, vi's PID is 2621, but its parent's PID is 11289.)
Now you need to decide what process to kill. You could simply kill them all, assuming you own them (on some systems, the xterm process may be owned by root, so you can't kill it unless you can become superuser). But a little detective work can save trouble. For instance, see whether the xterm is still alive by trying to open its menus (Section 5.17). If a menu pops up, the problem is likely with the shell (here, bash2) or its child process (here, vi). Try killing the most junior process (here, vi) first:
-9 Section 23.3
% kill 2671 % ps 2671 PID TTY STAT TIME COMMAND 2671 pts/5 S 0:00 vi 0568.sgm % kill -9 2671 %
In this case, killing the process with a plain TERM signal didn't do the job; ps showed it was still running. So I had to use kill -9. After this, if there's a shell prompt in the formerly frozen window, you're probably okay -- although you may need to reset the terminal modes if it's still acting weird. On the other hand, if the window is still frozen, kill the next-higher process -- here, bash2. Continue killing from the bottom up until the window is unfrozen or until the window closes.
24.22.2. Example #2: A Web Browser
The rule I gave in the previous section -- killing the lowest child process first -- is usually right for xterm windows, but not always right. For example, I'm using a development version of the Mozilla browser. It starts a series of child processes. But all the processes are designed to run as a unit, so killing the lowest child may just leave the browser in an unstable state. In cases like this, it's better to kill the top-level process (or one of the top, as I'll explain) and then check to be sure all the children have died.
Start with the long listing of processes. Find the parent and its children. Note that, depending on how they were started, they may not have a tty of their own -- in general, a window doesn't need a tty unless it's running a shell-oriented utility. I've cut some lines and columns from the example to make it more readable:
% ps lwx UID PID PPID STAT TTY TIME COMMAND 1000 9526 752 S tty2 0:00 sh /usr/local/mozilla/... 1000 9536 9526 S tty2 11:49 /usr/local/mozilla/... 1000 9538 9536 S tty2 0:00 /usr/local/mozilla/... 1000 9539 9538 S tty2 0:03 /usr/local/mozilla/... 1000 19843 1 S tty2 0:00 ./psm 1000 19846 19843 S tty2 0:00 ./psm 1000 19847 19846 S tty2 0:00 ./psm 1000 19858 9538 S tty2 0:00 /usr/local/mozilla/... 1000 19859 19846 S tty2 0:00 ./psm 1000 19866 19846 S tty2 0:00 ./psm 1000 32316 9538 S tty2 0:00 /usr/local/mozilla/... 1000 5705 9538 S tty2 0:00 /usr/local/mozilla/...
I started Mozilla from a menu on the window system. The window system was started from tty2 (by typing startx in the second virtual console (Section 23.12)). So the processes are "on" tty2, too. I happen to know that the ./psm processes are started by Mozilla. Although the parent psm is owned by the init (Section 24.2) process (PID 1), these were either disowned (Section 23.11) by Mozilla, or somehow the top-level psm process "lost" its parent. Finding this sort of disconnected process can be hard. One clue is that its PID is close to other Mozilla processes. Another clue may come when you use an output format like ps ux, which shows the starting time ("wall clock" time -- not the CPU TIME column above): you may see that the processes all started near the same time of day.
The first process in the list, the shell script (starting with sh), is what probably started the chain of processes running. Often, on Unix systems, a shell script sets the environment correctly, then starts another library program running. All the other processes here seem to have been started from the process with PID 9536, which has used 11 minutes 49 seconds of CPU time. Just to be safe, I'll kill both top processes at once:
% kill 9526 9536
The browser window closed, to I'm close to done. I also need to do another ps to be sure the other processes have vanished; note that they may need a few seconds to die gracefully on their own. Sometimes you'll get a zombie process (Section 24.19) that can't be killed, but it usually doesn't hurt anything -- unless your window's processes have been doing some hardware accesses and the zombie is tying up the hardware. Section 24.18 has some ways to clean up in that case.
24.22.3. Closing a Window from a Shell Script
A shell script that opens windows also may need a way to close them. The simplest way is by killing the window's process. You should be sure that whatever this process does, killing it won't cause it to leave old lock files and other "e-debris" around; it should exit cleanly when it gets a signal.
The xmessage client works well in a case like this. It opens a little window with a text message in it. If the user clicks a button in the window, xmessage terminates. But, in the example below, I want the shell script to close the window instead. Here's how it works:
Go to http://examples.oreilly.com/upt3 for more information on: nupndown
The shell script has two links (Section 10.4), or names: nup and ndown. I use them on my workstation, which no one else (usually) shares. When I run nup, the script brings the network up by dialing the modem and making a PPP connection. It also opens a red xmessage window with the message "network up" to remind me that my phone line is being tied up. When I'm done online, I run ndown. ndown disconnects the modem and closes the xmessage window by killing its process. Here's the basic script:
#!/bin/sh pidfile=/tmp/.nup-pid case "$0" in *nup) xmessage -geometry 86x51+645+72 -fg white -bg red 'network up' & echo $! > $pidfile /sbin/ifup ppp0 ;; *ndown) pid=`cat $pidfile` case "`ps $pid`" in *xmessage*) kill $pid rm -f $pidfile ;; esac /sbin/ifdown ppp0 ;; esac
When the script is invoked as nup, it starts xmessage in the background (that is, disowned (Section 23.11)) and saves its PID in the temporary file. So xmessage will keep running after nup exits; its PID will be stored in the temporary file. Later, when the same script is invoked as ndown, it reads the temporary file to get the PID into a shell variable, runs ps to be sure that the process still exists and that its name still contains xmessage (in case another process has replaced xmessage in the meantime). If all's well, it kills that process to close the xmessage window, then removes the temporary file. Finally it shuts down the network.
The actual script (on the CD-ROM [see http://examples.oreilly.com/upt3]) does more error checking: verifying you're running the X window system before starting xmessage, ensuring the temporary file exists, and more. And, of course, this isn't foolproof by any means. For instance, if I click the "OK" button on the xmessage window, it will close while the modem is still on. But none of that is the point of this simple example script. It's to demonstrate how to close a window by killing its process. For instance, maybe your script opens an xclipboard window and wants to close it later if the user doesn't do so first.
Copyright © 2003 O'Reilly & Associates. All rights reserved.