linux进程基础与exec族函数简单使用
前面一章节学习了linux下的文件编程,对linux的简单操作也算是有了一点点认识,该章分享记录的是linux下进程的创建和使用场景,以及exec这个庞大的家族,当然对不同的新知识我的学习方式是,先认识,会自己去查找相关资料、将常见的一些函数操作自己可以写小demo来演示,这样自己对该知识点的掌握才会更加深刻。(常见Linux下man 手册来查看不同函数的原型)
文章目录
前言
该章对linux下进程的基础使用和exec函数族进行简单分析,记录自己学习的同时希望也可以帮助到其他小伙伴。提示:以下是本篇文章正文内容,下面案例可供参考
一、进程相关概念
从用户的角度来看:进程是程序的一次执行过程。
从操作系统的核心来看:进程是操作系统分配的内存、CPU时间片等资源的基本单位。
进程是资源分配的最小单位,每一个进程都有自己独立的地址空间与执行状态。像Linux这样的多任务操作系统能够让许多程序同时运行,每一个运行着的程序就构成了一个进程。
二、进程创建函数fork
2.1 fork实验一
下面以一个简单的demo进行本章内容的展开(进程的创建)fork()
函数原型
pid_t fork(void);
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
pid_t pid;
pid = getpid(); //获取进程pid
//fork一次有两个返回值 大于0父进程 等于0为子进程
printf("start pid:%d\n",pid);
fork(); //创建进程
//getpid() 获取当前进程的Pid号
//如果当前获取的pid与fork之前的Pid相等说明为父进程,否则为子进程的Pid
if(pid == getpid())
{
printf("this is father pid:%d\n",pid);
}
else
{
printf("this is child pid:%d\n",getpid());
}
return 0;
}
实验结果
2.2 fork实验二
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
pid_t pid;
pid_t pid2;
pid_t ret_pid;
pid = getpid(); //获取进程pid
//fork一次有两个返回值 大于0父进程 等于0为子进程
printf("before fork pid:%d\n",pid);
ret_pid = fork(); //创建进程
pid2 = getpid();
printf("after fork pid:%d\n",pid2);
if(pid == pid2) //父进程
{
printf("this is father pid:%d retpid:%d\n",getpid(),ret_pid);
}
else //子进程
{
printf("this is child pid:%d retpid:%d\n",getpid(),ret_pid);
}
return 0;
}
实验结果
从上面的实验中我们可以看出当fork创建进程成功的时候会返回两个值一个为大于的值返回给父进程也就是子进程的pid号,而子进程中返回的Pid号为0
三、创建新进程的实际应用(模拟客户端接入)
现在我们模拟这样一种场景、服务端一直等待客户端的接入,当我们客服端输入为1 时接入服务端,接入成功后fork创建子进程在子进程中一直等待客户端的命令执行并将当前子进程的pid打印出来,如果输入不为1 则打印等待什么都不做。
后面进行socket网络编程的时候也会再次使用到fork。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
//模拟服务器 客户端接入
int main(void)
{
pid_t pid;
int data = 0;
while(1)
{
printf("please input data:");
scanf("%d",&data);
//当我们输入为1时接入
if(data == 1)
{
pid = fork(); //创建进程
if(pid > 0) //父进程中什么都不做
{
}
else if(pid == 0) //子进程中获取请求并将当前子进程的pid打印出来
{
while(1)
{
printf("do net request...,pid = %d\n",getpid());
sleep(3); //每睡眠3s打印一次
}
}
}
else
{
printf("wait do nothing\n");
}
}
return 0;
}
实验结果
从上面结果中我们可以明显的看出当我们每输入一次1 的时候就会创建一个子进程,并且子进程运行时,我们照样可以进行命令的输入,这就是进程一个很好的使用。每个进程之间互不干扰。
四、vfork创建进程
函数原型
pid_t vfork(void);
vfork和fork的函数原型可以说的一样的只是各自使用时的功能不相同,下面以demo进行演示我们就可以清楚的看出vfork和fork的差异之处。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
int count = 0;
pid_t pid;
//vfork 直接使用父进程存储空间不拷贝
//vfokr保证子进程先运行,当子进程调用exit退出后,父进程才执行
pid = vfork();
if(pid > 0)
{
while(1)
{
printf("this is father pid:%d\n",getpid());
sleep(1);
printf("count = %d\n",count);
}
}
else if(pid == 0)
{
while(1)
{
printf("this is child pid:%d count:%d\n",getpid(),count);
sleep(1);
if(count++ == 5)
{
exit(0);
}
}
}
return 0;
}
vfork实验结果
fork实验结果
上面两种不同的结果分别是调用vfork和fork的现象,清楚的可以得出一个结论是当我们使用vfork 来创建子进程是要等我们子进程执行结束后父进程才开始执行,而fork创建进程时父进程和子进程是一种争夺资源的关系,谁都有可能先执行。为什么会有这两种不同的创建方式,也是适合不同场景的应用吧,学习过程中搞清楚两个的区别就可以了。
五、进程退出
函数原型
void exit(int status);
status为不同的状态 具体可以进行man手册查看
在上面试验中就已经应用了进程退出的函数exit(0); 该函数的作用就是结束摸个进程的运行,当然进程的结束也会分正常退出、异常退出和其他的,后面状态不同的参数也表示不同的退出状态。
六、exec族函数
所谓exec函数族,其实有六种以exec开头的函数,统称exec函数:
int execl(const char *path, const char *arg, …);
int execlp(const char *file, const char *arg, …);
int execle(const char *path, const char *arg, …, char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
exec函数一旦调用成功即执行新的程序,不返回。只有失败才返回,错误值-1。所以通常我们直接在exec函数调用后直接调用perror()和exit(),无需if判断。
l (list) 命令行参数列表
p (path) 搜素file时使用path变量
v (vector) 使用命令行参数数组
e (environment) 使用环境变量数组,不使用进程原有的环境变量,设置新加载程序运行的环境变量
在这六个exec函数族中后缀为e的很少用到l 和p的一般居多,下面也只是进行常用的函数族进行演示。
6.1 execl
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(void)
{
printf("before execl\n");
//在当前路径下执行echoarg程序 如果失败返回-1 execl末尾已NULL结尾
/*
if(execl("./echoarg","echoarg","linux",NULL) == -1)
{
printf("execl failed\n");
perror("why");
}
*/
if(execl("/bin/ls","ls","-l",NULL) == -1)
{
printf("execl failed\n");
perror("why");
}
printf("execl after\n"); //调用成功 该语句不执行
return 0;
}
运行结果
6.2 execlp
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(void)
{
printf("before execlp\n");
//后面加上P的可以不加绝对路径 会去环境变量下找该指令
if(execlp("ls","ls","-l",NULL) == -1)
{
printf("execl failed\n");
perror("why");
}
printf("execlp after\n"); //调用成功 该语句不执行
return 0;
}
6.1和6.2实验的结果完全一样,只是在使用时调用的exec函数族不一样。里面所传入参数不同,execl使用时需要将绝对路径写出来,而execlp使用时不用写绝对路径
下面进行一个fork和exec族函数配合使用进行config,txt中数据的指定修改将LENGTH=5修改为LENGTH=9
修改文件的编写
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <error.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc ,char *argv[])
{
int src = 0;
char *ReadBuf = NULL; //缓冲空间
char *Result = NULL;
int file_size = 0;//文件大小
if(argc != 2)
{
perror("param \n");
exit(-1);
}
//打开源文件
src = open(argv[1],O_RDWR);
if(src == -1)
{
printf("open src file failed\n");
}
//先计算文件大小
file_size = lseek(src,0,SEEK_END);
//将文件描述符移值开头
lseek(src,0,SEEK_SET);
//为readbuf开辟空间
ReadBuf = (char *)malloc(sizeof(char)*file_size + 8);
//将文件内容读完到Readbuf
read(src,ReadBuf,file_size);
//与目标字符串进行比较
Result= strstr(ReadBuf,"LENGTH=");
if(NULL == Result)
{
printf("not find \n");
exit(-1);
}
Result = Result + strlen("LENGTH="); //指针后移
//修改值
*Result = '9';
lseek(src,0,SEEK_SET);
//将修改后的值重新写入文件
write(src,ReadBuf,strlen(ReadBuf));
//关闭文件描述符
close(src);
return 0;
}
fork和exec结合使用
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(void)
{
pid_t pid;
int data = 0;
while(1)
{
printf("please input data:");
scanf("%d",&data);
if(data == 1)
{
pid = fork();
if(pid > 0)
{
wait(NULL);
}
else if(pid == 0)
{
execl("./changeDat","changeDat","config.txt",NULL);
exit(0);
}
}
else
{
printf("wait do nothing\n");
}
}
return 0;
}
七、system函数
函数原型
int system(const char *command);
基于上面实验将execl换成system函数的实现就可以了
system("./changeDat config.txt");
八、popen函数
函数原型
FILE *popen(const char *command, const char *type);
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
FILE *fp;
char Readbuf[1024]= {
0};
//popen 函数将读取到的数据缓存出来
//FILE *popen(const char *command, const char *type);
fp = popen("ps","r");
fread(Readbuf,sizeof(char),sizeof(Readbuf),fp);
printf("Readbuf = %s\n",Readbuf);
return 0;
}
运行结果
popen函数的好处就是可以将执行后的结果通过文件流打印出来
总结
以上就是对linux下的进程创建使用和exec函数族的部分使用分享,学会如何输入更应该学会如何输出,加油。