Linux c编程之进程的基本操作总结
进程是程序执行和资源管理的最小单元,可以说运行的程序就是进程,了解并掌握进程的基本操作是十分重要的。
1.Linux进程的基本操作
每个进程都是有“生老病死”的过程的,如同的进程及几种状态(就绪状态、运行状态、阻塞状态、僵尸状态、停止状态如下图):
因此掌握进程的基本操作是十分重要的
进程的主要操作:
(1)创建进程
使用fork函数创建一个子进程,
fork的函数原型:pid_t fork(void)
fork函数运行一次返回两次,在父进程中返回子进程的PID(进程的标识编号,PID是非零正整数),在子进程中返回0,因此通常是这样使用:
但要注意的是使用fork函数创建的子进程与父进程是两个独立的进程,并且子进程会复制父进程的内存空间的基本上所有的数据,只有少数数据(如定时器、操作信号等)不会复制,子进程会从fork函数的返回处继续向下运行,如下图所示:
如下代码中子进程对a进行了修改,但并不影响父进程中对a输出时的值
输出的结果为:a:100
因此可见父进程和子进程是两个独立的进程,不共享内存空间。
使用vfork函数也可以创建一个新的子进程
vfork的函数原型:pid_t vfork(void)
但与fork函数不同的是,vfork函数创建子进程后,子进程与父进程共享内存空间,此时子进程与父进程会相互影响,直到子进程中调用exec族函数运行一个新的程序或者使用exit()函数退出时,系统才会将子进程与父进程的内存独立开来,如下代码,在子进程中对a修改为99后再通过exec族函数运行ls指令,父进程中对a进行输出
运行结果为:
父进程中a已经被修改为99了
(2)进程退出
进程中可以在main函数中使用return语句退出,也可以使用exit函数或者_exit函数退出,函数原型为:
void exit(int status) /void _exit(int status)
exit/_exit的参数为退出的状态码,该状态码可以传递给等待该进程的wait/waitpid函数,或者返回给操作系统,该状态码虽然是int类型,但是最好在0~255或者-127到127之间,man手册中有相关说明:
状态码status会与0377(八进制数)按位与后返回给父进程,并可以被wait家族函数接收,0377的十进制数为255,即0377只占一个字节,因此状态码status的数据最好不要超过一个字节。
exit函数与_exit函数的区别为exit函数会刷新IO缓存区,而_exit函数则直接退出
(3)进程等待
wait函数:
在产生一个子进程时,子进程可能处理完某些事情后将结果返回给父进程,此时如果父进程比子进程先结束,则父进程无法获取到子进程的结果,因此有等待子进程的必要,使用wait函数等待一个子进程结束,wait的函数原型为:
pid_t wait(int* status)
wait函数会等待一个子进程结束,如果子进程没有结束wait函数会一直阻塞父进程直到一个子进程结束,wait函数的参数为输出型参数,用来获取所等待的进程的exit/_exit函数中的状态码参数,要想正确获取该状态码需要使用WEXITSTATUS宏进行转换,wait函数返回值为所等待的子进程的PID。
如下代码为子进程统计指定目录下文件与目录的个数,通过exit函数返回给父进程中,父进程对目录的个数进行输出:
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <dirent.h>
#include <sys/wait.h>
#include <stdlib.h>
int getDirNums(char* path)
{
int num = 0;
DIR* dir = opendir(path);
if(!dir)
{
perror("opendir");
return 0;
}
struct dirent* ent;
while(ent = readdir(dir))
{
if(strcmp(ent->d_name,".") == 0 ||
strcmp(ent->d_name,"..") == 0)
{
continue;
}
num++;
}
closedir(dir);
return num;
}
int main(int argc,char* argv[])
{
char path[255] = "./";
if(argc >= 2)
{
strcpy(path,argv[0]);
}
pid_t pid = fork();
if(pid < 0)//fork函数错误时返回值为-1
{
perror("fork");
exit(-1);
}
if(pid == 0)//子进程调用函数统计目录下文件与子目录的个数
{
int num = getDirNums(path);
exit(num);
}
else//父进程等待子进程通过exit函数返回个数
{
int status;
wait(&status);
printf("该目录下有%d个文件\n",WEXITSTATUS(status));
}
}
waitpid:
waitpid函数与wait函数类似,但不仅仅用于等待子进程,可以指定进程,该函数的函数原型为:
pid_t waitpid(pid_t pid,int* status,int option)
该函数会阻塞调用进程,直到某个进程退出,pid参数为等待进程的PID,status为输出型参数获取等待进程的退出码,不关注退出码时可直接填入NULL,option为退出方式,一般填0。
关于pid参数手册中有如下解释:
pid不同的取值针对不同种类的进程
关于option参数手册中有如下解释:
wait与waitpid函数的区别为,wait函数只能等待一个子进程,而waitpid可等待任意一个进程。
(4)一个进程中运行一个新的程序
可以使用exec族函数在本进程中执行一个新的可执行文件,比如一些shell命令、自己编写的程序等,一旦调用exec族函数执行了新的可执行文件,该进程的堆栈空间、代码段等都会被新的进程所覆盖。
exec族函数主要有如下:
execl系列的函数:
int execl(const char *path, const char *arg, ...);
第一个参数为可执行程序的绝对路径,后边的参数为命令行参数,以程序名开始,最后以NULL作为最后一个参数
例如:execl(“/bin/ls","ls","-a",NULL);其中"-a"参数可以没有,但是”ls"与NULL一定要有
int execlp(const char *file, const char *arg, ...);
第一个参数为可执行程序的文件名,后边的参数与execl函数一样,但此时没有指定可执行程序的绝对路径,是在环境变量PATH的目录下查找该程序,可在终端使用env指令查看环境变量PATH中的目录:
int execle(const char *path, const char *arg, ..., char *const envp[]);
该函数第一个参数为指定可执行文件的绝对路径,后边的参数为命令行参数,最后一个参数可通过定义环境变量来传递字符串参数,参数往往在子进程调用execle函数之前传递。
演示如下,先编译一个名为test的可执行程序,可执行程序test的源代码如下(wait函数中示例代码稍作修改),在test中获取环境变量的值,作为统计文件/目录个数的路径:
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <dirent.h>
#include <sys/wait.h>
#include <stdlib.h>
int getDirNums(const char* path)
{
int num = 0;
DIR* dir = opendir(path);
if(!dir)
{
perror("opendir");
return 0;
}
struct dirent* ent;
while(ent = readdir(dir))
{
if(strcmp(ent->d_name,".") == 0 ||
strcmp(ent->d_name,"..") == 0)
{
continue;
}
num++;
}
closedir(dir);
return num;
}
int main()
{
const char* path = getenv("PATH");
pid_t pid = fork();
if(pid < 0)
{
perror("fork");
exit(-1);
}
if(pid == 0)
{
int num = getDirNums(path);
exit(num);
}
else
{
int status;
wait(&status);
printf("该目录下有%d个文件\n",WEXITSTATUS(status));
}
}
再使用另外一个程序test2中,使用execle传递环境变量给test程序作为目录的路径:
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#define PATH "/home/gec/test"
#define ENV "PATH=/"
#define PROCNAME "test"
int main(void)
{
pid_t pid = fork();
if(pid < 0)
{
perror("fork");
return -1;f
}
if(pid == 0)
{
char* const argp[] = {ENV,NULL};//定义环境变量PATH并赋值为./
execle(PATH,PROCNAME,NULL,argp);
}
else
{
wait(NULL);
}
}
该程序为统计根目录下文件与目录的个数
以上三个函数中,参数时通过列表list方式传递,但也可以通过字符指针数组来传递:
execv系列的函数:
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[]);
使用方法与execl系列的函数相同,唯一不同的就是将一个一个的参数直接用一个字符数组传递。
以上就为进程的基本操作总结,如有什么不正确之处还望指正!