cha34.进程组、会话和作业控制

读书笔记

34.1

假设一个父进程执行了下面的步骤。
这个应用程序设计可能会碰到什么问题?考虑 shell 管道。
如何避免此类问题的发生?

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

void handler(int sig){
printf("%d, received %d(%s)\n", getpid(), sig, strsignal(sig));
}

int main() {
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGUSR1);
signal(SIGUSR1, handler);
for(int i = 0; i < 10; i++) {
pid_t pid;
if((pid = fork()) == 0) {
pause();
return 0;
} else {
printf("child-%d: %d\n", i, pid);
}
}
signal(SIGUSR1, SIG_IGN);
killpg(getpgrp(), SIGUSR1);
waitpid(-1, NULL, 0);
}
  • 假设不发送SIGUSER时
    • 当不使管道时,可以看到有小于等于10个子进程显示收到SIGUSER1
    • 如果使用管道,会发现child-%d: %d被重复输出了多次,这是由于stdout被重定向后变成了全缓冲,fork的子进程退出后fflush()时会将fork前未flush的数据打印出来。
  • 假设执行该命令./practice34.1 | cat,输出会是User defined signal 1,即cat与程序同属一个进程组,cat也会收到该信号,导致无法正常执行(非预期内)

避免

记录每个子进程的pid,依次给每个pid发送信号,避免使用killpg或kill时使用负值pid

34.2

编写一个程序来验证父进程能够在子进程执行exec0之前修改子进程的进程组ID
但无法在执行exec0之后修改子进程的进程组ID。

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
42
43
44
45
46
47
48
49
50
51
52
//
// Created by root on 7/10/23.
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <errno.h>

pid_t pid;
void parent_handler(int sig) {
printf("pid = %d, recv sig-%d(%s), setpgid pid=%d, pgid=%d\n", getpid(), sig, strsignal(sig), pid, getpgid(getpid()));
if(setpgid(pid, getpgid(getpid())) == -1) {
printf("setpgid fail, %d, err:%s\n", __LINE__, strerror(errno));
}
printf("newpgid pid=%d, pgid=%d\n", pid, getpgid(pid));
kill(pid, SIGUSR1);
}

int main(int argc, char **argv) {
if(argc == 1) {
printf("before fork, pgid=%d\n", getpgid(getpid()));
if((pid = fork()) == 0) {
printf("before exec, pid = %d, gid = %d\n", getpid(), getpgrp());
fflush(stdout);
int len = snprintf(NULL, 0, "%d", getppid());
char *spid = malloc(len + 1);
sprintf(spid, "%d", getppid());
if(spid) {
fflush(stdout);
if(execl(argv[0], argv[0], spid) == -1) {
printf("execl error!\n");
}
} else {
printf("error: malloc, len = %d\n", len);
}
} else {
// 父进程先执行
signal(SIGUSR1, parent_handler);
setpgid(pid, 0);
// kill(pid, SIGUSR1);
wait(NULL);
}
} else {
printf("exec pid = %d, gid = %d\n", getpid(), getpgrp());
kill(atoi(argv[1]), SIGUSR1);
pause();
}
}

可以看到,在parent_handler中,对子进程修改pgid后再次查询其pgid,并没有改变
报错为Permission denied

34.3

编写一个程序来验证在进程组首进程中调用setsid会失败

1
2
3
4
5
6
7
8
9
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
int main() {
if(setsid() == -1) {
fprintf(stderr, "error: %s\n", strerror(errno));
}
}

结果

1
error: Operation not permitted

34.4

修改程序清单34-4 (disc_SIGHUP.c)来验证当控制进程在收到 SIGHUP 信号而不终止时,内核不会向前台进程组中的成员发送SIGHUP信号。

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
#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#define CHECK(x) do { if(!(x)) { fprintf(stderr, "CHECK: %s, %s:%d\n", strerror(errno), __FILE__, __LINE__); } } while(0)

void handler(int sig) {
printf("PID=%ld received signal: %d(%s)\n", (long) getpid(), sig, strsignal(sig));
}

int main(int argc, char **argv) {
setbuf(stdout, NULL);
if(argc < 2) {
printf("Usage: exec %s [enable-sighup|disable-sighup] [d|s]...\n", argv[0]);
}
printf("PID=%ld, PGID=%ld (parent)\n", (long)getpid(), (long) getpgrp());
if(argv[1][0] == 'd') {
CHECK(signal(SIGHUP, handler) != SIG_ERR);
}
for(int i = 2; i < argc; i++) {
pid_t pid;
CHECK((pid = fork()) != -1);
if(!pid) {
if(argv[i][0] == 'd') {
CHECK(setpgid(0, 0) != -1);
}
CHECK(signal(SIGHUP, handler) != SIG_ERR);
break;
}
}
printf("PID=%ld, PGID=%ld\n", (long)getpid(), (long) getpgrp());
alarm(60);
for(;;) pause();
getpgid(getpid());
}

小结

嗯,确实是这样

  • 复习知识
    • alarm到期发送SIGALARM,默认行为是结束进程
    • getpgrp是获取当前进程的进程组id,getpgid是获得参数pid指定进程的进程组id
    • signal函数失败时的返回值是SIG_ERR

知识补漏

  • SIGTSTPSIGSTOP
    • 两个信号的作用都是让进程暂停,区别是SIGSTOP不可以捕获。
  • waitwaitpid
    • wait当进程停止时返回
    • waitpid可通过options参数等待停止的子进程

waitflags.h中的部分宏定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* Bits in the third argument to `waitpid'.  */
#define WNOHANG 1 /* Don't block waiting. */
#define WUNTRACED 2 /* Report status of stopped children. */

/* Bits in the fourth argument to `waitid'. */
#if defined __USE_XOPEN_EXTENDED || defined __USE_XOPEN2K8
# define WSTOPPED 2 /* Report stopped child (same as WUNTRACED). */
# define WEXITED 4 /* Report dead child. */
# define WCONTINUED 8 /* Report continued child. */
# define WNOWAIT 0x01000000 /* Don't reap, just poll status. */
#endif

#define __WNOTHREAD 0x20000000 /* Don't wait on children of other threads
in this group */
#define __WALL 0x40000000 /* Wait for any child. */
#define __WCLONE 0x80000000 /* Wait for cloned process. */

等待暂停的进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <wait.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>

#define CHECK(x) do { if(!(x)) { fprintf(stderr, "CHECK: %s, %s:%d\n", strerror(errno), __FILE__, __LINE__); } } while(0)

int main() {
pid_t pid;
CHECK((pid = fork()) != -1);
if(!pid) {
// raise(SIGABRT);
raise(SIGSTOP);
} else {
int status;
// CHECK(wait(&status) != (pid_t) -1);
CHECK(waitpid(pid, &status, WUNTRACED) != (pid_t) -1);
int term_sig = WTERMSIG(status);
printf("PID=%d terminated, terminate signal=%d(%s)\n", pid, term_sig, strsignal(term_sig));
kill(pid, SIGKILL);
}
}

34.5

假设将程序清单34-6中的信号处理器中解除阻塞SIGTSTP信号的代码移动到处理器的开头部分。这样做会导致何种竞争条件?

可能会导致同时执行多个tstphandler?

34.6

编写一个程序来验证当位于孤儿进程组中的一个进程试图从控制终端调用read时会得到EIO的错误.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>

#define CHECK(file, x) do { if(!(x)) { fprintf(file, "CHECK: error:%d(%s), %s:%d\n", errno, strerror(errno), __FILE__, __LINE__); } } while(0)

int main() {
int pid = fork();
CHECK(stdout, pid != -1);
if(!pid) {
CHECK(stdout, setpgid(0, 0) == 0);
char arr[4096] = {0};
CHECK(stdout, read(STDIN_FILENO, arr, sizeof(arr)) > 0);
fprintf(stdout, "EIO=%d\n", EIO);
}
}

嗯,确实是这样

1
2
CHECK: error:5(Input/output error), /root/linux/cha34/practice34.6.c:20
EIO=5

34.7

编写一个程序来验证当SIGTTIN、SIGTTOU或SIGTSTP三个信号中的一个信号被发送给孤儿进程组中的一个成员时,如果这个信号会停止该进程(即处理方式为SIG_DFL),那么这个信号就会被丢弃(即不产生任何效果),但如果该信号存在处理器,就会发送该信号。

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
//
// Created by root on 7/13/23.
//
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <wait.h>
#include <stdlib.h>
#include <sys/shm.h>
#define CHECK(x) do { if(!(x)) { fprintf(stderr, "CHECK: error:%d(%s), %s:%d\n", errno, strerror(errno), __FILE__, __LINE__); } } while(0)
void handler(int sig) {
int saveerrno = errno;
printf("pid=%d received signal=%d(%s)\n", getpid(), sig, strsignal(sig));
CHECK(signal(sig, SIG_DFL) != SIG_ERR);
sigset_t set, old;
CHECK(sigemptyset(&set) != -1);
CHECK(sigaddset(&set, sig) != -1);
CHECK(raise(sig) != -1);
CHECK(sigprocmask(SIG_UNBLOCK, &set, &old) != -1);
printf("continue\n");
CHECK(sigprocmask(SIG_SETMASK, &old, NULL) != -1);
CHECK(signal(sig, handler) != SIG_ERR);
errno = saveerrno;
}

#define SHM_KEY (key_t)5341
int shmid;

void ps(char *argpid){
pid_t pspid;
if((pspid = fork()) == 0) {
CHECK(execlp("ps", "ps", "-f", argpid, NULL) != -1);
_exit(0);
}
CHECK(pspid != (pid_t)-1);
CHECK(waitpid(pspid, NULL, 0) != (pid_t)-1);
}

int main(int argc, char **argv) {
CHECK(argc == 3);
CHECK(argv[1][0] == 'e' || argv[1][0] == 'd');
CHECK(argv[2][0] >= '0' || argv[2][0] <= '2');
int sigs[3] = {
SIGTTIN,
SIGTTOU,
SIGTSTP
};
if(argv[1][0] == 'e') {
CHECK(signal(sigs[argv[2][0] - '0'], handler) != SIG_ERR);
}
CHECK((shmid = shmget(SHM_KEY, sizeof(pid_t), 0666|IPC_CREAT|IPC_EXCL)) != -1);

int pid = fork();
CHECK(pid != -1);
pid_t *grandson = shmat(shmid, (void*) 0, 0);
CHECK(grandson != (void *)-1);
if(!pid) {
int pid1 = fork();
CHECK(pid1 != -1);
if(!pid1) {
CHECK(setpgid(0, 0) != -1);
alarm(60);
for(;;) {
// printf("alive\n");
// sleep(1);
// pause();
}
} else {
setpgid(pid1, 0);
*grandson = pid1;
CHECK(shmdt(grandson) != -1);
_exit(0);
}
} else {
CHECK(signal(sigs[argv[2][0] - '0'], SIG_IGN) != SIG_ERR);
CHECK(wait(NULL) != (pid_t)-1);
printf("grandson = %d\n", *grandson);
char argpid[128] = {0};
sprintf(argpid, "%d", *grandson);
ps(argpid);
kill(*grandson, sigs[argv[2][0] - '0']);
ps(argpid);
kill(*grandson, SIGCONT);
ps(argpid);
kill(*grandson, SIGKILL);
ps(argpid);
CHECK(shmdt(grandson) != -1);
CHECK(shmctl(shmid, IPC_RMID, 0) != -1);
}
}

似乎还没成功,输出一直是R(运行中的后台进程组)

1
2
3
4
5
6
7
8
9
10
11
/root/linux/practice34.7 e 0
grandson = 31406
UID PID PPID C STIME TTY STAT TIME CMD
root 31406 15886 0 16:54 pts/5 R 0:00 /root/linux/practice34.7 e 0
pid=31406 received signal=21(Stopped (tty input))
continue
UID PID PPID C STIME TTY STAT TIME CMD
root 31406 15886 0 16:54 pts/5 R 0:00 /root/linux/practice34.7 e 0
UID PID PPID C STIME TTY STAT TIME CMD
root 31406 15886 0 16:54 pts/5 R 0:00 /root/linux/practice34.7 e 0
UID PID PPID C STIME TTY STAT TIME CMD

可能发现能调用handler就可以了吧?

作者

Meow Meow Liu

发布于

2023-07-10

更新于

2024-04-23

许可协议

评论