20145201《信息安全系统设计基础》第11周学习总结
20145201 《信息安全系统设计基础》第11周学习总结
教材学习内容总结
第8章异常控制流
控制流:控制转移序列。
控制转移:从一条指令到下一条指令。
异常控制流:现代操作系统通过使控制流发生突变来对系统状态做出反应,这些突变称为异常控制流。
硬件的一个系统状态的改变,会触发控制转移到异常处理程序。(程序状态变化时,会触发控制转移到该去的地方)内核状态的变化,会触发控制从一个用户进程转移到另一个用户进程。应用层状态的变化,会让一个用户进程将控制转移到其信号处理程序中。
所以,异常控制流,讲的是控制各种状态;状态变化了,肯定有一定的方法可以检测,检测之后,就对应的转移控制。其实就是设置了一些条件,如果条件触发了,就做一些事情。核心就是这样的。
现在有两个问题:系统状态是我设置的条件,我如何检测条件是否触发了;对各种系统状态的触发,结果是什么?
ECF是操作系统用来实现I/O、进程和虚拟存储器的基本机制。
系统调用是一种ECF,我在程序中调用系统函数,就是一个触发,然后系统给出反应,操作系统为应用程序提供了强大的ECF机制,用来创建新进程等。
ECF是系统的基础,系统中能称为触发的很多,或者说绝大多数的操作都可以称为触发,也就是变化系统状态,也就是ECF的一种表现,ECF无处不在。操作系统也就是一个各种触发和反应的集合体。
8.1 异常
异常是ECF的一种,一部分由硬件实现,一部分由操作系统实现。就是位于硬件和操作系统之间的ECF。
硬件上,系统状态实际是处理器的状态,处理器的状态通常就是不同的位和信号(寄存器的位),处理器状态的变化(比如说某个bit置一)称为事件。
事件可能和当前指令的执行相关。比如当前指令执行中产生了溢出。
事件也可能和当前指令没有关系,比如系统定时器产生信号,或者,一个I/O请求完成。
1、异常处理
异常表:当处理器检测到有事件发生时,它会通过跳转表,进行一个间接过程调用(异常),到异常处理程序。
异常号:系统中可能的某种类型的异常都分配了一个唯一的非负整数的异常号。异常号是到异常表中的索引。
关系:
异常号是到异常表中的索引,异常表的起始地址放在异常表基址寄存器。
异常类似于过程调用,但有一些重要的不同之处。
1.处理器压入栈的返回地址,是当前指令地址或者下一条指令地址。
2.处理器也把一些额外的处理器状态压到栈里
3.如果控制一个用户程序到内核,所有项目都压到内核栈里。
4.异常处理程序运行在内核模式下,对所有的系统资源都有完全的访问权限。
一旦硬件触发了异常,异常处理程序则由软件完成。
2、异常的类别——中断、陷阱、故障和终止
a)中断处理:异步是指硬件中断不是由任何一条指令造成的,而是由外部I/O设备的事件造成的。
b)陷阱和系统调用:系统调用是一些封装好的函数,内部通过指令int n实现。
陷阱最重要的用途是提供系统调用。系统调用运行在内核模式中,并且可以访问内核中的栈。
系统调用的参数是通过通用寄存器而不是栈来传递的,如,%eax存储系统调用号,%ebx,%ecx,%edx,%esi,%edi,%ebp最多存储六个参数,%esp不能用,因为进入内核模式后,会覆盖掉它。
c)故障
d)终止
二、进程
(操作系统层):逻辑控制流,私有地址空间,多任务,并发,并行,上下文,上下文切换,调度。
进程就是一个执行中的程序实例。系统中的每个程序都是运行在某个进程的上下文中的。
进程提供给应用程序的关键抽象:
- 一个独立的逻辑控制流 类似独占的使用处理器
- 一个私有的地址空间 类似独占的使用存储器系统
1、逻辑控制流
程序计数器(PC)值的序列叫做逻辑控制流,简称逻辑流。如下图所示,处理器的一个物理控制流分成了三个逻辑流,每个进程一个。
并发流:并发流一个逻辑流的执行在时间上与另一个流重叠。
并发:多个流并发执行的一般现象称为并发。
多任务:多个进程并发叫做多任务。
并行:并发流在不同的cpu或计算机上。
2、私有地址空间
一个进程为每个程序提供它自己的私有地址空间。
运行应用程序代码的进程初始时是在用户模式中的。进程从用户模式变为内核模式的唯一方法是通过异常。
linux提供了/proc文件系统,它允许用户模式进程访问内核数据结构的内容。
3、私有地址空间
每个私有地址空间都关联着一段真实的存储器空间,一般来说,一般的理解,这些对应着的真实的存储器空间应该是相互都错开的。但每个这样的空间都有相同的通用结构。
- 运行应用程序的进程初始时是在用户模式中的。
- 进程从用户模式变为内核模式的唯一方法是通过诸如中断、故障、陷阱这样的异常。
- 当异常发生时,控制转移到异常处理程序,处理器将模式从用户模式变为内核模式。当它返回到应用程序代码时,处理器就把模式从内核模式改回到用户模式。
4、上下文切换,调度
上下文切换:操作系统内核使用叫上下文切换的异常控制流来实现多任务。
上下文切换:
- 保存当前进程的上下文;
- 恢复某个先前被抢占的进程被保存的上下文;
- 将控制传递给这个新恢复的进程
调度:内核中的调度器实现调度。
当内核代表用户执行上下文切换时,可能会发生上下文切换。如果系统调用发生阻塞,那么内核可以让当前进程休眠,切换到另一个进程,如read系统调用,或者sleep会显示地请求让调用进程休眠。一般,即使系统调用没有阻塞,内核亦可以决定上下文切换,而不是将控制返回给调用进程。
中断也可能引起上下文切换。如,定时器中断。
三、系统调用错误
当UNIX系统级函数遇到错误时,它们典型地会返回-1,并设置全局整数变量errno来表示什么出错了。
四、进程控制
1、获取进程ID
每个进程都有一个唯一的正数进程ID(PID)。
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void); 返回调用进程的PID
pid_t getppid(void); 返回父进程的PID(创建调用进程的进程)
2、创建和终止进程
从程序员的角度,我们可以认为进程总处于下面三种状态之一:
运行——在cpu上运行,或者,等待运行且最终会运行(会被内核调度)
停止——进程被挂起(也就是被其他的进程抢占了),且不会被调度,但可以被信号唤醒
终止——进程被永远的停止了,受到终止信号,或者从主程序返回,或者调用exit函数。
父进程通过调用fork创建一个新的运行子进程:父进程与子进程有相同(但是独立的)地址空间
fork函数定义如下:
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
- 调用一次,返回两次。父进程返回子进程的PID,子进程返回0.如果失败返回-1.
- 父进程和子进程是独立的进程,并发执行的。一般而言,作为程序员,我们决不能对不同的进程中指令的交替执行做任何假设。
- 相同的但是独立的地址空间。
- 共享文件。
调用fork函数n次,产生2^n次方个进程
3、回收子进程
-
当一个进程由于某种原因终止时,内核并不是立即把它从系统中清除。相反,进程被保持在一种已终止的状态中。直到被它的父进程回收。
-
当父进程回收已终止的子进程时,内核将子进程的退出状态传递给父进程。然后抛弃已终止的进程。
-
一个终止了但是还未被回收的进程称为僵死进程。
-
如果父进程没有回收它的僵死子进程就终止了,那么内核就会安排另外一个进程来回收它们,这个进程是init。其pid为1。其在系统初始化时由内核创建。
-
一个进程可以通过调用waitpid函数来等待它的子进程终止或者停止。
waitpid函数定义如下:
这个函数有三个参数:
pid
status
options
当options=0时,调用这个函数的进程挂起,也就是这个进程处于函数中什么也不做,等待着,等待什么呢,等待其子进程终止,如果终止了一个,那么函数就返回了,返回的,就是终止的子进程的pid,并且将这个子进程从系统中除去。
-
等待的子进程有哪些?
这点由pid决定:pid=-1,那么就是所有的子进程
pid>0,那么就是一个子进程,当前pid表示的那个子进程。 -
修改默认行为
options=WNOHANG时,如果没有终止的子进程,那么函数立即返回,返回0。
options=WUNTRACED时,和options=0类似,但这里还检测停止的子进程。
options=0只检测终止的子进程。且,本options不会将子进程从系统中除去。
options=WNOHANG|WNUNTRACED时,立即返回,返回值要么是停止或者终止的pid,要么是0。 -
条件错误
如果调用进程没有子进程,那么waitpid返回-1,并设置errno为ECHLILD;如果waitpid函数被一个信号中断,那么它返回-1,并设置errno为EINTR。 -
wait函数:
wait函数是waitpid函数的简单版本
wait(&status)函数,等价于调用wait(-1,&status,0)
4、让进程休眠:
sleep函数将一个进程挂起一段指定的时间。
- sleep函数定义:
#include <unistd.h>
unsigned int sleep(unsigned int secs);
返回值是剩下还要休眠的秒数,如果请求时间量到了返回0。
- pause函数让调用函数休眠,知道该进程收到一个信号。
5、加载并运行程序
execve函数在当前进程的上下文中加载并运行一个新程序。
#include <unistd.h>
int execve(const char *filename, const char *argv[], const char *envp[]);
成功不返回,错误返回-1。
execve函数调用一次,从不返回。
解释:
filename:可执行目标文件
argv:参数列表
envp:环境列表
新程序开始时:
fork函数和execve函数的区别:
fork函数在新的子进程中运行相同的程序,新的子进程是父进程的一个复制品。
execve函数在当前进程的上下文中加载并运行一个新的程序,它会覆盖当前进程的地址空间,但并没有创建一个新进程。
新的程序仍然有相同的pid,并且继承了调用execve函数时已打开的所有文件描述符。
五、信号
Unix信号是一种更高层次的软件形式的异常。
linux支持30种不同类型的异常。
每种信号类型都对应于某种系统事件。底层的硬件异常是由内核异常处理程序处理的,正常情况下,对用户进程而言是不可见的。信号提供了一种机制,通知用户进程发生了这些异常。
1、信号术语
传递一个信号到目的进程的两个步骤
发送信号——内核通过更新目的进程上下文中的某个状态,告诉目的进程,有一个信号来了。
接收信号——当目的进程被内核强迫以某种方式对信号的发送做出反应时,目的进程就接收了信号。
进程可以忽略、终止、捕获。
2、发送信号的方式
/bin/kill
键盘发送信号
kill函数
alarm函数
3、接收信号的默认行为
- 进程终止
- 进程终止并转储存储器
- 进程停止直到被SIGCONT信号重启
- 进程忽略该信号。
但接受信号的行为可以不是默认行为,通过设置信号处理程序。
4、信号处理问题
当一个程序要捕获多个信号时,一些细微的问题就产生了。
- 待处理信号被阻塞——Unix信号处理程序通常会阻塞当前处理程序正在处理的类型的待处理信号。
- 待处理信号不会排队等待——任意类型至多只有一个待处理信号。
- 系统调用可以被中断——像read、wait、accept这样的系统调用潜在的会阻塞进程一段较长的时间,称为慢速系统调用。
-
父进程有一套自己的信号处理程序,放在那里,然后父进程用了一个read函数,这个函数会执行一段时间,在执行的这段时间里,来了一个信号,所以控制转移到了信号处理程序,也就是说控制从read函数里跳到了信号处理程序。当处理程序处理完毕了,其返回,在有的系统中,返回了,但read函数的调用不再继续了,而是直接返回给用户一个错误。当然在其他的系统中,比如linux,还是会返回到read函数继续执行。
-
父子进程的,对于任意一个进程,单独的进程,其只有一份控制,这份控制可以交给陷阱(系统调用),也可以交给信号处理程序,但貌似,信号处理程序有更高的优先级,当信号来的时候,如果不被阻塞,那么立刻的就跳到了信号处理程序中处理,而不管当前的控制是否在main中还是是否在系统调用也就是内核中。更有一点,就是信号处理程序处理完了返回时,系统调用是否恢复是因系统而异的。
同时,父子进程之间存在这一种竞争关系,一般是:父子之间相互发信号。
6 非本地跳转
c语言提供一种用户级异常控制流形式——非本地跳转。
- setjump函数在env缓冲区中保存当前调用环境,以供后面longjmp使用,并返回零。
调用环境:程序计数器,栈指针,通用目的寄存器
- longjmp函数从env缓冲区中恢复调用环境,然后触发一个从最近一次初始化env的setjmp调用的返回。然后setjmp返回,并带有非零的返回值retval。
setjump函数与longjmp函数区别:
setjmp函数只被调用一次,但返回多次
longjmp函数被调用一次,但从不返回。
7、操作进程的工具
STRACE:打印一个正在运行的程序和他的子程序调用的每个系统调用的痕迹
PS:列出当前系统中的进程,包括僵死进程
TOP:打印出关于当前进程资源使用的信息
PMAP:显示进程的存储器映射
/proc
8、小结
- 硬件层——四种不同类型的异常:中断、故障、终止、陷阱。
- 操作系统层——内核用ECF提供进程的基本概念。
- 在操作系统和应用程序的接口处——信号
- 应用层——非本地跳转
process实践部分
1、argtest.c
#include <stdio.h>
#include <stdlib.h>
#include "argv.h"//该函数库中包括freemakeargv.c及makeargv.c函数的调用
int main(int argc, char *argv[])
{
char delim[] = " \t";//制表符
int i;
char **myargv;//见下方解释
int numtokens;
if (argc != 2)//如果输入的命令字符个数不等于2,就输出标准错误
{
fprintf(stderr, "Usage: %s string\n", argv[0]);
return 1;
}
if ((numtokens = makeargv(argv[1], delim, &myargv)) == -1)
{
fprintf(stderr, "Failed to construct an argument array for %s\n", argv[1]);//意思是无法构造一个参数数组
return 1;
}
printf("The argument array contains:\n");
for (i = 0; i < numtokens; i++)
printf("%d:%s\n", i, myargv[i]);
execvp(myargv[0], myargv);
return 0;
}
2、environ.c
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
printf("PATH=%s\n", getenv("PATH"));//getenv函数用来取得参数PATH环境变量的值,执行成功则返回该内容的指针
setenv("PATH", "hello", 1);/*setenv用来在本次函数运行的过程中增加或者修改环境变量。当最后一个参数不为0的时候,原来的内容会被修改为第二个参数所指的内容。*/
printf("PATH=%s\n", getenv("PATH"));
#if 0
printf("PATH=%s\n", getenv("PATH"));
setenv("PATH", "hellohello", 0);
printf("PATH=%s\n", getenv("PATH"));
printf("MY_VER=%s\n", getenv("MY_VER"));//版本
setenv("MY_VER", "1.1", 0);
printf("MY_VER=%s\n", getenv("MY_VER"));
#endif
return 0;
}
3、environvar.c
#include <stdio.h>
int main(void)
{
extern char **environ;
int i;
for(i = 0; environ[i] != NULL; i++)/*该变量指向一个叫“environment”的字符串数组。包括USER(登录用户的名字),LOGNAME(与user类似),HOME(用户登录目录),LANG(地域名),PATH等*/
printf("%s\n", environ[i]);
return 0;
}
4、consumer.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#define FIFO_NAME "/tmp/myfifo"
#define BUFFER_SIZE PIPE_BUF
int main()
{
int pipe_fd;
int res;
int open_mode = O_RDONLY;
char buffer[BUFFER_SIZE + 1];
int bytes = 0;
memset(buffer, 0, sizeof(buffer));
printf("Process %d opeining FIFO O_RDONLY \n", getpid());
pipe_fd = open(FIFO_NAME, open_mode);
printf("Process %d result %d\n", getpid(), pipe_fd);
if (pipe_fd != -1) {
do {
res = read(pipe_fd, buffer, BUFFER_SIZE);
bytes += res;
} while (res > 0);
close(pipe_fd);
} else {
exit(EXIT_FAILURE);
}
printf("Process %d finished, %d bytes read\n", getpid(), bytes);
exit(EXIT_SUCCESS);
}
5、producer
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#define FIFO_NAME "/tmp/myfifo"
#define BUFFER_SIZE PIPE_BUF
#define TEN_MEG (1024 * 1024 * 10)
int main()
{
int pipe_fd;
int res;
int open_mode = O_WRONLY;
int bytes = 0;
char buffer[BUFFER_SIZE + 1];
if (access(FIFO_NAME, F_OK) == -1) {//检查文件是否有相应的权限
res = mkfifo(FIFO_NAME, 0777);//依据FIFO_NAME创建fifo文件,0777依次是相应权限
if (res != 0) {
fprintf(stderr, "Could not create fifo %s \n",
FIFO_NAME);
exit(EXIT_FAILURE);
}
}
printf("Process %d opening FIFO O_WRONLY\n", getpid());
pipe_fd = open(FIFO_NAME, open_mode);
printf("Process %d result %d\n", getpid(), pipe_fd);
if (pipe_fd != -1) {
while (bytes < TEN_MEG) {
res = write(pipe_fd, buffer, BUFFER_SIZE);
if (res == -1) {
fprintf(stderr, "Write error on pipe\n");
exit(EXIT_FAILURE);
}
bytes += res;
}
close(pipe_fd);
} else {
exit(EXIT_FAILURE);
}
printf("Process %d finish\n", getpid());
exit(EXIT_SUCCESS);
}
6、testmf.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()//就是创建fifo文件
{
int res = mkfifo("/tmp/myfifo", 0777);
if (res == 0) {
printf("FIFO created \n");
}
exit(EXIT_SUCCESS);
}
7、pipe.c
#include <stdio.h>
#include<stdlib.h>
#include <unistd.h>
#define oops(m,x) //当linux系统执行代码遇到问题时,就会报告错误
{ perror(m); exit(x); }
int main(int ac, char **av)
{
int thepipe[2], newfd,pid;
if ( ac != 3 ){//输入的命令长度不等于3
fprintf(stderr, "usage: pipe cmd1 cmd2\n");
exit(1);
}
if ( pipe( thepipe ) == -1 ) //以下是各种错误
oops("Cannot get a pipe", 1);
if ( (pid = fork()) == -1 )
oops("Cannot fork", 2);
if ( pid > 0 ){
close(thepipe[1]);
if ( dup2(thepipe[0], 0) == -1 )
oops("could not redirect stdin",3);
close(thepipe[0]);
execlp( av[2], av[2], NULL);
oops(av[2], 4);
}
close(thepipe[0]);
if ( dup2(thepipe[1], 1) == -1 )
oops("could not redirect stdout", 4);
close(thepipe[1]);
execlp( av[1], av[1], NULL);
oops(av[1], 5);
}
8、stdinredir1.c
#include <stdio.h>
#include <fcntl.h>
int main()
{
int fd ;
char line[100];
fgets( line, 100, stdin ); printf("%s", line );//打印输入的字符串
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
close(0);
fd = open("/etc/passwd", O_RDONLY);
if ( fd != 0 ){//打开/创建失败的时候执行
fprintf(stderr,"Could not open data as fd 0\n");
exit(1);
}
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
}
结果输入什么就打印出什么
9、testtty.c
#include <unistd.h>
int main()
{
char *buf = "abcde\n";
write(0, buf, 6);/*write(int handle,void *buf,int nbyte); 第一个参数是文件描述符,第二个参数是指向一端内存单元的指针,第三个参数是要写入指定文件的字节个数;成功时返回字节个数,否则返回-1。*/
}
10、sigactdemo1.c
#include <stdio.h>
#include<unistd.h>
#include <signal.h>
#define INPUTLEN 100
void inthandler();
int main()
{
struct sigaction newhandler;
sigset_t blocked; //信号集,用来描述信号的集合,与信号阻塞相关函数配合使用
char x[INPUTLEN];
newhandler.sa_handler = inthandler; //函数指针
newhandler.sa_flags = SA_RESTART|SA_NODEFER
|SA_RESETHAND; //sa_flags是一个位掩码。这里,第一个参数使得被信号打断的一些原语“正常返回”
sigemptyset(&blocked);
sigaddset(&blocked, SIGQUIT);
newhandler.sa_mask = blocked;
if (sigaction(SIGINT, &newhandler, NULL) == -1)
perror("sigaction");
else
while (1) {
fgets(x, INPUTLEN, stdin);
printf("input: %s", x);
}
return 0;
}
void inthandler(int s)
{
printf("Called with signal %d\n", s);
sleep(s * 4);
printf("done handling signal %d\n", s);
}
输入什么就打印什么,如下打印学号:
11、sigdemo1.c
#include <stdio.h>
#include <signal.h>
void f(int);
int main()
{
int i;
signal( SIGINT, f );
for(i=0; i<5; i++ ){
printf("hello\n");
sleep(2);
}
return 0;
}
void f(int signum)
{
printf("OUCH!\n");
}
signal函数:
原型 signal(参数1,参数2);
其中参数1是进行处理的信号,参数2是处理的方式。
12、sigactdemo2.c
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
void sig_alrm( int signo )
{
/*do nothing*/
}
unsigned int mysleep(unsigned int nsecs)
{
struct sigaction newact, oldact;
unsigned int unslept;
newact.sa_handler = sig_alrm;
sigemptyset( &newact.sa_mask );
newact.sa_flags = 0;
sigaction( SIGALRM, &newact, &oldact );
alarm( nsecs );
pause();
unslept = alarm ( 0 );
sigaction( SIGALRM, &oldact, NULL );
return unslept;
}
int main( void )
{
while( 1 )
{
mysleep( 2 );
printf( "Two seconds passed\n" );
}
return 0;
}
休息two seconds秒后返回;或者被信号中断且信号处理函数返回后sleep()返回0。
如果不计较返回值的话,pause()的功能相当于无限期的sleep()。
13、exec1
#include <stdio.h>
#include <unistd.h>//表头文件
int main()
{
char *arglist[3];//arglist属于命令行参数,表示不定参数列表,表示后跟不定个参数。
arglist[0] = "ls";
arglist[1] = "-l";
arglist[2] = 0 ;//NULL
printf("* * * About to exec ls -l\n");
execvp( "ls" , arglist );
printf("* * * ls is done. bye");
return 0;
}
定义execvp函数:
int execvp(const char file ,char const argv []);
execvp()会从PATH 环境变量所指的目录中查找符合参数file 的文件名,找到后便执行该文件,然后将第二个参数argv传给该欲执行的文件。
如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中。
14、exec2
exevp函数的第一个参数,exec2的参数是arglist[0],与exec1等价,运行结果相同
15、exec3
#include <stdio.h>
#include <unistd.h>
int main()
{
char *arglist[3];
char*myenv[3];
myenv[0] = "PATH=:/bin:";
myenv[1] = NULL;
arglist[0] = "ls";
arglist[1] = "-l";
arglist[2] = 0 ;
printf("* * * About to exec ls -l\n");
execlp("ls", "ls", "-l", NULL);
printf("* * * ls is done. bye\n");
}
这里使用了execlp()函数,
头文件:
include<unistd.h>
定义函数:
int execlp(const char file, const char argv ...)
execlp()会从PATH 环境变量所指的目录中查找符合参数file的文件名,找到后便执行该文件,然后将第二个以后的参数当做该文件的argv[0]、argv[1]……,最后一个参数必须用空指针(NULL)作结束。
如果用常数0来表示一个空指针,则必须将它强制转换为一个字符指针,否则将它解释为整形参数,如果一个整形数的长度与char * 的长度不同,那么exec函数的实际参数就将出错。
如果函数调用成功,进程自己的执行代码就会变成加载程序的代码,execlp()后边的代码也就不会执行了.
返回值:
如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno 中。
exec3代码指定了环境变量,然后依然执行了ls -l指令,成功后没有返回,所以最后一句话不会输出。运行结果同exec1。
16、forkdemo1
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int ret_from_fork, mypid;
mypid = getpid();
printf("Before: my pid is %d\n", mypid);
ret_from_fork = fork();
sleep(1);
printf("After: my pid is %d, fork() said %d\n",
getpid(), ret_from_fork);
return 0;
}
该代码首先打印进程pid,然后调用fork函数生成子进程,休眠一秒后再次打印进程id,这时父进程打印子进程pid,子进程返回0。
17、forkdemo2
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("before:my pid is %d\n", getpid() );
fork();
fork();
printf("aftre:my pid is %d\n", getpid() );
return 0;
}
调用两次fork,共产生四个子进程,所以打印四个aftre输出。
18、forkdemo3
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
int fork_rv;
printf("Before: my pid is %d\n", getpid());
fork_rv = fork(); /* create new process */
if ( fork_rv == -1 ) /* check for error */
perror("fork");
else if ( fork_rv == 0 ){
printf("I am the child. my pid=%d\n", getpid());
exit(0);
}
else{
printf("I am the parent. my child is %d\n", fork_rv);
exit(0);
}
return 0;
}
代码调用一次fork产生子进程,父进程返回子进程pid,不为0,所以输出父进程,子进程返回0,所以输出子进程。
19、forkgdb
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int gi=0;
int main()
{
int li=0;
static int si=0;
int i=0;
pid_t pid = fork();
if(pid == -1){
exit(-1);
}
else if(pid == 0){
for(i=0; i<5; i++){
printf("child li:%d\n", li++);
sleep(1);
printf("child gi:%d\n", gi++);
printf("child si:%d\n", si++);
}
exit(0);
}
else{
for(i=0; i<5; i++){
printf("parent li:%d\n", li++);
printf("parent gi:%d\n", gi++);
sleep(1);
printf("parent si:%d\n", si++);
}
exit(0);
}
return 0;
}
调用一次fork,父进程先打印两句,然后休眠一秒,然后打印一句,子进程先打印一句,然后休眠一秒,然后打印两句。这两个线程是并发的,所以可以看到在一个线程休眠的那一秒,另一个线程在执行,并且线程之间相互独立互不干扰。
20、waitdemo2
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#define DELAY 10
void child_code(int delay)
{
printf("child %d here. will sleep for %d seconds\n", getpid(), delay);
sleep(delay);
printf("child done. about to exit\n");
exit(27);
}
void parent_code(int childpid)
{
int wait_rv;
int child_status;
int high_8, low_7, bit_7;
wait_rv = wait(&child_status);
printf("done waiting for %d. Wait returned: %d\n", childpid, wait_rv);
high_8 = child_status >> 8; /* 1111 1111 0000 0000 */
low_7 = child_status & 0x7F; /* 0000 0000 0111 1111 */
bit_7 = child_status & 0x80; /* 0000 0000 1000 0000 */
printf("status: exit=%d, sig=%d, core=%d\n", high_8, low_7, bit_7);
}
int main()
{
int newpid;
printf("before: mypid is %d\n", getpid());
if ( (newpid = fork()) == -1 )
perror("fork");
else if ( newpid == 0 )
child_code(DELAY);
else
parent_code(newpid);
}
waitdemo1:如果有子进程,则终止子进程,成功返回子进程pid。
waitdemo2:多了一个子进程的状态区分,把状态拆分成三块,exit,sig和core。
代码调试问题
testmf.c代码执行完毕后无显示,为了方便知道它已经执行完毕,自己尝试修改了源代码,如下:
再次运行结束后便可以清楚的知道它finish了:
sigactdemo2.c开始执行后,每两秒才会显示,并无限循环。
可以使用ctrl+c使它停止
其他
本章阅读内容比较多,主要掌握进程及其控制函数的使用,信号发送函数,接收函数和处理函数。
这周老师给了很多代码包,动手实践的过程很多,感觉学到了不少的内容。比如fork个函数的特点,虽然在书本上有讲过,但动手实践后感受到了它更深的原理。
本周代码托管截图
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 100/100 | 2/2 | 25/25 | 安装了虚拟机并学习掌握核心的linux命令 |
第二周 | 100/200 | 1/3 | 30/55 | 虚拟机上的C语言编程 |
第三周 | 150/350 | 1/4 | 10/65 | 计算机中信息的表示和运算 |
第四周 | 0/350 | 0/4 | 3/68 | 复习前几周内容 |
第五周 | 75/420 | 1/5 | 20/88 | 程序的机器级表示 |
第六周 | 125/545 | 1/6 | 20/108 | Y86指令 硬件语言控制HCL |
第七周 | 72/617 | 1/7 | 20/128 | 磁盘 存储器相关结构 |
第八周 | 0/617 | 2/9 | 20/148 | 期中总结 |
第九周 | 185/802 | 2/11 | 25/173 | 系统级的输入输出 |
第十周 | 669/1472 | 2/13 | 20/193 | 重点代码的学习 |
第十一周 | 669/1472 | 2/15 | 35/228 | process代码的学习 |