Linux进程
进程相关概念、创建进程fork()、进程退出exit()、等待函数wait()、exec族、system函数、popen函数
一、进程相关概念
问1. 什么是程序,什么是进程,有什么区别?
程序是静态的概念,gcc xxx.c –o pro,磁盘中生成pro文件。
进程是程序的一次运行活动,通俗点意思是程序跑起来了,系统中就多了一个进程。
问2. 如何查看系统中有哪些进程?
a. ps -aux|grep +(程序名)
b. top指令查看,类似于任务管理器。
问3. 什么是进程标识符?
每个进程都有一个非负整数表示的唯一ID,叫做pid,类似身份证。
特别地:
Pid=0: 称为交换进程(swapper)作用—进程调度
Pid=1:init进程作用—系统初始化。
获取pid值:
调用getpid函数获取自身的进程标识符。
getppid获取父进程的进程标识符。
问4. 什么叫父进程,什么叫子进程?
进程A创建了进程B。那么A叫做父进程,B叫做子进程,
问5. C程序的存储空间是如何分配?

正文段:代码段
初始化的数据:数据段
未初始化的数据:bss段通常是用来存放程序中未初始化的全局变量和静态变量
堆:malloc等函数申请临时空间
栈:函数调用、局部变量
二、创建进程
fork头文件和函数
// 头文件
#include <unistd.h>
#include <sys/types.h>
//函数
pid_t fork(void);
//返回:success 返回两次 返回值为0 代表当前进程是子进程
// 返回值为非负数 代表当前进程是父进程,返回值为子进程ID
pid_t 实质是 int 被定义在 #include<sys/types.h>
fork创建子进程的目的
-
一个父进程复制自己,父、子进程同时执行不同代码段。父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork。使得子进程处理这种请求,父进程继续下一请求。
-
一个进程执行不同的程序,这是shell常见的情况,这种情况下fork返回立即调用exec。
fork写时拷贝
Linux的fork()使用写时拷贝(copy-on-write)页实现。写时拷贝是一种可以推迟甚至避免拷贝数据的技术。内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间。只用在需要写入的时候才会复制地址空间,从而使各个进行拥有各自的地址空间。
也就是说,资源的复制是在需要写入的时候才会进行,在此之前,只有以只读方式共享。这种技术使地址空间上的页的拷贝被推迟到实际发生写入的时候。
vfork与fork的区别
区别一:vfork直接使用父进程储存空间,不拷贝。
区别二:vfork保证子进程先运行,当子进程exit后,父进程运行。
区别一举例说明:当子进程修改变量a=10时,那么父进程中的a也改成了10。
三、进程退出
正常退出
- Main函数调用return
- 进程调用exit(),标准C语言库
- 进程调用_exit()或者_Exit(),属于系统调用
- 进程最后一个线程返回
- 最后一个线程调用pthread_exit
异常退出
- 调用abort函数
- 当进程收到某些信号时,如ctrl+C
- 最后一个线程对取消(cancellation)请求做出响应
正常线程退出使用pthread_exit(),进程退出调用exit()。不论时正常还是异常退出状态下,子进程需要传参父进程,使得父进程知道子进程运行状态,将运行状态status传递给wait或者waitpid参数。
下面展示一些 exit代码片。
// exit头文件
#include <stdlib.h>
//exit主函数
void exit(int status);
//_exit头文件
#include <unistd.h>
//_exit主函数
void _exit(int status);
//_Exit头文件
#include <stdlib.h>
//Exit主函数
void Exit(int status);
四、进程等待
进程等待函数的意义:父进程等待子进程退出,并收集子进程的退出状态。如果子进程的退出状态不被收集,变成僵尸进程(zombie)。
下面展示一些 wait()、waitpid()头文件和主函数。
// 头文件
#include <sys/types.h>
#include <sys/wait.h>
//主函数
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
//返回
// success 返回 子进程ID ; error 返回 -1
下面展示一些 参数解释。
status:是一个整型数指针
非空:子进程退出状态放在它所指向的地址中。
空:不关心退出状态。
一般想要获取status的返回值,所用函数为WEXITSTATUS(status)。举例说明,如果检测子进程exit(3),可以得到WEXITSTATUS(status)=3,用法如下:
wait(status);
printf(“father process, status = %d\n”,WEXITSTATUS(status));
pid_t pid的取值
pid==-1,等待任一子进程,此时和wait()等效。
pid>0,等待与pid值一致的子进程。
pid==0,等到其组ID和调用进程的组ID相等的任一子进程。
pid<-1,等到其组ID和pid绝对值相等的任一子进程。
options的取值可以取0,或者取下列组合:
WCONTINUED:pid指定的任一子进程在暂停后已经继续,但其状态尚未报告,则返回其状态。
WNOHANG:常用,waitpid将不阻塞如果指定的pid并未结束。
WUNTRACED:如果子进程进入暂停执行情况则马上返回,但结束状态不予以理会。
孤儿进程
含义:父进程先结束,子进程后结束。子进程就变成了孤儿进程。
Linux处理方法:Linux避免存在过多孤儿进程,init进程收留孤儿进程,变成孤儿进程的父进程。
**pr_exit(status)**打印status的数值
下面展示一些 pr_exit()代码段。
// pr_exit()函数
void pr_exit(int status)
{
if(WIFEXITED(status)){
printf("normal termination, exit status = %d\n",WEXITSTATUS(status));
}elseif (WIFESIGNALED(status)){
printf("abnormal termination, exit status = %d\n",WTERMSIG(status));
}
}
五、exec族
功能:
在调用进程内部执行一个可执行文件。可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。
当进程调用exec函数时,该进程被完全替换为新程序。因为调用exec函数并不创建新进程,所以前后进程的ID并没有改变。
函数族:
exec函数族分别是:execl, execlp, execle, execv, execvp, execvpe
// 头文件
#include <unistd.h>
//主函数
extern char **environ;
int execl(const char *path, const char *arg, .../* (char *) NULL */);
int execlp(const char *file, const char *arg, .../* (char *) NULL */);
int execle(const char *path, const char *arg, ... /*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
//返回
//success时 不会返回 ; error时 返回 -1 ,然后从原程序的调用点接着往下执行。
参数说明:
path:可执行文件的路径名字
arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。
exec族函数参数极难记忆和分辨,函数名中的字符会给我们一些帮助:
l : 使用参数列表
p:使用文件名,并从PATH环境进行寻找可执行文件
v:应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。
e:多了envp[]数组,使用新的环境变量代替调用进程的环境变量
举例说明 execl。
// A code block
execl("./forkex","forkex","config.txt",NULL);
//第一个参数path代表可执行程序的路径,第二个参数arg代表可执行程序的名字。第三个参数file代表真正的参数,第四个参数必须已NULL结尾
具体程序看后面编程举例
六、system
执行程序两种方法 。 ./11 或者 sh -c ./11
// 头文件
#include <stdlib.h>
//主函数
int system(const char* command);
//返回
//success时 返回进程状态值 ; error时 返回 -1 ,当sh不能执行,返回127
system和exec的区别
- system返回成功后继续执行原始程序的代码。
- exec返回成功后,子进程直接结束。
举例说明 system和exec
//二者等同
system("./changeData config,txt");
excel("./changeData", "changeData" ,"config,txt", NULL);
七、popen
// 头文件
#include <stdio.h>
//主函数
FILE popen(const char* command,const char* type);
//返回
//success时 返回 指向文件指针 ; error时 返回 -1
参数说明:
type:“r” 只读 “w” 只写
函数作用
popen创建一个管道:其内部实现和调用fork,产生一个子进程,执行一个shell命令来开启一个进程,这个进程,这个进程必须由pclose()函数关闭。
与system区别
popen可以获取运行输出结果
举例说明 popen
//二者等同
fp = popen("./changeData abc","r");
feof()
int feof(FILE *stream) 测试给定流 stream 的文件结束标识符。
//头文件
#include<stdio.h>
// 主函数
int feof(FILE *stream)
//返回
//当设置了与流关联的文件结束标识符时,该函数返回一个非零值,否则返回零。
参数说明:
stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
实战举例一:进程等待
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int cnt=0;
pid =fork();
int status=10;
if(pid>0){ //进入父进程
// wait(&status);
waitpid(pid, &status, WNOHANG); //进程等待
while(1){
printf("cnt=%d\n",cnt);
printf("father process , pid =%d,status=%d\n",getpid(),WEXITSTATUS(status));
sleep(1);
}
}else if(pid == 0) { //进入子进程
while(1){
printf("child process , pid =%d\n",getpid());
cnt++;
sleep(1);
if(cnt==4){
exit(5);
}
}
}
return 0;
}
实战举例二:exec应用
echoarg.c
#include <stdio.h>
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函数应用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型: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");
}
printf("after execl\n");
return 0;
}
execlp函数应用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
int main(void)
{
printf("before execl\n");
if(execlp("echoarg","echoarg","abc",NULL) == -1)
{
printf("execl failed!\n");
}
printf("after execl\n");
return 0;
}
execvp函数应用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
int main(void)
{
printf("before execl\n");
char *argv[]={"echoarg","abc","NULL"};
if(execvp("echoarg",argv) == -1){
printf("execl failed!\n");
}
printf("after execl\n");
return 0;
}
system函数应用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
int main(void)
{
if(system("./echoarg abc") == -1)
{
printf("execl failed!\n");
perror("why");
}
printf("after execl\n");
return 0;
}
实战举例三:popen应用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
char ret[1024]={0};
FILE *fp=NULL;
fp=popen("./echoarg abc","r");
int nread = fread(ret,1,1024,fp);
perror("why");
printf("read %d types,ret = %s\n",nread,ret);
return 0;
}
实战举例四:fork和exec联合使用
TEST.config
SPEED=3
LENG=5
SCORE=9
LEVEL=5
主函数
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main()
{
int data=10;
pid_t pid;
while(1){
printf("please input a data\n");
scanf("%d",&data);
if(data==1){
pid==fork();
if(pid>0){
wait(NULL);
}
int fdSrc;
if(pid==0){
execl("./forkex","forkex","config.txt",NULL); //forkex是生成好的可执行程序
exit(0);
}
}
else{
printf("wait, do nothing\n");
}
}
return 0;
}
本文详细介绍了Linux进程管理的相关概念,包括进程的创建(fork)、退出(exit)、等待(wait)、exec族函数以及system和popen函数的使用。通过多个实战例子展示了如何在C语言中运用这些函数,帮助读者深入理解Linux进程操作。

715

被折叠的 条评论
为什么被折叠?



