笔记二:进程间的通信(fork、孤儿进程,僵死进程等)
以下是以前学习《unix环境高级编程》时的一些笔记和测试代码,好久没看过了,没有再次验证,存在错误的话,希望见谅,分享下主要是!!!
ps 查看系统中的进程 ps–axj
A与B在用户空间是不能通信的,相当于封闭的房子,也没有窗户,所以在用户空间是无法通信的
二:进程控制相关函数
1.fork()函数
功能:创建一个子进程
参数:没有参数
返回值:成功,返回二个值;0 --- 子进程中 >0(子进程的PID号) ---- 父进程中
出错,返回一个值-1
成功时,内核为何会返还二个值?
用户空间的进程,可以认为是一个封闭的房子(在用户空间),内核是怎样对用户空间这么多进程进行管理,怎样识别,是通过PID。
fork() 调用成功,内核则在用户空间创建一个进程(房子),这个房子和之前的进程代码段,数据段等都一样(除了PID之外)
以下两段fork函数简单的使用
以下两段fork函数简单的使用
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "stdlib.h"
int main()
{
pid_t pid;
pid=fork();
printf("hello linux\n");
usleep(200); //200微秒
printf("11111111111\n");
while(1);
return 0;
}
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "stdlib.h"
int main()
{
pid_t pid;
pid=fork();
if(pid >0)
{
printf("parent process run\n");
}
if(pid ==0)
{
printf("child process run\n");
}
while(1);
return 0;
}
getpid(); 获取本进程的PID号(返回值)
getppid();获取本进程父进程PID号(返回值)
exit(): 进程退出函数
什么是僵尸进程?什么是孤儿进程?
什么是僵尸进程?什么是孤儿进程?
子进程退出,父进程没有退出,此时子进程处于Z(zobile)(僵尸) 状态,处于此状态的进程仍要耗费系统资源。子进程自己不能回收资源,只有父亲负责为子进程回收资源。父进程退出,子进程没有退出,此时子进程变为孤儿进程,会被1号进程收养,即init进程为其父进程。
例子1:
/*僵尸进程 查看进程pid状态等消息 可以终端使用命令ps aux或者ps axj*/
#include "signal.h"
#include "sys/types.h"
#include "stdio.h"
int main()
{
pid_t pid;
pid = fork();
if(pid < 0)
{
printf("创建进程失败\n");
return -1;
}
/*对于父进程首先是睡眠(8秒)S状态,8秒后进入R(运行)状态
对于子进程就变成了T状态;
*/
if(pid > 0)
{
sleep(8);//睡眠状态;
/*如果设置的waitpid设置为waitpid(pid,NULL,0)为阻塞,那么将不会产生僵尸进程,父进程会为其回收*/
if(waitpid(pid,NULL,WNOHANG) == 0)//等待子进程结束;可以设置为非阻塞(WNOHANG);
{
kill(pid,9);//杀死子进程,父进程没有结束,子进程将变成Z状态,因为父进程没有给子进程回收资源,子进程将变为僵尸进程;
}
while(1);
}
if(pid == 0)
{
printf("raise function before\n");
raise(SIGTSTP);//暂停程序,相当于ctrl+z; T状态
printf("raise function after\n");
exit(0);
}
}
如果我们要求:子进程退出,父进程没有退出,同时也不要子进程处于Z(zobile) 状态,而是完全结束,怎么办?
2.exit()--------库函数 进程的退出函数
头文件:#include “stdlib.h”
参数: int 退出状态----- 假设:0 为正常退出,-1为非正常退出
exit中的参数退出状态主要作用是传递给父进程,父进程根据这些状态可判定子进程现在处于什么情况。父进程怎样接收到这个状态呢?是通过wait 或waitpid
exit()------系统调用函数----unistd.h
以下为exit()和_exit()基本使用
以下为exit()和_exit()基本使用
#include "stdio.h"
#include "unistd.h"
#include "stdlib.h"
int main()
{
printf("hello linux");
// fflush(stdout);
//_exit(0);
exit(0);
printf("1111111111111");
return 0;
}
3 wait()函数
所需头文件:
#include <sys/types.h>
#include <sys/wait.h>
函数原型:
pid_t wait(int *status)
函数参数:
status 是一个整型指针,指向的对象用来保存子进程退出时的状态。
status 若为空,表示忽略子进程退出时的状态
status 若不为空,表示保存子进程退出时的状态
另外,子进程的结束状态可由Linux中一些特定的宏来测定。
函数返回值: 成功:子进程的进程号 失败:-1
什么时候会阻塞,什么时候不会阻塞?
调用该函数使进程阻塞(睡眠),直到任一个子进程结束或者是该进程接收到了一个信号为止。
如果该进程没有子进程或者其子进程已经结束,wait函数会立即返回,即不会阻塞。
以下是关于wait函数的使用
以下是关于wait函数的使用
#include "sys/wait.h"
#include "sys/types.h"
#include "stdio.h"
int main()
{
printf("wait before\n");
wait(NULL);
printf("wait after\n");
return 0;
}
#include "sys/types.h"
#include "stdio.h"
#include "unistd.h"
int main()
{
pid_t pid;
pid=fork();
if(pid <0 )
{
printf("creat process failure\n");
return -1;
}
if(pid ==0)//child process
{
printf("this is child process\n") ;
while(1);
}
if(pid >0) //parent process
{
printf("this is parent process\n");
printf("parent process:wait before\n");
wait(NULL);
printf("parent process:wait after\n");
while(1);
}
return 0;
}
解析:
子进程1:
printf ------- pid
sleep(10) -------S 10秒后 结束
exit(1)
子进程2:
printf ------- pid
sleep(20) -------S S(睡眠状态) 再过10秒
exit(3)
父进程:
printf---
printf ---wait before
pet=wait------------------S
printf---wait after ret=%d
pet=wait S(睡眠状态) R(运行状态)
printf second wait ret=%d
#include "sys/types.h"
#include "sys/wait.h"
#include "unistd.h"
#include "stdio.h"
#include "stdlib.h"
int main()
{
pid_t pid;
int ret;
pid = fork();
if(pid <0)
{
printf("creat process failure\n");
return -1;
}
if(pid == 0) //child process1
{
printf("this is a child process,pid=%d\n",getpid());
sleep(10);
exit(1);
}
if(pid >0)
{
pid_t pid1;
pid1=fork();
if(pid1<0)
{
printf("creat child process1 failure\n");
exit(2);
}
if(pid1 ==0)
{
printf("this is a child process1 run,pid=%d\n",getpid());
sleep(20);
exit(3);
}
printf("this is a parent process\n");
printf("wait before\n");
ret=wait(NULL);
printf("wait after,first wait ret=%d\n",ret);
ret=wait(NULL);
printf("second wait after,ret=%d\n",ret);
while(1);
return 0;
}
4. waitpid() 功能比wait 功能更强
可以等待某一个子进程退出,可通过第一个参数来实现
所需头文件:#include <sys/types.h>
#include <sys/wait.h>
函数原型: pid_t waitpid(pid_t pid, int *status, int options)
函数参数: pid pid>0:只等待进程ID等于pid的子进程,不管已经有其他子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
pid=-1:等待任何一个子进程退出,此时和wait作用一样。
pid=0:等待其组ID等于调用进程的组ID的任一子进程。
pid<-1:等待其组ID等于pid的绝对值的任一子进程。
status
同wait
options WNOHANG:若由pid指定的子进程并不立即可用,则waitpid不阻塞,此时返回值为0
WUNTRACED:若某实现支持作业控制,则由pid指定的任一子进程状态已暂停,且其状态自暂停以来还未报告过,则返回其状态。
0:同wait,阻塞父进程,等待子进程退出。
函数返回值: 正常:结束的子进程的进程号
使用选项WNOHANG且没有子进程结束时:0
调用出错:-1
例子:
例子:
#include "sys/types.h"
#include "sys/wait.h"
#include "unistd.h"
#include "stdio.h"
#include "stdlib.h"
int main()
{
pid_t pid;
int ret;
int status;
pid = fork();
if(pid <0)
{
printf("creat process failure\n");
return -1;
}
if(pid == 0) //child process1
{
printf("this is a child process,pid=%d\n",getpid());
sleep(10);
exit(1);
}
if(pid >0)
{
pid_t pid1;
pid1=fork();
if(pid1<0)
{
printf("creat child process1 failure\n");
exit(2);
}
if(pid1 ==0)
{
printf("this is a child process1 run,pid=%d\n",getpid());
sleep(20);
exit(3);
}
printf("this is a parent process\n");
printf("wait before\n");
ret=waitpid(pid1,&status,0);
printf("wait after,first wait ret=%d,status=%d\n",ret,WEXITSTATUS(status));
// ret=wait(&status);
// printf("second wait after,ret=%d,status=%d\n",ret,WEXITSTATUS(status));
while(1);
return 0;
}
}
5 vfork() ----形式和fork()
vfork() 调用成功,内核则在用户空间创建一个进程,但是不会重新建立一个房子,父子里程共享父进程这个房子,然后子进程先运行,父进程后运行
6.在此之前创建子进程,子进程进行了代码的完全拷贝,虽然我们用返回值的不同使得父子进程执行了二个不同的代码区。但是很多情况下:
父子进程的代码完全不一样,比如 bash 这是父进程,a.out 是子进程,这二段代码没有相似性;或父进程是C代码,子进程是一个shell脚本,等,我们应该怎么处理?
当进程认为自己不能再为系统和用户做出任何贡献了时就可以调用exec函数,让自己执行新的程序,我们应该怎么处理?
伪代码:
伪代码:
int main() computer 这个可执行程
{
printf(“dfdfggf\n”);
i++;
.....
Computer//不是函数,而是另外一个程序
}
可以通过exec 函数族来解决这些问题。
函数的功能:可以调用另外一个程序来运行,会把原来的程序中的代码段,数据段等都替换掉,从这个调用的程序的开始来执行,会保留原进程的PID,其它都不保留。
什么时候要用这个函数呢?
当进程认为自己不能再为系统和用户做出任何贡献了时就可以调用exec函数,让自己执行新的任务。
如果某个进程想同时执行另一个程序,它就可以调用fork函数创建子进程,然后在子进程中调用任何一个exec函数。这样看起来就好像通过执行应用程序而产生了一个新进程一样。
所需头文件:#include <unistd.h>
函数原型: int execl(const char *path, const char *arg, ...);
int execv(const char *path, char *const argv[]);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execve(const char *path, char *const argv[], char *const envp[]:xp[]);
int execlp(const char *file, const char *arg, ...);
int execvp(const char *file, char *const argv[]);//环境变量加
函数返回值: -1:出错,成功是0
参数: 第一个参数,前面四个都一样,后面二个一样,path file
path 必须要指定所在程序的路径,要不是绝对路径或相对路径
file 不用指定路径(指定路径也可以),在环境变量PATH中找这个程序
char * 逐个列举
例如这样一个程序
ls –l /mnt ./child/xxx “./child/xxx” NULL
怎样传递这个参数呢?
“ls” “-l” “/mnt” NULL
采用字符指针数组的方式传递参数
char *argv[]={“ls” “-l” “/mnt” NULL }
第一个参数:有二类,char *path—不仅要指定程序的名称也要指定程序的路径
char *file----指定程序的名称,你这个程序的所在路径一定要在环境变量位PATH中找到
第二参数: char *arg …… char *argv[]
execl例子
execl例子
#include "stdio.h"
#include "unistd.h"
int main()
{
printf("exec before\n");
execl("./xxx","./xxx",NULL);
printf("exec after\n");
return 0;
}
execv例子
#include "stdio.h"
#include "unistd.h"
int main()
{
char *buf[]={"./child/xxx",NULL};
printf("exec before\n");
execv("./child/xxx",buf);
printf("exec after\n");
return 0;
}
execvp例子
#include "stdio.h"
#include "unistd.h"
int main()
{
char *buf[]={"xxx",NULL};
printf("exec before\n");
execvp("xxx",buf);
printf("exec after\n");
return 0;
}