过程有点曲折。写最后题的时候发现前面的这章内容其实没有全懂。果然实践的时候才能暴露不足阿。而且最后一题被网上的一份题解坑的不惨。不过最后也是学到了新东西。写博客的时候发现共享文件夹莫名其妙坏了。修了1小时也没修好。然后转用之前一个软件XTFP调了一会儿。
1.编写一个调用 fork()的程序。在调用 fork()之前,让主进程访问一个变量(例如 x) 并将其值设置为某个值(例如 100)。子进程中的变量有什么值?当子进程和父进程都改变 x 的值时,变量会发生什么?
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(int argc,char *argv[]){
int x=100;
int rc=fork();
if(rc<0){
printf("error");
exit(1);
}
else if(rc==0){///child
x=-1;
printf("child's x=%d\n",x);
}
else{//father
x=10;
printf("father's x=%d\n",x);
}
return 0;
}
返回结果两者都是100.修改的话就是各自变成各自的x。
子进程在一定程度上拷贝了父进程。之后存的就是私有内存。
2.编写一个打开文件的程序(使用 open()系统调用),然后调用 fork()创建一个新进程。 子进程和父进程都可以访问 open()返回的文件描述符吗?当它们并发(即同时)写入文件时, 会发生什么?
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<string.h>
int main(int argc,char *argv[]){
int fd=open("./issue2.output", O_CREAT|O_WRONLY|O_TRUNC,S_IRWXU);
int rc=fork();
if(rc<0){
printf("error");
exit(1);
}
else if(rc==0){///child
printf("child's %d \n",fd);
char buf[] ="I am child\n";
write(fd,buf,strlen(buf));
}
else{//father
printf("father's %d \n",fd);
char buf[]="I am father\n";
write(fd,buf,strlen(buf));
}
return 0;
}
都可以。我这边显示的是先父亲再儿子。
3.使用 fork()编写操一个程序。子进程应打印“hello”,父进程应打印“goodbye”。你 应该尝试确保子进程始终先打印。你能否不在父进程调用 wait()而做到这一点呢?
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<string.h>
#include<sys/wait.h>
int main(int argc,char *argv[]){
int rc=fork();
if(rc<0){
printf("error");
exit(1);
}
else if(rc==0){///child
printf("hello\n");
}
else{//father
// wait(NULL);
sleep(1);
printf("goodbye\n");
}
return 0;
}
我是尝试用sleep()
4.编写一个调用 fork()的程序,然后调用某种形式的 exec()来运行程序/bin/ls。看看是 否可以尝试 exec()的所有变体,包括 execl()、execle()、execlp()、execv()、execvp()和 execvP()。进程 API 为什么同样的基本调用会有这么多变种?
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<string.h>
#include<sys/wait.h>
int main(int argc,char *argv[]){
int rc=fork();
if(rc<0){
printf("error");
exit(1);
}
else if(rc==0){///child
printf("This is child\n");
execl("/bin/ls","ls","-l",NULL);
}
else{//father
waitpid(rc,NULL,0);
char* v[]={"ls","-l",NULL};
printf("This is father\n");
execve("/bin/ls",v,NULL);
execv("/bin/ls",v);
execvp("/bin/ls",v);
}
return 0;
}
函数的用法参数列表可以在RTFM中找。
所有的exec变体都可以运行程序/bin/ls。execve()是基础的系统调用,其他的变种都是在这个基础上包装的库函数。因为要应对各种不同的需求,所以才衍生出了这么多的形式。
比如“l”表示参数以列表的形式表示,“v”表示参数以数组的形式表示,“p”表示在PATH中搜索执行文件,“e”表示可附加环境参数。
5.现在编写一个程序,在父进程中使用 wait(),等待子进程完成。wait()返回什么?如 果你在子进程中使用 wait()会发生什么
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<string.h>
#include<sys/wait.h>
int main(int argc,char *argv[]){
int rc=fork();
if(rc<0){
printf("error");
exit(1);
}
else if(rc==0){///child
int res=wait(NULL);
printf("child %d %d \n",res,(int)getpid());
}
else{//father
/// int res= wait(NULL);
/// printf("%d %d\n",res,(int)getpid());
printf("father %d\n",(int)getpid());
}
return 0;
}
父进程等待子进程完成,返回子进程的pid
子进程用wait等待返回的是-1.
说明了wait对应的实际上是父子关系。(man wait可以找到)[这在最后一题很重要,虽然那一份题解输出是对的,但是实际上是错的。我本地输出和朋友输出两份代码输出的是不一样的】
All of these system calls are used to wait for state changes in a child of the calling process, and obtain information about the child whose state has changed. A state change is considered to be: the child terminated; the child was stopped by a signal; or the child was resumed by a signal. In the case of a terminated child, performing a wait allows the system to release the resources associated with the child; if a wait is not performed, then the terminated child remains in a "zombie" state (see NOTES below).
If a child has already changed state, then these calls return immediately. Otherwise they block until either a child changes state or a signal handler interrupts the call (assuming that system calls are not automatically restarted using the SA_RESTART flag of sigaction(2)). In the remainder of this page, a child whose state has changed and which has not yet been waited upon by one of these system calls is termed waitable.
6.对前一个程序稍作修改,这一使用 waitpid()而不是 wait()。什么时候 waitpid()会 有用?
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<string.h>
#include<sys/wait.h>
int main(int argc,char *argv[]){
int rc=fork();
if(rc<0){
printf("error");
exit(1);
}
else if(rc==0){///child
/// int res=waitpid(NULL);
printf("child %d \n",(int)getpid());
}
else{//father
int res= waitpid(-1,NULL,0);
printf("father %d %d\n",(int)getpid(),res);
}
return 0;
}
由pid的参数决定,waitpid( pid_t, int* status,int options)
通常后两者取NULL,0
pid_t取要等待结束的pid
7.编写一个创建子进程的程序,然后在子进程中关闭标准输出(STDOUT_FILENO)。 如果子进程在关闭描述符后调用 printf()打印输出,会发生什么?
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<string.h>
#include<sys/wait.h>
int main(int argc,char *argv[]){
int rc=fork();
if(rc<0){
printf("error");
exit(1);
}
else if(rc==0){///child
close(STDOUT_FILENO);
printf("child %d \n",(int)getpid());
}
else{//father
/// int res= wait(NULL);
/// printf("%d %d\n",res,(int)getpid());
printf("father %d\n",(int)getpid());
}
return 0;
}
我的测试是只会输出父程序的printf到屏幕上
8.编写一个程序,创建两个子进程,并使用 pipe()系统调用,将一个子进程的标准输 出连接到操一个子进程的标准输入。
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<string.h>
#include<sys/wait.h>
int main(int argc,char *argv[]){
int result=-1;
int fd[2]={0,0};
result=pipe(fd);
if(0!=result){
printf("fail to create pipe\n");
return 0;
}
int p1=fork();
if(-1==p1){
printf("fail to fork1\n");
exit(-1);
}
else if(0==p1){
close(fd[0]); //close read
char* str="Hello";
write(fd[1],str,strlen(str));
close(fd[1]); //close write
printf("I am P1. I has sent! \n");
return 0;
}
waitpid(p1,NULL,0);
int p2=fork();
/* if(0!=p2){
printf("fail to fork2 %d \n",p2);
///father also touch (more infomation --P29)
}*/
if(-1==p2){
printf("fail to fork2 \n");
}
else if(0==p2){
int errno=0;
waitpid(p1,NULL,0); //wati until pid1 ok
// perror("wait error?: ");
printf("Now it's p2\n");
close(fd[1]);
char buf[1024];
int len=read(fd[0],buf,1024);
if(len>0){
buf[len]='\0';
printf("I am P2.I has revecived %s\n",buf);
}
printf("???\n");
return 0;
}
waitpid(p1,NULL,0);
waitpid(p2,NULL,0);
printf("I am father,exerything is over!\n");
return 0;
}
这个题让我理清了之前没有懂的一些东西。
1.waitpid()父子进程之间的,兄弟进程之间是无效的。所以你用perpor测waitpid的返回值会返回无子进程。所以我的当时本地输出是"fuck"先出来的。
2.对于fork,两个进程会同时从这里返回,但是主进程返回儿子进程的pid,儿子进程返回0
3.对于pipe的读写,及时关闭不用的一端,写完也及时关。因为不保证原子操作。其他进程可能也会用到导致意外错误
4.之前gdb调试都是反汇编,一下子不知道对于程序怎么处理。主要还是一个原始代码怎么在gdb中显示出来。
GDB 可以打印出所调试程序的源代码,当然,在程序编译时一定要加上-g的参数,把源程序信息编译到执行文件中。不然就看不到源程序了。当程序停下来以后, GDB会报告程序停在了那个文件的第几行上。你可以用list命令来打印程序的源代码。
gcc -c issue8.c -g
gcc -o issue88.out issue8.o (中间的是filename,自己任意)
gdb issue88.out (gdb一个可执行文件)
输入l就可以看到代码行
关于list的用法
一般来说在list后面可以跟以下这们的参数:
<linenum> 行号。
<+offset> 当前行号的正偏移量。
<-offset> 当前行号的负偏移量。
<filename:linenum> 哪个文件的哪一行。
<function> 函数名。显示函数名为function的函数的源程序。
<filename:function> 哪个文件中的哪个函数。
<*address> 程序运行时的语句在内存中的地址。
list <first>, <last> 显示从first行到last行之间的源代码。
一般是打印当前行的上5行和下5行,如果显示函数是是上2行下8行,默认是10行,当然,你也可以定制显示的范围,使用下面命令可以设置一次显示源程序的行数。
set listsize <count>
设置一次显示源代码的行数。