1、什么是程序,什么是进程,有什么区别?
程序是静态的概念,进程是动态的概念。gcc a.c -o a,a就是一个程序,存在于硬盘中,当a跑起来之后,系统中就多了一个进程,进程就是跑起来的程序。
2、什么是进程标识符?
每个进程都有唯一的非负整数表示唯一ID,叫做pid,类似进程的身份证
pid = 0,交换进程,作用是进程调度
pid = 1,init进程,作用是系统初始化
用top来查看进程的pid以及占用cup,以评估程序好坏。
#include#include #include int main() { pid_t pid;//定义一个进程标识符 pid = getpid();//获取当前进程的pid printf("my pid is %dn",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 %dn",getpid()); fork();//在此处创建进程 printf("after fork pid is %dn",getpid()); if(pid == getpid()) { printf("this is father forkn"); } else { printf("this is child fork,child pid is %dn",getpid()); } return 0; }
这里我们可以分析得出,创建进程就是把fork后面的代码(包括fork行)重新运行一次,不过得父进程运行完成之后再来运行子进程
我们通过fork返回的值来判断父子进程
#include#include #include int main() { pid_t pid; pid = getpid();//先把父进程的pid打印出来,动态值赋值给静态 printf("this is father fork,pid is %dn",pid); pid = fork();//在此处创建进程 if(pid>0) { printf("this is father fork,pid is %dn",getpid()); } else if(pid == 0) { printf("this is child fork,child pid is %dn",getpid()); } return 0; }
那父子进程创建过程中发生了什么呢?
#include#include #include int main() { pid_t pid; pid = getpid(); int data = 10; printf("this is father fork,pid is %dn",pid); pid = fork();//在此处创建进程 if(pid>0) { printf("this is father fork,pid is %dn",getpid()); } else if(pid == 0) { printf("this is child fork,child pid is %dn",getpid()); data = data + 100; } printf("data = %dn",data); return 0; }
从程序的运行结果可以看出,当子进程运行时,程序从父进程备份了一份数据,在对子进程操作时,改变的是子进程的数据,不会对父进程数据造成影响。
fork创建一个子进程的一般目的:
1.一个父进程希望复制自己,使父子进程同时执行不同的代码段,在这个网络服务进程中是常见的。父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求到达。
2.一个进程要执行一个不同的程序,这对shell常见的情况。在这种情况下,子进程从fork返回后立即调用exec
需求1应用场景:
#include#include #include int main() { pid_t pid; int data; while(1)//父进程一直存在检测用户输入 { printf("please input a numnern"); scanf("%d",&data); if(data == 1)//客户端有输入 { pid = fork();//来一个客户则创建一个子进程来执行请求 if(pid>0) { } else if(pid == 0)//子进程运行,服务客户端 { while(1) { printf("do net request,pid is %dn",getpid()); sleep(3); } } } else { printf("wait,do nothingn"); } } return 0; }
父子进程两者都是同时运行,父进程一直监测用户输入,当有客户端来时,创建子进程服务于客户端。父进程只检测什么也不干。这里我们可以看出当未创建服务端时,父进程一直在等待输入,当服务端来临时,立刻创建一个子进程,同时父进程也在运行等待新的输入,最后我们输入三个1意味着创建了三个子进程,这三个子进程同时运行。
!!!核心:创建子进程时,子进程copy了父进程的代码部分存到存储器,这时共有两份数据同时运行。
我们来验证一下父子进程同时在运行的结果:
#include#include #include int main() { pid_t pid; pid = fork(); if(pid>0) { while(1) { printf("this is father fork,pid is %dn",getpid()); sleep(1); } } else if(pid == 0) { while(1) { printf("this is fchild fork,pid is %dn",getpid()); sleep(1); } } return 0; }
这里我们可以看到父子进程同时运行,在争夺CPU的资源
我们来了解一下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 %dn",getpid()); sleep(1); printf("cnt = %dn",cnt); } } else if(pid == 0) { while(1) { printf("this is child fork,pid is %dn",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 %dn",getpid()); sleep(1); printf("cnt = %dn",cnt); } } else if(pid == 0) { while(1) { printf("this is child fork,pid is %dn",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 %dn",WEXITSTATUS(status)); printf("this is father fork,pid is %dn",getpid()); sleep(1); printf("cnt = %dn",cnt); } } else if(pid == 0) { while(1) { printf("this is child fork,pid is %dn",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); while(1) { printf("the status is %dn",WEXITSTATUS(status)); printf("this is father fork,pid is %dn",getpid()); sleep(1); printf("cnt = %dn",cnt); } } else if(pid == 0) { while(1) { printf("this is child fork,pid is %dn",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 %dn",getpid()); sleep(1); } else if(pid == 0) { while(1) { printf("this is child fork pid is %d ,my father pid is %dn",getpid(),getppid()); sleep(1); cnt++; if(cnt == 3) { exit(0);//正常退出 } } } return 0; }
首先父进程先运行,子进程与父进程同时运行,过一会父进程退出,此时子进程变为孤儿进程,系统默认把init进程作为子进程的父进程(init进程默认pid为1)。
孤儿进程:父进程如果不等待子进程退出,在子进程结束之前就结束自己的生命,此时子进程叫做孤儿进程,Linux系统避免存在过多孤儿进程,init进程收留孤儿进程,变成孤儿进程的父进程。
exec族函数作用:在调用一个进程中,转而调用另一个进程。
这段代码我们在自己写cp指令时用到过,作用是打印出命令行所有指令。
#includeint main(int argc,char *argv[]) { int i = 0; for(i = 0; i < argc; i++) { printf("argv[%d]: %sn",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 execln"); if(execl("./echoarg","echoarg","abc",NULL) == -1) { printf("execl failed!n"); perror("why"); //如果execl返回出错,返回了一个error,可以被这perror( )解析出来 } printf("after execln"); return 0; }
ls是否也可被执行呢?我们用whereis ls查看ls绝对路径。
#include#include #include //函数原型:int execl(const char *path, const char *arg, ...); int main(void) { printf("before execln"); if(execl("/bin/ls","ls",NULL,NULL) == -1) { printf("execl failed!n"); perror("why"); //如果execl返回出错,返回了一个error,可以被这perror( )解析出来 } printf("after execln"); 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 execln"); 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 execln"); return 0; }
通过环境变量自动找到date命令。
fork和exce一起使用:这里我们用检测输入的方式调用子进程,创建好shell脚本,作用为改变文本配置,子进程为调用exce函数,通过这个函数来调用changedata脚本。
#include#include #include int main() { pid_t pid; int data; while(1)//父进程一直存在检测用户输入 { printf("please input a numnern"); 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 nothingn"); } } 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 errorn"); 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 Foundn"); 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 %sn",n_read,ret); return 0; }



