Next we will introduce the second part of this section. It will reflect the different influ- ences of the signal on the execution state of the process by comparing two processes with
“interruptible state” and “uninterruptible state” respectively.
The case with interruptible state as follows:
After the process shell creates this user process (this process will naturally become the child of the process shell), then it is set to the interruptible state. Now, the user process will exit, so we use that as an example to introduce the influence of signal to the process execution state.
User process exits and sends signals to the process shell.
The user process calls the exit() function to deal with some affairs first before exit- ing, including releasing the memory page that its own program occupied, removing the relationship between the process and the file on which the process operates, and so on.
Then send the “child processes exit” signal to the process shell, informing the process shell that you are going to exit, and set yourself to zombie state and call the schedule() function finally, preparing the process switching. The corresponding code is as follows:
#include <stdio.h>
main() {
exit();
}
//code source:kernel/exit.c:
int do_exit(long code) //child processes exit {
……
if (current->leader) kill_session();
current->state = TASK_ZOMBIE;
current->exit_code = code;
tell_father(current->father); //send signal to father process schedule(); //process switching
return (-1); /* just to suppress warnings */
}
The procedure of sending signals in the user’s exit process and setting itself to zombie state are shown in Figure 8.29.
Process shell is waken up and switching to execute. After entering the schedule() function, traversal all processes for the first time first. If there is a process that has received specific signals and the process is in the interruptible state, set the process to the ready state. The system can get that process shell to comply with this condition through traversalling, and the process shell is set to the ready state. It is shown in Figure 8.30.
kernel
ROM BIOS and VGA
0x00000 0x9FFFF 0xFFFFF 0x3FFFFF 0x5FFFFF 0xFFFFFF
task_struct of shell process
The page where task_struct of
shell process resides Step 1:
pages which user process occupied are released
Step 2:
sending signal to shell process Process status
User process Shell process
Zombie Interruptible
Current process Step 3:
the state of user process is set as zombie Figure 8.29 User process sends signals to the process shell.
static void tell_father(int pid) {
int i;
if (pid)
for (i = 0;i<NR_TASKS;i++) { // seek father process, that’s, //process shell
if (!task[i]) continue;
if (task[i]->pid ! = pid) continue;
task[i]->signal | = (1<<(SIGCHLD-1)); // send “child processes exit”
//signal to process shell return;
}
……
}
The corresponding code is shown as follows:
Then, traversal all processes for the second time. There is only the process shell in the ready state, so we switch to the process shell to execute. The execution code is shown as follows:
ROM BIOS and VGA
kernel
0x00000 0x9FFFF 0xFFFFF 0x3FFFFF 0x5FFFFF 0xFFFFFF
Process status
User process Shell process
Zombie Ready
Current process
Figure 8.30 Process shell is set to ready state.
//code source:kernel/sched.c:
void schedule(void) {
……
for(p = &LAST_TASK ; p > &FIRST_TASK ;-- p) if (*p) {
if ((*p)->alarm && (*p)->alarm < jiffies) { (*p)->signal | = (1<<(SIGALRM-1));
(*p)->alarm = 0;
}
if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) && // Check whether the process // received signal
(*p)->state = =TASK_INTERRUPTIBLE) // Check whether the process is // can interrupt wait state (*p)->state = TASK_RUNNING; // If the conditions are
// satisfied at the same // time, set the process // to ready state }
……
}
//code source:kernel/sched.c:
void schedule(void) {
……
while (1) { c = -1;
next = 0;
i = NR_TASKS;
p = &task[NR_TASKS];
while (-- i) { if (!*-- p)
continue;
if ((*p)->state = = TASK_RUNNING && (*p)->counter > c) c = (*p)->counter, next = i;
}
if (c) break;
for(p = &LAST_TASK ; p > &FIRST_TASK ;-- p) if (*p)
(*p)->counter = ((*p)->counter >> 1) + (*p)->priority;
}
switch_to(next); //switch to process shell to execute }
Process shell execute, final processing for the exit of child process. After the process shell start, call the wait() function for the child process exit, including releasing the page that the task_struct of child process occupied, etc. It is shown in Figure 8.31.
The code is as follows:
kernel
0x00000 0x9FFFF 0xFFFFF 0x3FFFFF 0x5FFFFF 0xFFFFFF
Process status
ROM BIOS and VGA
Shell process Ready
Page occupied by the task_struct of user process is released
Current process
Figure 8.31 Process shell handling the rehabilitation work after user process exit.
//code source:kernel/exit.c:
int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options) {
……
repeat:
switch ((*p)->state) { case TASK_STOPPED:
if (!(options & WUNTRACED)) continue;
put_fs_long(0x7f,stat_addr);
return (*p)->pid;
case TASK_ZOMBIE: //detect the child processes is
//zombie state, do as follows
current->cutime + = (*p)->utime;
current->cstime + = (*p)->stime;
flag = (*p)->pid;
code = (*p)->exit_code;
release(*p);
put_fs_long(code,stat_addr);
return flag;
default:
flag = 1;
continue;
} if (flag) {
if (options & WNOHANG) return 0;
current->state = TASK_INTERRUPTIBLE;
schedule();
if (!(current->signal & = ~(1<<(SIGCHLD-1)))) //get that the signal received is //child processes exit signal goto repeat;
else
return -EINTR;
}
return -ECHILD;
}
Process shell is suspended again. Then the process shell continues executing, reading data from the terminal device file tty0. We assume that the user doesn’t input any infor- mation through the keyboard at this time, so the process shell doesn’t read any data, and so the process shell will be set to the interruptible state, waiting for the next waken up. It is shown in Figure 8.32.
Thus, to the process with interruptible state, if sends signal to it, when the schedule() function executes, the signal it received and its state will be detected, and its state will be changed to the ready state and then wake up the process.
Next, we will introduce the uninterruptible state of the process. We assume that a system has three user processes now, and they are, respectively, process A, process B, and process C, and they are in ready state now. Process B is the child process of process A, and process A is running. We take this scene as an example to introduce the influence of the signal to the state of process execution.
Process A and process B case program:
Process C case program:
kernel
ROM BIOS and VGA
0x00000 0x9FFFF 0xFFFFF 0x3FFFFF 0x5FFFFF 0xFFFFFF
Process status
Shell process Interruptible Current process
Figure 8.32 Process shell gets into interruptible state again.
main() {
char buffer[12000];
int pid,i;
int fd = open(“/mnt/user/hello.txt”, O_RDWR,0644));
read(fd,buffer,sizeof(buffer)); //read file if (!(pid = fork())) {
exit(); //code of process B(child process) }
if (pid>0)
while (pid ! = wait(&i) //wait for the child process exit close(fd);
return;
}
main() {
int i,j;
for(i = 0;i<1000000;i++) for(i = 0;i<1000000;i++) }
Process A is suspended due to the wait for read disk. Process A needs to read data from hard disk. So it calls the read() function to trigger a soft interrupt, and finally map to the sys_read() function to execute. After a series of function calls, send the read disk com- mand. After it returns, process A is set to the uninterruptible state. This is because the next execution of process A needs the support of the data read out from the disk. Before the data was read, no matter what signal this process has received, it cannot be waken up.
If it was waken up, it will operate the data in the buffer, but at this time, the data in the buffer has not been read from the hard disk, so this will cause data chaos. It is shown in Figure 8.33.
Process A executed and was finally suspended. The corresponding code is shown below:
kernel
ROM BIOS and VGA
0x00000 0x9FFFF 0xFFFFF 0x3FFFFF 0x5FFFFF 0xFFFFFF
Buffer block
Disk Process status
Process C Process B Process A
Ready Ready Uninterruptible
Current process
Figure 8.33 Process A is suspended.
//code source:fs/buffer.c:
struct buffer_head * bread(int dev,int block) {
……
if (bh->b_uptodate) return bh;
ll_rw_block(READ,bh);
wait_on_buffer(bh); //Test whether need to wait until buffer //block unlock
if (bh->b_uptodate) return bh;
……
}
static inline void wait_on_buffer(struct buffer_head * bh) {
cli();
while (bh->b_lock) //Buffer block has really been lock sleep_on(&bh->b_wait); //Have to suspend process A sti();
}
Switch process A to process B to execute. It also needs to call schedule() later, and finally switches to the other process to execute. Assume that switch to the child process of process A, namely process B, to execute. Process B was executed and ready to exit, so process B is set to zombie state and then sends a signal to process A, informing process A that it would exit and finally call the schedule() function, preparing process switching. It is shown in Figure 8.34.
Execution code is as follows:
ROM BIOS and VGA
kernel
0x00000 0x9FFFF 0xFFFFF 0x3FFFFF 0x5FFFFF 0xFFFFFF
Some pages of process B are released
task_struct of process A
The page where task_struct of process A resides
Sending signal to process A Buffer block
Disk Process status
Process C Process B Process A
Ready Zombie state Uninterruptible
Current process
Figure 8.34 Process B exit and send signal to process A.
//code source:kernel/sched.c:
void sleep_on(struct task_struct **p) {
……
tmp = *p;
*p = current;
current->state = TASK_UNINTERRUPTIBLE; // set process A to uninterruptible state schedule();
if (tmp)
tmp->state = 0;
}
//code source:kernel/exit.c:
int do_exit(long code) //process B exit
{
……
if (current->leader) kill_session();
current->state = TASK_ZOMBIE;
current->exit_code = code;
Although a signal has been received by process A, it can’t be waken up. After entering the schedule() function, traversal all processes for the first time. Although process A has received a signal at this time, because it was uninterruptible state, it would not be set to ready state, and we have to switch to process C to execute (Figure 8.35).
Execution code is shown below:
ROM BIOS and VGA
kernel
0x00000 0x9FFFF 0xFFFFF 0x3FFFFF 0x5FFFFF 0xFFFFFF
Buffer block
Disk Process status
Process C Process B Process A
Ready Zombie Uninterruptible
Current process
Figure 8.35 For the uninterruptible state, it can’t use the signal to wake up.
tell_father(current->father); //send signal to parent process
schedule(); //process switch
return (-1); /* just to suppress warnings */
}
static void tell_father(int pid) {
int i;
if (pid)
for (i = 0;i<NR_TASKS;i++) { //seek parent process, namely, process A if (!task[i])
continue;
if (task[i]->pid ! = pid) continue;
task[i]->signal | = (1<<(SIGCHLD-1)); //send process A “child process exit” signal return;
}
……
}
//code source:kernel/sched.c:
void schedule(void) {
……
for(p = &LAST_TASK ; p > &FIRST_TASK ;-- p) if (*p) {
if ((*p)->alarm && (*p)->alarm < jiffies) {
Because peripheral data has been read completely, process A was waken up. After pro- cess C executes for a period of time, the data process A specified has been read from the hard disk, so the hard disk interrupt service routine will set process A to the ready state forcibly. (This is also the only way to set the process in the uninterruptible state to the ready state.) It is shown in the bottom right of Figure 8.36, and the execution code is shown below:
So process A has executive ability, but this is not equal to process A executing imme- diately. After the hard disk interrupt routine return, it is still process C to continue execut- ing as shown in the left bottom of Figure 8.36.
Switch to process A to execute and process the signal. The time slice of process C was used up and switched the process. After entering into the schedule() function here, it is found that only process A is in ready state, so switch to process A to execute. As process A was started, process the data that was read out from the hard disk just now first; thus, the sys_read() execution has completed. At this time, soft interrupt is ready to return, and before it returns, it will check whether process A received any signal first. Sure enough, it has detected that process A received a signal, so process the entry address of the process- ing signal service handler, so that once the soft interrupt returns, the signal processing handler will process the signal as shown in Figure 8.37.
//code path:kernel/blk_dev/blk.h:
extern inline void end_request(int uptodate) {
DEVICE_OFF(CURRENT->dev);
if (CURRENT->bh) {
CURRENT->bh->b_uptodate = uptodate;
unlock_buffer(CURRENT->bh); //buffer block release }
if (!uptodate) {
printk(DEVICE_NAME “ I/O error\n\r”);
printk(“dev%04x, block%d\n\r”,CURRENT->dev, CURRENT->bh->b_blocknr);
}
……
}
extern inline void unlock_buffer(struct buffer_head * bh) {
if (!bh->b_lock)
printk(DEVICE_NAME “: free buffer being unlocked\n”);
bh->b_lock = 0;
wake_up(&bh->b_wait); //waken up the process that wait buffer block //release, namely, waken up process A }
(*p)->signal | = (1<<(SIGALRM-1));
(*p)->alarm = 0;
}
if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) && //detect process A has //really received the signal (*p)->state = =TASK_INTERRUPTIBLE) //process A is uninterruptible state
(*p)->state = TASK_RUNNING; //won’t execute here }
……
}
ROM BIOS and VGA
kernel
0x00000 0x9FFFF 0xFFFFF 0x3FFFFF 0x5FFFFF 0xFFFFFF
Buffer block
Disk Process status
Process C Process B Process A
Ready Zombie Ready
Current process
Figure 8.36 Process A is waken up.
kernel
ROM BIOS and VGA
0x00000 0x9FFFF 0xFFFFF 0x3FFFFF 0x5FFFFF 0xFFFFFF
Page occupied by the task_struct of process B is released Buffer block
Disk Process status
Process C Process B Process A
Time slice reduces to
zero Ready Zombie Ready
Current process
Figure 8.37 Process A execute and process the signal.
Thus it can be seen that for the process in the uninterruptible state, unless it is set to ready state directly, there is no other way to change its state to a ready state, and whether it has received a signal is meaningless.