本篇文章是对 CSAPP 第 8 章异常控制流的复现记录,部分代码有笔者的更改
Linux 系统调用
int main() {
write(1, "hello, world\n", 13);
_exit(0);
}
进程控制
获取进程 ID
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main () {
printf("pid = %d\n", getpid());
printf("ppid = %d\n", getppid());
return 0;
}
创建和终止进程
#include "csapp.h"
/* $begin fork */
/* $begin wasidefork */
int main()
{
pid_t pid;
int x = 1;
pid = Fork(); //line:ecf:forkreturn
if (pid == 0) { /* Child */
printf("child : x=%d\n", ++x); //line:ecf:childprint
exit(0);
}
/* Parent */
printf("parent: x=%d\n", --x); //line:ecf:parentprint
exit(0);
}
/* $end fork */
/* $end wasidefork */
#include "csapp.h"
int main() {
Fork();
Fork();
printf("hello\n");
return 0;
}
回收子进程
/* $begin waitpid2 */
#include "csapp.h"
#define N 2
int main()
{
int status, i;
pid_t pid[N], retpid;
/* Parent creates N children */
for (i = 0; i < N; i++)
if ((pid[i] = Fork()) == 0) /* Child */ //line:ecf:waitpid2:fork
exit(100+i);
/* Parent reaps N children in order */
i = 0;
while ((retpid = waitpid(pid[i++], &status, 0)) > 0) { //line:ecf:waitpid2:waitpid
if (WIFEXITED(status))
printf("child %d terminated normally with exit status=%d\n",
retpid, WEXITSTATUS(status));
else
printf("child %d terminated abnormally\n", retpid);
}
/* The only normal termination is if there are no more children */
if (errno != ECHILD)
unix_error("waitpid error");
exit(0);
}
/* $end waitpid2 */
#include "csapp.h"
/* $begin waitprob1 */
/* $begin wasidewaitprob1 */
int main()
{
int status;
pid_t pid;
printf("Hello\n");
pid = Fork();
printf("%d\n", !pid);
if (pid != 0) {
if (waitpid(-1, &status, 0) > 0) {
if (WIFEXITED(status) != 0)
printf("WEXITSTATUS: %d\n", WEXITSTATUS(status));
}
}
printf("Bye\n");
exit(2);
}
/* $end waitprob1 */
/* $end wasidewaitprob1 */
让进程休眠
unsigned int snooze(unsigned int secs) {
unsigned int rc = sleep(secs);
printf("Slept for %d of %d secs.\n", secs-rc, secs);
return rc;
}
加载并运行程序
#include "csapp.h"
int main(int argc, char *argv[], char *envp[]) {
int i;
printf("Command-line arguments:\n");
for(i = 0; argv[i] != NULL; ++i)
printf(" argc[%2d]: %s\n", i, argv[i]);
printf("\n");
printf("Environment variables: \n");
for(i = 0; envp[i] != NULL; ++i)
printf(" envp[%2d]: %s\n", i, envp[i]);
return 0;
}
利用 fork 和 execve 运行程序
code/ecf/shellx.c
实现了一个简单的 shell,这里略去代码
信号
发送信号
/* $begin kill */
#include "csapp.h"
int main()
{
pid_t pid;
/* Child sleeps until SIGKILL signal received, then dies */
if ((pid = Fork()) == 0) {
Pause(); /* Wait for a signal to arrive */
printf("control should never reach here!\n");
exit(0);
}
/* Parent sends a SIGKILL signal to a child */
Kill(pid, SIGKILL);
exit(0);
}
/* $end kill */
上面的示例代码没有明显现象,就不放图了
接收信号
/* $begin sigint */
#include "csapp.h"
void sigint_handler(int sig) /* SIGINT handler */ //line:ecf:sigint:beginhandler
{
printf("Caught SIGINT!\n"); //line:ecf:sigint:printhandler
exit(0); //line:ecf:sigint:exithandler
} //line:ecf:sigint:endhandler
int main()
{
/* Install the SIGINT handler */
if (signal(SIGINT, sigint_handler) == SIG_ERR) //line:ecf:sigint:begininstall
unix_error("signal error"); //line:ecf:sigint:endinstall
pause(); /* Wait for the receipt of a signal */ //line:ecf:sigint:pause
return 0;
}
/* $end sigint */
既然我们已经修改了默认的 sigint_handler
,为了让收到 SIGINT
信号程序不会终止,我尝试把 exit(0)
改成 while(1)
void sigint_handler(int sig) /* SIGINT handler */ //line:ecf:sigint:beginhandler
{
printf("\rCaught SIGINT!\n"); //line:ecf:sigint:printhandler
while(1);
// exit(0); //line:ecf:sigint:exithandler
}
再尝试改成 while(1) pause();
的时候,效果实现了:
练习题 8.7
/* $begin snoozesignal */
#include "csapp.h"
/* SIGINT handler */
void handler(int sig)
{
return; /* Catch the signal and return */
}
/* $begin snooze */
unsigned int snooze(unsigned int secs) {
unsigned int rc = sleep(secs);
printf("\rSlept for %d of %d secs.\n", secs-rc, secs);
return rc;
}
/* $end snooze */
int main(int argc, char **argv) {
if (argc != 2) {
fprintf(stderr, "usage: %s <secs>\n", argv[0]);
exit(0);
}
if (signal(SIGINT, handler) == SIG_ERR) /* Install SIGINT handler */
unix_error("signal error\n");
(void)snooze(atoi(argv[1]));
exit(0);
}
/* $end snoozesignal */
编写信号处理程序
signal1.c
,存在 bug,它假设信号是会排队的
signal2.c
,正确的信号处理
修改了下 signal2.c
,看看是不是第一次触发 handler2
的时候就能回收完所有僵死进程
确实如此,代码如下
#include "csapp.h"
/* $begin signal2 */
void handler2(int sig)
{
static int cnt = 0;
int olderrno = errno;
while (waitpid(-1, NULL, 0) > 0) {
Sio_puts("Handler reaped child\n");
}
if (errno != ECHILD)
Sio_error("waitpid error");
Sio_puts("Called times: ");
Sio_putl(++cnt);
Sio_puts("\n");
Sleep(1);
errno = olderrno;
}
/* $end signal2 */
int main()
{
int i, n;
char buf[MAXBUF];
if (signal(SIGCHLD, handler2) == SIG_ERR)
unix_error("signal error");
/* Parent creates children */
for (i = 0; i < 3; i++) {
if (Fork() == 0) {
printf("Hello from child %d\n", (int)getpid());
exit(0);
}
}
/* Parent waits for terminal input and then processes it */
if ((n = read(STDIN_FILENO, buf, sizeof(buf))) < 0)
unix_error("read");
printf("Parent processing input\n");
while (1)
;
exit(0);
}
练习题 8.8
用 sigprocmask 来同步进程
在这个例子中,父进程保证在相应的 deletejob 之前执行 addjob
#include "csapp.h"
void initjobs()
{
}
void addjob(int pid)
{
}
void deletejob(int pid)
{
}
/* $begin procmask2 */
void handler(int sig)
{
int olderrno = errno;
sigset_t mask_all, prev_all;
pid_t pid;
Sigfillset(&mask_all);
while ((pid = waitpid(-1, NULL, 0)) > 0) { /* Reap a zombie child */
Sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
deletejob(pid); /* Delete the child from the job list */
Sigprocmask(SIG_SETMASK, &prev_all, NULL);
}
if (errno != ECHILD)
Sio_error("waitpid error");
errno = olderrno;
}
int main(int argc, char **argv)
{
int pid;
sigset_t mask_all, mask_one, prev_one;
Sigfillset(&mask_all);
Sigemptyset(&mask_one);
Sigaddset(&mask_one, SIGCHLD);
Signal(SIGCHLD, handler);
initjobs(); /* Initialize the job list */
while (1) {
Sigprocmask(SIG_BLOCK, &mask_one, &prev_one); /* Block SIGCHLD */
if ((pid = Fork()) == 0) { /* Child process */
Sigprocmask(SIG_SETMASK, &prev_one, NULL); /* Unblock SIGCHLD */
Execve("/bin/date", argv, NULL);
}
Sigprocmask(SIG_BLOCK, &mask_all, NULL); /* Parent process */
addjob(pid); /* Add the child to the job list */
Sigprocmask(SIG_SETMASK, &prev_one, NULL); /* Unblock SIGCHLD */
}
exit(0);
}
/* $end procmask2 */
显示等待信号
使用 sigsuspend
挂起该进程,sigsuspend
等价于下述代码的原子的(不可中断的)版本:
sigprocmask(SIG_SETMASK, &mask, &prev);
pause();
sigprocmask(SIG_SETMASK, &prev, NULL);
以下是完整代码实现:
/* $begin sigsuspend */
#include "csapp.h"
volatile sig_atomic_t pid;
void sigchld_handler(int s)
{
int olderrno = errno;
pid = Waitpid(-1, NULL, 0);
errno = olderrno;
}
void sigint_handler(int s)
{
}
int main(int argc, char **argv)
{
sigset_t mask, prev;
Signal(SIGCHLD, sigchld_handler);
Signal(SIGINT, sigint_handler);
Sigemptyset(&mask);
Sigaddset(&mask, SIGCHLD);
while (1) {
Sigprocmask(SIG_BLOCK, &mask, &prev); /* Block SIGCHLD */
if (Fork() == 0) /* Child */
exit(0);
/* Wait for SIGCHLD to be received */
pid = 0;
while (!pid)
Sigsuspend(&prev);
/* Optionally unblock SIGCHLD */
Sigprocmask(SIG_SETMASK, &prev, NULL);
/* Do some work after receiving SIGCHLD */
printf(".");
}
exit(0);
}
/* $end sigsuspend */
非本地跳转
出于某种超出本书描述范围的原因,
setjmp
返回的值不能被赋值给变量,不过它可以安全地作用在swtich
或条件语句的测试中
setjmp
只被调用一次,但返回多次(第一次调用时返回 0,后面可能多次从 longjmp
返回,返回值非 0,从下图可以发现即使 VAL = 0 也会变成 1)
非本地跳转的一个重要应用就是允许从一个深层嵌套的函数调用中立即返回,我们可以使用非本地跳转直接返回到一个普通的本地化的错误处理程序,而不是费力地解开调用栈
/* $begin setjmp */
#include "csapp.h"
jmp_buf buf;
int error1 = 0;
int error2 = 1;
void foo(void), bar(void);
int main()
{
switch(setjmp(buf)) {
case 0:
printf("case = 0\n");
foo();
break;
case 1:
printf("Detected an error1 condition in foo\n");
break;
case 2:
printf("Detected an error2 condition in foo\n");
break;
default:
printf("Unknown error condition in foo\n");
}
exit(0);
}
/* Deeply nested function foo */
void foo(void)
{
if (error1)
longjmp(buf, 1);
bar();
}
void bar(void)
{
if (error2)
longjmp(buf, 2);
}
/* $end setjmp */
sigsetjmp
和 siglongjmp
函数是 setjmp
和 longjmp
的可以被信号处理程序使用的版本。 sigsetjmp
多了一个参数 savemask
,如果非 0 则会保存 signal mask
。
通过学习了下 C 语言插入内嵌汇编的格式,我修改了一下 restart.c
,达到了如下效果:
cnt
是保存在栈上的,而 r15
则是对应的寄存器 %r15
,通过多次尝试我发现,这里所谓的保存当前环境,只会保存寄存器,不会保存栈。(我曾尝试过把 cnt
前加 register
修饰,或者加上 -O2
优化,但每次恢复后 cnt
的值总是没有恢复)
/* $begin restart */
#include "csapp.h"
sigjmp_buf buf;
void handler(int sig)
{
siglongjmp(buf, 1);
}
int main()
{
int cnt = 0, r15;
asm volatile (
"movq $0, %r15"
);
if (!sigsetjmp(buf, 1)) {
Signal(SIGINT, handler);
Sio_puts("starting\n");
} else
Sio_puts("restarting\n");
while(1) {
Sleep(1);
Sio_puts("cnt = ");
Sio_putl(++cnt);
asm volatile (
"inc %%r15 \n"
"movl %%r15d, %0 \n"
:"=r"(r15)
:
:
);
Sio_puts(" r15 = ");
Sio_putl(r15);
Sio_puts(" processing...\n");
}
exit(0); /* Control never reaches here */
}
/* $end restart */
https://www.xianwaizhiyin.net/?p=1104
https://cloud.tencent.com/developer/article/1166330
https://blog.csdn.net/zjy900507/article/details/79487605
以上是 C 语言内嵌汇编的相关参考博客,感觉没有一个写得比较全写得好的,查个格式都要看半天😓
Comments | NOTHING