When you run a program, the code that will be executed is stored in a disk file. In general, a process can't write to the memory area. The area is for holding the program code so that the code can be loaded into memory as read-only (so, it can be safely shared).
ps
command shows the processes-e
option and -f
to get full information (ps -ef
) like last week's exerciseHere is the STAT
output from ps
:
$ ps -ax
PID TTY STAT TIME COMMAND
1 ? Ss 1:48 init [3]
2 ? S< 0:03 [migration/0]
3 ? SN 0:00 [ksoftirqd/0]
....
2981 ? S<sl 10:14 auditd
2983 ? S<sl 3:43 /sbin/audispd
....
3428 ? SLs 0:00 ntpd -u ntp:ntp -p /var/run/ntpd.pid -g
3464 ? Ss 0:00 rpc.rquotad
3508 ? S< 0:00 [nfsd4]
....
3812 tty1 Ss+ 0:00 /sbin/mingetty tty1
3813 tty2 Ss+ 0:00 /sbin/mingetty tty2
3814 tty3 Ss+ 0:00 /sbin/mingetty tty3
3815 tty4 Ss+ 0:00 /sbin/mingetty tty4
.....
19874 pts/1 R+ 0:00 ps -ax
19875 pts/1 S+ 0:00 more
21033 ? Ss 0:39 crond
24765 ? Ss 0:01 /usr/sbin/httpd
init
PID TTY STAT TIME COMMAND
1 ? Ss 1:48 init [3]
init
, with process ID 1
.
init
or by other processes started by init
.init
starts the getty
program once for each terminal that we can use to log in, and it is shown in the ps
. 3812 tty1 Ss+ 0:00 /sbin/mingetty tty1
init
again...The getty
processes wait for activity at the terminal,
init
starts another getty
process.The ability to start new processes and to wait for them to finish is fundamental to the system.
You can do the same thing within your own programs with the system calls fork()
, exec()
, and wait()
.
A system call is a controlled entry point into the kernel, allowing a process to request that the kernel perform some action for the process.
Changes the processor state from user to kernel mode
Kernel has a range of services
exec()
An exec()
function replaces the current process with a new process specified by the path or file argument.
Use exec()
to hand off execution of our program to another.
|
exec()
is C/* Execute PATH with arguments ARGV and environment from `environ'. */
extern int execv (__const char *__path, char *__const __argv[])
__THROW __nonnull ((1));
/* Execute PATH with all arguments after PATH until a NULL pointer,
and the argument after that for environment. */
extern int execle (__const char *__path, __const char *__arg, ...)
__THROW __nonnull ((1));
/* Execute PATH with all arguments after PATH until
a NULL pointer and environment from `environ'. */
extern int execl (__const char *__path, __const char *__arg, ...)
__THROW __nonnull ((1));
/* Execute FILE, searching in the `PATH' environment variable if it contains
no slashes, with arguments ARGV and environment from `environ'. */
extern int execvp (__const char *__file, char *__const __argv[])
__THROW __nonnull ((1));
/* Execute FILE, searching in the `PATH' environment variable if
it contains no slashes, with all arguments after FILE until a
NULL pointer and environment from `environ'. */
extern int execlp (__const char *__file, __const char *__arg, ...)
__THROW __nonnull ((1)
execlp()
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("ps with execlp\n");
execlp("ps", "ps", 0);
printf("Done.\n");
exit(0);
}
fork()
fork()
.fork()
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUF_SIZE 150
int main()
{
int pid = fork();
char buf[BUF_SIZE];
int print_count;
switch (pid)
{
case -1:
perror("fork failed");
exit(1);
case 0:
/* When fork() returns 0, we are in the child process. */
print_count = 10;
sprintf(buf,"child process: pid = %d", pid);
break;
default: /* + */
/* When fork() returns a positive number, we are in the parent process
* (the fork return value is the PID of the newly created child process) */
print_count = 5;
sprintf(buf,"parent process: pid = %d", pid);
break;
}
for(;print_count > 0; print_count--) {
puts(buf);
sleep(1);
}
exit(0);
}
wait()
The primary role of wait()
is to synchronisation with children.
Suspends current process (the parent) until one of its children terminates.
Return value is the pid of the child process that terminated, and on a successful return, the child process is reaped by the parent.
If child_status != NULL
, the value of the status will be set to indicate why the child process terminated.
If parent process has multiple children, wait()
will return when any of the children terminates.
The waitpid()
can be used to wait on a specific child process.
wait()
#include <sys/wait.h>
pid_t wait(int *child_status);
A parent process needs to know when one of its child processes changes state, when the child terminates, hence wait()
or is stopped by a signal, SIGCHLD
.
The system call wait()
blocks the calling process until one of its child processes exits, or a signal is received.
The wait()
takes the address of an integer variable and returns the process ID of the completed process.
fork()
and wait()
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUF_SIZE 150
int main()
{
int pid = fork();
char buf[BUF_SIZE];
int print_count;
switch (pid)
{
case -1:
perror("fork failed");
exit(1);
case 0:
print_count = 10;
sprintf(buf,"child process: pid = %d", pid);
break;
default:
print_count = 5;
sprintf(buf,"parent process: pid = %d", pid);
break;
}
if(!pid) {
int status;
int pid_child = wait(&status);
}
for(;print_count > 0; print_count--) puts(buf);
exit(0);
}
_exit()
and exit()
Library callexit()
library function is layered on top of the _exit()
system call.fork()
, generally only one of the parent and child terminate by calling exit()
the other process should terminate using _exit()
Either the parent outlives the child or vice versa.
A process which has terminated, but its entry still exists in the process table until the parent terminates normally or calls wait()
.
The denotation defunct
is used to identify a zombie process.
The kernel deals with this situation by turning the child into a zombie. This means that most of the resources held by the child are released back to the system to be reused by other processes.
EXIT_ZOMBIE
and the process's parent is notified that its child process has died with the SIGCHLD
signal.wait()
system call to read the dead process's exit status and other information.wait()
is called, the zombie process is completely removed from memory.#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUF_SIZE 150
int main()
{
int pid = fork();
char buf[BUF_SIZE];
int print_count;
switch (pid)
{
case -1:
perror("fork failed");
exit(1);
case 0:
print_count = 2;
sprintf(buf,"child process: pid = %d", pid);
break;
default:
print_count = 10;
sprintf(buf,"parent process: pid = %d", pid);
break;
}
for(;print_count > 0; print_count--) {
puts(buf);
sleep(1);
}
exit(0);
}
ctrl
+c
is one event that triggers a signal if executed in the terminal to stop current process/program.SIGKILL
has signal number 9
kill -9 <somepid>
or kill SIGKILL <somepid>
SIGHUP
is used to signal that a terminal hangup has occurred, and it has a value of 1
With the exception of SIGKILL
and SIGSTOP
which always terminate the process or stops the process, respectively, processes may control what happens when they receive a signal. They can:
Signals can be:
If a process receives signals such as SIGFPE
, SIGKILL
, etc., the process will be terminated immediately, and a core dump file is created. The core file is an image of the process, and you can use it to debug.
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void my_signal_interrupt(int sig)
{
printf("I got signal %d\n", sig);
(void) signal(SIGINT, SIG_DFL);
}
int main()
{
(void) signal(SIGINT,my_signal_interrupt);
while(1) {
printf("Waiting for interruption...\n");
sleep(1);
}
}
- N = Low priority task, nice - R = Running or runnable (either executing or about to run) - S = Sleeping. Usually waiting for an event to occur, such as a signal or input to become available. - s = The process is a session leader - < High priority task - + = The process is in the foreground process group