pid_t pid;//定义一个进程标识符
pid = getpid();//获取当前进程的pid
fork();//创建进程
fork()是有返回值的,返回值为0代表当前进程是子进程,返回值为非负数代表当前进程是父进程,
返回-1代表调用失败
我们来了解一下vfork函数,那vfork和fork有什么区别呢?
区别一:vfork直接使用父进程存储空间,不拷贝
!!!区别二:vfork保证子进程先运行,当子进程调用exit()退出后,父进程再运行**
break;//非正常退出
exit(0);//正常退出
wait(&status),//收集子进程退出状态,等待子进程退出再运行父进程
WEXITSTATUS(status)//显示真正的退出状态
异常退出会造成子进程变为僵尸进程,使用wait(NULL)。//等待子进程先运行,子进程运行完父进程运行,并且手机子进程退出状态。
1、什么是程序,什么是进程,有什么区别?
程序是静态的概念,进程是动态的概念。gcc a.c -o a,a就是一个程序,存在于硬盘中,当a跑起来之后,系统中就 多了一个进程,进程就是跑起来的程序。
2、什么是进程标识符?
每个进程都有唯一的非负整数表示唯一ID,叫做pid,类似进程的身份证
pid = 0,交换进程,作用是进程调度
pid = 1,init进程,作用是系统初始化
用top来查看进程的pid以及占用cup率,以评估程序好坏。
ps -aux|grep snake 筛选出进程为snake的相关进程
#include
#include
#include
int main()
{
pid_t pid;//定义一个进程标识符
pid = getpid();//获取当前进程的pid
printf("my pid is %d\n",pid);
while(1);
return 0;
}
~
3、什么是父进程,什么是子进程
A进程创建了B进程,则称A进程是B进程的父进程,B进程是A进程的子进程,父子进程是相对的概念。
4、C程序的存储空间是如何分配的?
malloc申请的空间返回值就是在堆里
寄存器:最快的存储区
一、创建进程实战
pid_t fork(void);
fork函数调用成功返回两次,
fork = 0,表示当前进程是子进程
fork返回值是非负数,表示当前进程是父进程
调用失败返回﹣1
#include
#include
#include
int main()
{
pid_t pid;
pid = getpid();
printf("before fork pid is %d\n",getpid());
fork();//在此处创建进程
printf("after fork pid is %d\n",getpid());
if(pid == getpid())
{
printf("this is father fork\n");
}
else
{
printf("this is child fork,child pid is %d\n",getpid());
}
return 0;
}
这里我们可以分析得出,创建进程就是**把fork后面的代码(包括fork行)**重新运行一次,不过得父进程运行完成之后再来运行子进程
我们通过fork返回的值来判断父子进程
#include
#include
#include
int main()
{
pid_t pid;
pid = getpid();//先把父进程的pid打印出来,动态值赋值给静态
printf("this is father fork,pid is %d\n",pid);
pid = fork();//在此处创建进程
if(pid>0)
{
printf("this is father fork,pid is %d\n",getpid());
}
else if(pid == 0)
{
printf("this is child fork,child pid is %d\n",getpid());
}
return 0;
}
#include
#include
#include
int main()
{
pid_t pid;
pid = getpid();
int data = 10;
printf("this is father fork,pid is %d\n",pid);
pid = fork();//在此处创建进程
if(pid>0)
{
printf("this is father fork,pid is %d\n",getpid());
}
else if(pid == 0)
{
printf("this is child fork,child pid is %d\n",getpid());
data = data + 100;
}
printf("data = %d\n",data);
return 0;
}
从程序的运行结果可以看出,当子进程运行时,程序从父进程备份了一份数据,在对子进程操作时,改变的是子进程的数据,不会对父进程数据造成影响。
fork创建一个子进程的一般目的:
1.一个父进程希望复制自己,使父子进程同时执行不同的代码段(多个子进程同时运行),在这个网络服务进程中是常见的。父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求到达。
2.一个进程要执行一个不同的程序,这对shell常见的情况。在这种情况下,子进程从fork返回后立即调用exec
需求1应用场景:
#include
#include
#include
int main()
{
int mark;
pid_t pid;
while(1)
{
printf("please input mark:\n");
scanf("%d",&mark);
if(mark == 1)
{
pid = fork();
if(pid > 0)
{
printf("this is father process\n");
}
else if(pid == 0)
{
while(1)
{
printf("no request,pid is %d\n",getpid());
sleep(7);
}
}
}
else
{
printf("do nothing");
}
}
}
//fork()函数创建的子进程会和父进程争夺cpu,不一定一先一后。
#include
#include
#include
#include
int main()
{
int mark;
pid_t pid;
int cnt = 0;
while(1)
{
printf("please input mark:\n");
scanf("%d",&mark);
if(mark == 1)
{
pid = vfork();
if(pid > 0)
{
printf("this is father process:%d\n",getpid());
printf("cnt = %d\n",cnt);
sleep(2);
}
else if(pid == 0)
{
while(1)
{
cnt++;
printf("no request,pid is %d\n",getpid());
if(cnt == 3)
{
cnt = 0;
exit(0);
}
sleep(2);
}
}
}
else
{
printf("do nothing\n");
sleep(0.5);
}
}
}
在这里插入代码片
父子进程两者都是同时运行,父进程一直监测用户输入,当有客户端来时,创建子进程服务于客户端。父进程只检测什么也不干。这里我们可以看出当未创建服务端时,父进程一直在等待输入,当服务端来临时,立刻创建一个子进程,同时父进程也在运行等待新的输入,最后我们输入三个1意味着创建了三个子进程,这三个子进程同时运行。
!!!核心:创建子进程时,子进程copy了父进程的代码部分存到存储器,这时共有两份数据同时运行。
我们来验证一下父子进程同时在运行的结果:
#include
#include
#include
int main()
{
pid_t pid;
pid = fork();
if(pid>0)
{
while(1)
{
printf("this is father fork,pid is %d\n",getpid());
sleep(1);
}
}
else if(pid == 0)
{
while(1)
{
printf("this is fchild fork,pid is %d\n",getpid());
sleep(1);
}
}
return 0;
}
我们来了解一下vfork函数,那vfork和fork有什么区别呢?
区别一:vfork直接使用父进程存储空间,不拷贝
区别二:vfork保证子进程先运行,当子进程调用exit()退出后,父进程再运行
下面我们来验证一下:
#include
#include
#include
int main()
{
pid_t pid;
pid = vfork();
int cnt = 0;
if(pid>0)
{
while(1)
{
printf("this is father fork,pid is %d\n",getpid());
sleep(1);
printf("cnt = %d\n",cnt);
}
}
else if(pid == 0)
{
while(1)
{
printf("this is child fork,pid is %d\n",getpid());
sleep(1);
cnt++;
if(cnt == 3)
{
break;//非正常退出
}
}
}
return 0;
}
#include
#include
#include
#include
int main()
{
pid_t pid;
int cnt = 0;
pid = vfork();
if(pid>0)
{
while(1)
{
printf("this is father fork,pid is %d\n",getpid());
sleep(1);
printf("cnt = %d\n",cnt);
}
}
else if(pid == 0)
{
while(1)
{
printf("this is child fork,pid is %d\n",getpid());
sleep(1);
cnt++;
if(cnt == 3)
{
exit(0);//正常退出
}
}
}
return 0;
}
这里先让子进程运行三次,然后退出执行父进程。这里我们可以看出当程序异常退出时会改变cnt的值,我们必须使用exit使程序正常退出。
这个时候调用ps查看进程我们可以知道这个子进程为zombie进程即为僵尸进程,进程运行完后不收集会变成僵尸进程。
*为了避免僵尸进程,我们调用wait函数。
pid_t wait(int status);
它的参数是一个指针,指针为空时,不关心退出状态,非空时子进程退出状态放在它所指向的地址中;同样我们调用fork函数来验证一下wait函数的作用。
这里我们可以看到程序的运行结果,父进程先等待子进程运行,子进程运行完成后,父进程运行,并释放子进程,此时系统中无僵尸进程。
#include
#include
#include
#include
int main()
{
pid_t pid;
pid = vfork();
int status = 100;
int cnt = 0;
if(pid>0)
{
wait(&status);
while(1)
{
printf("the status is %d\n",WEXITSTATUS(status));
printf("this is father fork,pid is %d\n",getpid());
sleep(1);
printf("cnt = %d\n",cnt);
}
}
else if(pid == 0)
{
while(1)
{
printf("this is child fork,pid is %d\n",getpid());
sleep(1);
cnt++;
if(cnt == 3)
{
exit(0);//正常退出
}
}
}
return 0;
}
*这里为什么父进程要等待子进程退出?父进程给子进程交代任务,不管干没干完什么退出情况都要给父进程一个退出码,要调用WEXITSTATUS(status)函数对退出码进行解析。
wait函数的三大功能:
(1)如果其所有子进程都还在运行,则阻塞。
(2)如果一个子进程已终止,正等待父进程收集其状态则取得该子进程终止状态并立即返回。
(3)如果它没有任何子进程则立即返回出错。
wait使调用者阻塞(即父进程不运行等待子进程结束运行),waitpid有一个选项可以使调用者不阻塞。
pid_t waitpid(pid_t pid, int status, int options);
第一个参数:进程的pid号,在父进程调用,这个pid即为子进程pid号。
第二个参数:子进程退出状态存到该地址里
第三个参数:多用WNOHANG
#include
#include
#include
#include
#include
int main()
{
pid_t pid;
int status = 100;
int cnt = 0;
pid = fork();
if(pid>0)
{
waitpid(pid,&status,WNOHANG);//pid就是子进程pid
while(1)
{
printf("the status is %d\n",WEXITSTATUS(status));
printf("this is father fork,pid is %d\n",getpid());
sleep(1);
printf("cnt = %d\n",cnt);
}
}
else if(pid == 0)
{
while(1)
{
printf("this is child fork,pid is %d\n",getpid());
sleep(1);
cnt++;
if(cnt == 3)
{
exit(0);//正常退出
}
}
}
return 0;
}
这里我们让父子进程一起运行,用到waitpid这个api,那这个子进程就变成了僵尸进程。
#include
#include
#include
#include
#include
int main()
{
pid_t pid;
pid = fork();
int cnt = 0;
if(pid>0)
{
printf("this is father fork,pid is %d\n",getpid());
sleep(1);
}
else if(pid == 0)
{
while(1)
{
printf("this is child fork pid is %d ,my father pid is %d\n",getpid(),getppid());
sleep(1);
cnt++;
if(cnt == 3)
{
exit(0);//正常退出
}
}
}
return 0;
}
//getppid()获取父进程号
首先父进程先运行,子进程与父进程同时运行,过一会父进程退出,此时子进程变为孤儿进程,系统默认把init进程作为子进程的父进程(init进程默认pid为1)。
孤儿进程:父进程如果不等待子进程退出,在子进程结束之前就结束自己的生命,此时子进程叫做孤儿进程,Linux系统避免存在过多孤儿进程,init进程收留孤儿进程,变成孤儿进程的父进程。
exec族函数作用:在调用一个进程中,转而调用另一个进程。
这段代码我们在自己写cp指令时用到过,作用是打印出命令行所有指令。
#include
int main(int argc,char *argv[])
{
int i = 0;
for(i = 0; i < argc; i++)
{
printf("argv[%d]: %s\n",i,argv[i]);
}
return 0;
当execl调用失败后,返回-1,并接着未执行的程序往下执行。执行成功的话不往下运行。
perror(“why”); //如果execl返回出错,返回了一个error,可以被这perror( )解析出来。
#include
#include
#include
//函数原型:int execl(const char *path, const char *arg, ...);
int main(void)
{
printf("before execl\n");
if(execl("./echoarg","echoarg","abc",NULL) == -1)
{
printf("execl failed!\n");
perror("why"); //如果execl返回出错,返回了一个error,可以被这perror( )解析出来
}
printf("after execl\n");
return 0;
}
ls是否也可被执行呢?我们用whereis ls查看ls绝对路径。
#include
#include
#include
//函数原型:int execl(const char *path, const char *arg, ...);
int main(void)
{
printf("before execl\n");
if(execl("/bin/ls","ls",NULL,NULL) == -1)
{
printf("execl failed!\n");
perror("why"); //如果execl返回出错,返回了一个error,可以被这perror( )解析出来
}
printf("after execl\n");
return 0;
}
这里我们让excel运行ls程序,我们查看ls的绝对路径,用whereis ls,用族函数打开与用命令行打开运行的结果是一样的。如果要实现ls -l呢,很简单,在excel后面给ls传参即可,第三个参数变为“-l”即可。
同样的操作我们来查看系统时间:
#include
#include
#include
//函数原型:int execl(const char *path, const char *arg, ...);
int main(void)
{
printf("this pro system date:\n");
if(execl("/bin/date","date",NULL,NULL) == -1)
{
printf("execl failed!\n");
perror("why"); //如果execl返回出错,返回了一个error,可以被这perror( )解析出来
}
printf("after execl\n");
return 0;
}
同样的步骤,先用whereis date查看date的绝对路径,其次date不需要其他参数,第三个参数为NULL,运行该程序,execl自动调用date函数。
那么每次都需要通过whereis来查找变量绝对路径是不是太麻烦呢,我们可以使用函数,execlp通过环境变量来查找可执行文件。
#include
#include
#include
//函数原型:int execl(const char *path, const char *arg, ...);
int main(void)
{
printf("this pro system date:\n");
if(execlp("date","date",NULL,NULL) == -1)
{
printf("execl failed!\n");
perror("why"); //如果execl返回出错,返回了一个error,可以被这perror( )解析出来
}
printf("after execl\n");
return 0;
}
通过环境变量自动找到date命令。
fork和exce一起使用:这里我们用检测输入的方式调用子进程,创建好shell脚本,作用为改变文本配置,子进程为调用exce函数,通过这个函数来调用changedata脚本。
#include
#include
#include
int main()
{
pid_t pid;
int data;
while(1)//父进程一直存在检测用户输入
{
printf("please input a numner\n");
scanf("%d",&data);
if(data == 1)//客户端有输入
{
pid = fork();//来一个客户则创建一个子进程来执行请求
if(pid>0)
{
}
else if(pid == 0)//子进程运行,服务客户端
{
execl("./changedata","changedata","config.txt",NULL);
}
}
else
{
printf("wait,do nothing\n");
}
}
return 0;
}
~
changedata为:
#include
#include
#include
#include
#include
#include
#include
int main(int argc,char* argv[2])
{
char* readbuf;
int fdScr;
if(argc != 2)//命令行必须输入两个操作命令
{
printf("input error\n");
exit(-1);
}
fdScr = open(argv[1],O_RDWR);
int size = lseek(fdScr,0,SEEK_END);//lseek反应的是文件偏移量
lseek(fdScr,0,SEEK_SET);//光标重新移到文件头
readbuf = (char*)malloc(sizeof(char)*size);//而readbuf需要开辟(偏移量*字节数)
read(fdScr,readbuf,size*sizeof(char));//把目标文件读到readbuf里面
char* p = strstr(readbuf,"LENG=");//在目标文件里面查找“LENG=”字符
if(p == NULL)
{
printf("Not Found\n");
exit(-1);
}
p = p + strlen("LENG=");//如果找到该字符串,则strstr函数返回该字符串开始的位置,我们让该字符串偏移到我们想要的位置
*p = '9';//修改我们需要的文件配置,以上均是把静态数据区的文件复制到动态数据区,修改以后的值是在动态数据区的readbuf,文件里面存的都是字>符
lseek(fdScr,0,SEEK_SET);//光标移到文件头
write(fdScr,readbuf,strlen(readbuf));//把readbuf里面的值重新写到该文件从头覆盖
close(fdScr);//关闭该文件,并将数据同步更新到源文件
return 0;
}
system函数:
调用成功则返回进程的状态值;不能执行则返回127,失败返回-1.
作用也都是执行一个shell指令。当检测到子进程运行时,我们只需要让系统执行system函数即可。
system("./changedata config.txt");
popen函数:比system函数的好处是:system函数是把调用的结果直接打印到终端页面,popen函数把调用结果返回一个流,我们可以调用read函数读取这个流把结果读取出来。
#include
#include
int main()
{
FILE* fp;
char ret[1024];
fp = popen("ps","r");
int n_read = fread(ret,1,1024,fp);//一次读一个字节,读1024次
printf("read %d byte,context is %s\n",n_read,ret);
return 0;
}