Linux c编程之进程的基本操作总结

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系列的函数相同,唯一不同的就是将一个一个的参数直接用一个字符数组传递。

 

 

以上就为进程的基本操作总结,如有什么不正确之处还望指正!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值