shell lab

self handin Test

원래의 방식

1
2
3
make
make test0n
make rtest0n

후 눈으로 직접 비교한다. 후반으로 갈수록 내용이 많아서 비교하기 힘들어지는데 직접 비교하는걸 sh로 만들어 비교해서 보는 방법도 있다.

1
2
3
./sdriver.pl -t trace05.txt -s ./tsh -a "-v" > a
./sdriver.pl -t trace05.txt -s ./tshref -a "-v" > b
diff a b

이럴경우 차이가 나는 부분만 보여주며 실제로 차이나는 부분은 pid값에서만 차이가 나게된다. 이 이외의 차이가 나는 부분이있다면 잘못된것 verbose까지 제대로 완성하려면 위와 같이 -v옵션을 넣어서 테스트하면 된다. v옵션까지 제대로 처리하는걸 추천. 이게 오히려 더 편함.

참고로 나는 wrapping 함수를 만들어 사용하진 않았는데, 다른곳에서 이미 적용했던 방식이기도하고(만드는데만 시간 상당히 소요함) 귀찮아서 생략함.

trace01

pass

trace02

builtin quit 명령어 구현

void eval(char *cmdline)
{
	char *argv[MAXARGS];
	const int is_back_ground = parseline(cmdline, argv);
	char *cmd = argv[0];

    builtin_cmd(argv);
}


int builtin_cmd(char **argv)
{
	if(!strcmp(argv[0], "quit"))
	{
		exit(0);
	}
	
    return 0;     /* not a builtin command */
}

03

기본적인 명령어 실행 구현

void eval(char *cmdline)
{
	char *argv[MAXARGS];
	const int is_back_ground = parseline(cmdline, argv);
	char *cmd = argv[0];

	if(!builtin_cmd(argv))
	{
        const pid_t pid = fork();
		if(pid == 0)
		{
			if(execve(cmd, argv, environ) < 0)
			{
				unix_error("execve");
				exit(1);
			}
            
		}
     	if(pid < 0)
		{
			unix_error("fork");
		}
        wait(NULL);
    }
    return ;
}

04

baground 구현 위와 차이는 bg시 wait안함. parseline 함수 주석읽어보면 bg정보를 return 값으로 알려줌 그리고 출력값보면 jobs에 process 정보 올려서 관리하는 듯하니 거기에 맞게 만들어져있는 함수들 가져다가 작성

찾다보니 wait도 waitfg란 함수에서 따로 처리하길래 글로 옮겨줌

void eval(char *cmdline)
{
	char *argv[MAXARGS];
	const int is_back_ground = parseline(cmdline, argv);
	char *cmd = argv[0];

	if(!builtin_cmd(argv))
	{
		const pid_t pid = fork();
		if(pid == 0)
		{
			if(execve(cmd, argv, environ) < 0)
			{
				unix_error("execve");
				exit(1);
			}
		}
		if(pid < 0)
		{
			unix_error("fork");
		}

		addjob(jobs, pid, !is_back_ground ? FG : BG, cmdline);

		if(!is_back_ground)
		{
			waitfg(pid);
		}
		else
		{
			printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);
		}
	}
    return;
}

05

jobs builtin 명령어 구현 listjobs란 함수가져다 쓰기만하면됨

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int builtin_cmd(char **argv)
{
	if(strcmp(argv[0], "quit") == 0)
	{
		exit(0);
	}
    else if (strcmp("jobs", argv[0]) == 0) 
    {
        listjobs(jobs);
        return 1;
    }

    return 0;
}

06

sigint 처리

void sigint_handler(int sig)
{
	const pid_t pid = fgpid(jobs);
    
    if (pid != 0)
	{
        if (kill(pid, sig) < 0)
		{
			unix_error ("kill");
		}
    }

    return;
}

handler는 이미 등록되어있으니 함수 내용만 채우면됨. 단순히 현재 돌아가고잇는 child 프로세스에 kill 보내는걸로. 그로 인해 jobs 처리를 따로해줘야한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
void eval(char *cmdline)
{
	char *argv[MAXARGS];
	const int is_back_ground = parseline(cmdline, argv);
	char *cmd = argv[0];

	if(!builtin_cmd(argv))
	{
        sigset_t mask;
	    sigemptyset(&mask);
        sigaddset(&mask, SIGCHLD);
        sigprocmask(SIG_BLOCK, &mask, NULL);

		const pid_t pid = fork();
		if(pid == 0)
		{
			if(execve(cmd, argv, environ) < 0)
			{
				unix_error("execve");
				exit(1);
			}
		}
		if(pid < 0)
		{
			unix_error("fork");
		}

		addjob(jobs, pid, !is_back_ground ? FG : BG, cmdline);
        sigprocmask(SIG_UNBLOCK, &mask, NULL);

		if(!is_back_ground)
		{
			waitfg(pid);
		}
		else
		{
			printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);
		}
	}
    return;
}

동시성 문제를 해결하기위해 (csapp 책 참고) 다음과 같이 해주고 wait를 sigchild 쪽에서 따로해줘야한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void waitfg(pid_t pid)
{
	if(pid==0)
		return;
	if(getjobpid(jobs, pid) == NULL){
		return;
	}

    while(pid != fgpid(jobs))
        ;
    return;
}

void sigchld_handler(int sig)
{
	pid_t pid;
	int status;
	while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED))>0){
		if(WIFEXITED(status)){
			deletejob(jobs,pid);
		}
		else if(WIFSTOPPED(status)){
			printf("Job [%d] (%d) stopped by signal %d\n", pid2jid(pid), pid, WSTOPSIG(status));
			getjobpid(jobs, pid)->state = ST;
		}
		else if(WIFSIGNALED(status)){
			printf("Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), pid, WTERMSIG(status));
			deletejob(jobs,pid);
		}
	}

    if(pid < 0 && errno != ECHILD) {
        unix_error("waitpid");
    }
    return;
}

07

06과 동일

08

sigtstp 처리

어느정도 처리해줬는데 출력도 제대로 나오나 종료가 되지않고있었다 그래서 -v 옵션으로 보니까 생각보다 실제 tshref와 출력부분이 죄다 빠져있다. 이부분을 차근차근 채워줬음

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void waitfg(pid_t pid)
{
	if(pid==0)
		return;
	if(getjobpid(jobs, pid) == NULL){
		return;
	}

    while (1) 
    {
        if (pid != fgpid(jobs)) 
        {
            if (verbose) 
            {
                printf("waitfg: Process (%d) no longer the fg process\n", pid);
            }
            break;
        }
    }

    return;
}

08에서 엄청해멨는데 특정 pid로 sigtstp를 날리게 kill을 했는데도 왜 백그라운드 프로세스까지 kill이 들어가는건질 모르겠음 왜 계속 process group를 썼는지 이해가 안갔는데 이걸 안쓰면 안되네 SIGINT는 process group 설정안하고 pid로 그냥 쏴도 정상적으로 수행하는데 TSTP는 -pid로 쏴야만 제대로 작동을 함

write up hint 부분 보면 해당 부분을 어떻게 처리해야하는지에 대해 적혀있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
When you implement your signal handlers, be sure to send SIGINT and SIGTSTP signals to the entire foreground process group, using ”-pid” instead of ”pid” in the argument to the kill function.
The sdriver.pl program tests for this error
...
 When you run your shell from the standard Unix shell, your shell is running in the foreground process
group. If your shell then creates a child process, by default that child will also be a member of the
foreground process group. Since typing ctrl-c sends a SIGINT to every process in the foreground
group, typing ctrl-c will send a SIGINT to your shell, as well as to every process that your shell
created, which obviously isn’t correct.
Here is the workaround: After the fork, but before the execve, the child process should call
setpgid(0, 0), which puts the child in a new process group whose group ID is identical to the
child’s PID. This ensures that there will be only one process, your shell, in the foreground process
group. When you type ctrl-c, the shell should catch the resulting SIGINT and then forward it
to the appropriate foreground job (or more precisely, the process group that contains the foreground
job).

테스트에서조차 이렇게 한다고 명시되어있음

09 ~ 16

bg구현 인자처리 좀 신경써주고 하라는대로 하면됨

void do_bgfg(char **argv) 
{
    if (argv[1] == NULL) 
    {
        printf("%s command requires PID or %%jobid argument\n", argv[0]);
        return;
    }

    const char is_initial_percent = argv[1][0] == '%';
    if (!isdigit(argv[1][0]) && !is_initial_percent)
    {
        printf("%s: argument must be a PID or %%jobid\n", argv[0]);
        return;
    }
    
    struct job_t *job;
    if (is_initial_percent) 
    { 
        if ((job = getjobjid(jobs, atoi(&argv[1][1]))) == NULL) 
        {                                 
            printf("%s: No such job\n", argv[1]);
            return;
        }
    } 
    else 
    {        
        if ((job = getjobpid(jobs, (pid_t) atoi(argv[1]))) == NULL) {                                 
            printf("(%d): No such process\n", atoi(argv[1]));
            return;
        }
    }
    
    if(strcmp(argv[0], "bg") == 0) 
    {
        job->state = BG;       
        printf("[%d] (%d) %s", job->jid, job->pid, job->cmdline);
        kill(-job->pid, SIGCONT);                              
    } 
    else 
    {
        job->state = FG;                                            
        kill(-job->pid, SIGCONT);                              
        waitfg(job->pid);                                           
    }
}
Posted 2022-04-12