Baeldung Pro – Linux – NPI EA (cat = Baeldung on Linux)
announcement - icon

Learn through the super-clean Baeldung Pro experience:

>> Membership and Baeldung Pro.

No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.

Partner – Orkes – NPI EA (tag=Kubernetes)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

1. Overview

We may need to check what a process is doing in Linux for several reasons. Monitoring resource management and troubleshooting are two examples.

Sometimes, a process seems unresponsive. For example, it might be stuck in an infinite loop, deadlock, or race condition. However, it may also be waiting for input from the user, disk, or network resources.

In this tutorial, we’ll discuss how to tell if a command is waiting for input.

2. Using strace

One way to check whether a process is waiting for input is to use the strace command. strace monitors the system calls of a process. Running strace requires root privileges.

To see the usage of strace, let’s open a terminal and get its process ID (PID) using echo:

$ echo $$
3363

The $$ variable is the PID of the current shell.

Let’s attach to the shell waiting for input from another terminal using strace:

$ strace -p 3363
strace: Process 3363 attached
pselect6(1, [0], NULL, NULL, NULL, {[], 8}

The -p option specifies the process PID to attach. Here, we can see that the process is waiting for input using the pselect6() system call, which allows a process to monitor multiple file descriptors and wait until an I/O operation occurs.

In our case, the second argument of pselect6() is [0], meaning that it’s monitoring only the file descriptor 0, which is the standard input.

Now, let’s send a character from the standard input by pressing a key in the waiting terminal, for example, “b“:

$ strace -p 3363
strace: Process 3363 attached
pselect6(1, [0], NULL, NULL, NULL, {[], 8}) = 1 (in [0])
read(0, "b", 1)                         = 1
select(1, [0], NULL, [0], {tv_sec=0, tv_usec=0}) = 0 (Timeout)
write(2, "b", 1)                        = 1
pselect6(1, [0], NULL, NULL, NULL, {[], 8}

Once we press the “b” key, pselect6() returns, and the shell reads the input from the standard input using the read() system call, read(0, “b”, 1). The first argument of read() is the file descriptor 0, which is the standard input, and the second parameter is the bytes that were read, which would be “b” in our case.

Then, the character that was read is written to the standard error using the write() system call, write(2, “b”, 1). The first argument of write() corresponds to the output file descriptor, which is 2 in our case, corresponding to the standard error. Consequently, we see what we type in the terminal.

Our process is single-threaded. However, we can use the -f option of strace together with the -p option to attach to all threads of a multi-threaded process.

Therefore, monitoring a process’s system calls using strace can provide information about its activities.

3. Using pstack

Another alternative to check whether a process is waiting for input is to use the pstack command. It’s useful for printing the stack trace of a running process.

We’ll inspect the behavior of a TCP server using pstack as an example. Let’s start a TCP server using the iperf tool:

$ iperf -s
------------------------------------------------------------
Server listening on TCP port 5001
TCP window size: 85.3 KByte (default)
------------------------------------------------------------

The TCP server starts to run and waits for data from a TCP client. Let’s first get the PID of this process using the ps command:

$ ps -ef | grep iperf | grep -v grep
baeldung  178322    3363  0 11:23 pts/1    00:00:00 iperf -s

The grep commands filter the output of ps to list only the iperf command. The PID of the process is 178322. Now, let’s print the stack trace of the process using pstack:

$ pstack 178322
Thread 3 (Thread 0x7f5e589dc700 (LWP 178324)):
#0  0x00007f5e595b4b07 in accept () from /lib64/libpthread.so.0
#1  0x000055acca70240c in Listener::my_accept(thread_Settings*) ()
#2  0x000055acca7030ae in Listener::Run() ()
#3  0x000055acca700577 in listener_spawn ()
#4  0x000055acca71f097 in thread_run_wrapper ()
#5  0x00007f5e595ab17a in start_thread () from /lib64/libpthread.so.0
#6  0x00007f5e592dadc3 in clone () from /lib64/libc.so.6
Thread 2 (Thread 0x7f5e591dd700 (LWP 178323)):
#0  0x00007f5e595b174a in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
#1  0x000055acca704e6e in reporter_spawn ()
#2  0x000055acca71f115 in thread_run_wrapper ()
#3  0x00007f5e595ab17a in start_thread () from /lib64/libpthread.so.0
#4  0x00007f5e592dadc3 in clone () from /lib64/libc.so.6
Thread 1 (Thread 0x7f5e5a50bb80 (LWP 178322)):
#0  0x00007f5e595b13fc in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
#1  0x000055acca71ef07 in thread_joinall ()
#2  0x000055acca6fa5e6 in main ()

This is a multi-threaded process, and it waits for incoming connections in the third thread using the accept() function. accept() is blocking and handles incoming client requests by creating a socket file descriptor.

Let’s start a TCP client in another terminal using iperf again:

$ iperf -c localhost
------------------------------------------------------------
Client connecting to localhost, TCP port 5001
TCP window size: 2.50 MByte (default)
------------------------------------------------------------
[  1] local 127.0.0.1 port 50448 connected with 127.0.0.1 port 5001

The iperf -c localhost command connects to the server running on the same machine and starts data transmission for 10 seconds by default. Let’s print the stack trace of the server during the data transfer:

$ pstack 178322
Thread 4 (Thread 0x7f5e53fff700 (LWP 179213)):
#0  0x00007f5e595b4c66 in recv () from /lib64/libpthread.so.0
#1  0x000055acca714892 in Server::RunTCP() ()
#2  0x000055acca7005ed in server_spawn ()
#3  0x000055acca71f125 in thread_run_wrapper ()
#4  0x00007f5e595ab17a in start_thread () from /lib64/libpthread.so.0
#5  0x00007f5e592dadc3 in clone () from /lib64/libc.so.6
Thread 3 (Thread 0x7f5e589dc700 (LWP 178324)):
#0  0x00007f5e595b4b07 in accept () from /lib64/libpthread.so.0
#1  0x000055acca70240c in Listener::my_accept(thread_Settings*) ()
#2  0x000055acca7030ae in Listener::Run() ()
#3  0x000055acca700577 in listener_spawn ()
#4  0x000055acca71f097 in thread_run_wrapper ()
#5  0x00007f5e595ab17a in start_thread () from /lib64/libpthread.so.0
#6  0x00007f5e592dadc3 in clone () from /lib64/libc.so.6
...

As shown in the output, the server process has spawned a fourth thread, which reads the incoming messages from the socket using the recv() function. This socket has been opened by the accept() function. The fourth thread is closed once the data transfer ends. However, the third thread continues to listen for new connections.

Therefore, printing the stack trace of a process using pstack helps determine what the threads of a process are doing.

4. Using top

The top command gives information about the resource usage of processes. Therefore, we can use it to check whether a process is busy or not.

Let’s check the status of the TCP server we’ve started before using top:

$ top -p 178322
...
   PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
178322 baeldung  20   0  239868   2064   1840 S   0.0   0.0   0:08.93 iperf
...

The -p option specifies the PID of the process we want to monitor. The output of top is refreshed every three seconds by default.

The TIME+ column in the output shows the CPU time the process has used since it started. The value in this column seems to be constant as long as a client doesn’t send data to the server.

Now, let’s start a TCP client using iperf -c localhost as before and monitor the output of top meanwhile:

$ top -p 178322
...
   PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
178322 baeldung  20   0  239736   4902   1840 S   0.0   0.0   0:18.14 iperf
...

The value in the TIME+ column increases during data transfer. Therefore, constant values in the TIME+ column might indicate a process waiting for input.

The S column in top’s output may also help detect processes waiting for input. It shows the status of processes. The “S” value (sleeping) in this column means that the process is waiting for input or resources.

Additionally, the “D” value (uninterruptible sleep) in this column implies that the process is waiting for I/O like disk or network input.

Notably, the values top displays are summations of all threads of a process. However, while top is running, it’s possible to list the individual threads of a process by pressing “H“. This is an interactive option in the top command that toggles between showing processes and showing threads.

We can also use htop instead of top. Its usage is similar. It lists the resource usage of each thread of an application separately by default.

5. Conclusion

In this article, we discussed how to tell if a command is waiting for input.

First, we saw that we can monitor the system calls of a process using strace. Then, we explored the pstack command and learned that the stack trace of a process gives an idea about what the process is doing.

Finally, we saw that the TIME+ and S columns in top‘s output can indicate whether a process is waiting for input.