ubuntu内核的版本:
2.4 、 2.5 、 2.6
一、 在子进程里执行新的程序
1、 在fork创建子进程的时候,子进程的PCB是从父进程复制过来的,代码段共享。子进程和父进程各自拥有自己的数据段、堆栈空间。(代码段是只读的,只能读,不能写)
这时,可以在子进程的进程空间里,加载另一个新程序。
(首先父进程用fork产生一个子进程,子进程和父进程只有pid不一样,然后再将进程加载到子进程的空间里)
a.out的执行来说明: bash调用了fork,创建了一个子进程,为了让子进程执行一个新的程序,这时候,将程序加载到了内存,然后和虚拟地址空间一一做映射。这时,程序执行起来就是一个新的进程。 在bash的所有命令都是一样(ls cd 等等)
tarena@tarena-virtual-machine:~/day28$./a.out
tarena@tarena-virtual-machine:~/day28$pstree
├─gnome-terminal─┬─bash───a.out
│ ├─bash───pstree
加载程序:
execve(2):将进程加载到子进程的空间里
#include <unistd.h>
int execve(constchar *filename, char *const argv[], char *const envp[]);
功能:执行程序
参数:
返回值:
execl
execlp
execle
execv
execvp
execvpe
返回值:只有出错的时候才有返回值,返回值为-1,且errno被设置
#include<unistd.h>
extern char**environ;
int execve(constchar *filename, char *const argv[], char *const envp[]);
int execl(constchar *path, const char *arg, ...);
int execlp(constchar *file, const char *arg, ...);
int execle(constchar *path, const char *arg,..., char * const envp[]);
int execv(constchar *path, char *const argv[]);
int execvp(constchar *file, char *const argv[]);
intexecvpe(const char *file, char *const argv[], char *const envp[]);
execl l:代表列表,就是将参数看成列表,每个参数都写出来
execv v:就是将参数看成一个整体。
execvp p:在PATH指定的路径下找文件
execvpe e:environ
举例:
pid:子进程 ppid:父进程
int main(intargc, char *argv[])
int main(intargc, char *argv[],char *env[])
char * const ps_argv[]={“ps”,”-o”,”pid,ppid,comm”,NULL};
char *const ps_envp[]={“PATH=/bin:/usr/bin”,”TERM=console”,NULL};
execl(“/bin/ps”,”ps”,”-o”,”pid,ppid,comm”,NULL);
execle(“/bin/ps”,”ps”,”-o”,”pid,ppid,comm”,NULL,ps_envp);
execv(“/bin/ls”,ps_argv);
execvp(“ps”,ps_argv);
execve(“/bin/ps”,ps_argv,ps_envp);
execlp(“ps”,”ps”, ”-o”,”pid,ppid,comm”,NULL);
execvpe
(ps命令:查看进程的详细信息 pid:当前进程id,ppid:父进程的id)
tarena@tarena-virtual-machine:~/day29$ ps -o pid,ppid,comm
PID PPID COMMAND
2531 2520 bash
2681 2531 ps
举例:simple.c
1 #include<stdio.h>
2 #include<unistd.h>
3 int main(){
4 int ret;
5 printf("pid=%d\n",getpid());
6 //当前进程的image被ps的image替换
7 ret=execl("/bin/ps","ps","-o","pid,ppid,comm",NULL);
8 //出错的时候执行以下代码
9 if(ret==1){
10 perror("exec");
11 return 0;
12 }
13 //下边的代码永远不会执行,此时新的子进程已经覆盖了当前进程,不会再往下执> 行原进程的内容
14 printf("hahaha");
15 return 0;
16 }
pid=2768
PID PPID COMMAND
2531 2520 bash
2768 2531 ps
相当于这样查看
tarena@tarena-virtual-machine:~/day29$ ps -o pid,ppid,comm
PID PPID COMMAND
2531 2520 bash
2771 2531 ps
以上验证了这个实例验证了进程的pid不变,但是内容改变了
在子进程里执行新的程序:execl.c
1 #include<stdio.h>
2#include<sys/types.h>
3 #include<sys/wait.h>
4 #include<unistd.h>
5
6 int main(int argc,char*argv[]){
7 pid_t pid;
8
9 pid=fork();
10 if(pid<0){
11 perror("fork");
12 return 1;
13 }
14 if(pid==0){//子进程
15 execl("/bin/ps","ps","-o","pid,ppid,comm",NULL);
16 }
17 else{//父进程
18 等待子进程的结束,并回收子进程
19 wait(NULL);
20 }
21 return 0;
22 }
tarena@tarena-virtual-machine:~$ pstree
├─gnome-terminal─┬─bash───a.out
tarena@tarena-virtual-machine:~/day29$ ./a.out
PID PPID COMMAND
2531 2520 bash
3254 2531 a.out
3255 3254 ps
二、 vfork创建子进程
fork是父进程复制出子进程,同时在内核给子进程复制出一模一样的PCB(文件描述符是PCB的一部分,所以父子进程有相同的文件描述符)
写时复制
vfork和fork的区别:
fork的时候,在内核里,复制父进程的PCB表。父进程和子进程各有各自的PCB
vfork的时候,在内核里,父进程和子进程共享一个PCB,采用写时复制技术
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);
更多参见fork;
举例:vfork.c
1 #include<stdio.h>
2#include<sys/types.h>
3 #include<sys/wait.h>
4 #include<unistd.h>
5 #include<stdlib.h>
6 int main(int argc,char*argv[]){
7 pid_t pid;
8
9 pid=vfork();
10 if(pid<0){
11 perror("fork");
12 return 1;
13 }
14 if(pid==0){//子进程
15 printf("pid=%d\n",getpid());
16 _exit(1);//要使用_exit(1)只结束子进程,如果使用exit(1),则结束整个程序
17 }
18 else{//父进程
19 printf("ppid=%d\n",getpid());
20 }
21 return 0;
22 }
tarena@tarena-virtual-machine:~/day29$ ./a.out
pid=3452
ppid=3451
注意:使用vfork创建的子进程中,使用_exit(2)结束子进程,不要使用exit(3)结束
三、简单的进程间通讯(管道)
pipe(2)
#include<unistd.h>
#include <unistd.h>
int pipe(int pipefd[2]);
使用管道通讯的两个进程需要具有亲属关系
父子进程或者兄弟进程
返回值:0代表成功
-1代表失败,erron被设置
参数:
pipefd:两个文件描述符
步骤:
第一步:调用pipe,创建两个文件描述符,用于管道通信
第二步:调用fork,创建子进程
第三步:父进程写,子进程读。就完成了管道的单向通信。
举例:子进程写,父进程读 pipe.c
注意:fd[0]指向管道的读端,fd[1]指向管道的写端
1 #include<stdio.h>
2 #include<unistd.h>
3#include<sys/types.h>
4 #include<sys/wait.h>
5 int main(){
6 int fd[2];
7 char buf[8];
8 pid_t pid;
9 //调用pipe产生两个文件描述符
10 pipe(fd);
11 //创建子进程
12 pid=fork();
13 if(pid<0){
14 perror("fork");
15 return ;
16 }
17 if(pid==0){//子进程
18 close(fd[0]);//关闭读端
19 //向管道里写字符串
20 write(fd[1],"tang",5);
21 }
22 else{//父进程
23 close(fd[1]);//关闭写端
24 wait(NULL);//等待子进程的结束
25 read(fd[0],buf,5); //从管道里读出数据
26 write(1,buf,5);//1表示打印到屏幕上
27 write(1,"\n",1);
28 }
29 return 0;
30 }
tarena@tarena-virtual-machine:~/day29$ ./a.out
tang
补充:pend.c
1 #include<stdio.h>
2 #include<unistd.h>
3 int main(){
4 pid_t pid;
5 pid=fork();
6 if(pid<0){
7 perror("fork");
8 return 1;
9 }
10 if(pid==0){
11 sleep(5);
12 printf("children..\n");
13 }
14 else{
15 printf("parent..\n");
16 }
17 return 0;
18 }
tarena@tarena-virtual-machine:~/day29$ ./a.out
parent..
tarena@tarena-virtual-machine:~/day29$ children..
上例说明:bash启动了父进程,父进程结束后,bash调用wait回收了父进程的退出信息。bash的提示信息就输出了,而此时,父进程创建的子进程还没有结束。等这个子进程sleep以后,信息就输出到bash提示信息以后。(bash只负责父进程的回收,这个父进程是bash的子进程)
进程小结:
1、 fork/vforl
2、 exec系列的函数,作用:将程序加载到内存空间里,用新的映像替换旧的映像
3、 wait/waitpid:用来回收进程的退出信息,检测进程怎么结束的
4、 管道用于进程间的通讯
5、 进程的退出。 exit(3)调用了_exit(2), exit(3)是对_exit(2)的封装
_exit(2)只是清除进程自己部分的空间,不清除父进程
调用exit(3)将父进程和子进程全部结束
6、 孤儿进程和僵尸进程
7、 bash fork创建子进程,exec将新的image加载到子进程的空间里
四、信号的基本概念
作业:1、myls 2、myshell 实现bash的功能
Vi myshell.c