Linux系统编程:进程

本文详细介绍了Linux系统中的相关进程管理函数,如getpid获取线程ID,fork创建子进程,vfork共享内存,exit和wait处理进程结束,以及exec家族函数如execl,execlp,和popen用于执行外部命令。还讨论了孤儿进程的概念和环境变量的管理。
摘要由CSDN通过智能技术生成

目录

相关指令

ps指令

top指令       

相关函数及其示例

getpid()

 getpid()示例:

fork()

 示例:

示例2:fork()返回值

vfork()

 示例:与fork()区分

exit();_exit();_Exit();

abort()

wait()

示例:使用fork();wait();exit();

waitpid()

概念:孤儿进程

示例:getppid()可以获取父进程id

exec族函数

execl() 

示例:调用一个不存在的文件

示例:使用execl()调用ls指令

示例:获取系统时间 

execlp()

示例:execlp()调用ps指令

如何添加文件到环境变量呢?

execv()

system()

popen()

示例:使用popen()调用ls指令并获取其输出结果 


相关指令

ps指令

ps -aux        查看所有进程

ps -aux|grep init        筛选所有名字带init的进程

top指令       

类似win的任务管理器

相关函数及其示例

getpid()

获取当前线程的id

AME
       getpid, getppid - get process identification

SYNOPSIS
       #include <sys/types.h>
       #include <unistd.h>

       pid_t getpid(void);
       pid_t getppid(void);

 getpid()示例:

 #include <stdio.h>
 #include <sys/types.h>
 #include <unistd.h>

 int main(){
    pid_t pid;
    pid = getpid();

    printf("My pid is %d.\n",pid);

    return 0;
 }

输出:

My pid is 14142.

fork()

在调用出生成一个子进程。过去,创建子进程会直接复制一份父进程的内存空间,但后来经过改进,父子进程在存储空间上使用写时拷贝,即子进程对父进程的内存空间为只读,当需要操作某内存时,会将其拷贝。返回值0则为子进程,非负数父进程,调用失败-1。

NAME
       fork - create a child process

SYNOPSIS
       #include <sys/types.h>
       #include <unistd.h>

       pid_t fork(void);

 示例:

 #include <stdio.h>
 #include <sys/types.h>
 #include <unistd.h>

 int main(){
    pid_t pid;
    pid = getpid();
    fork();
    printf("My pid is %d.\n",pid);

    return 0;
 }

输出
My pid is 14258.
My pid is 14258.

pid在fork之前get,所以两次打印都是父进程pid

所以这样写

 #include <stdio.h>
 #include <sys/types.h>
 #include <unistd.h>

 int main(){
    pid_t pid;
    pid = getpid();
    fork();
    printf("Father's pid is %d.\n",pid);
    printf("My pid is %d.\n",getpid());

    return 0;
 }

输出:

Father's pid is 14289.
My pid is 14289.
Father's pid is 14289.
My pid is 14290.

示例2:fork()返回值

 #include <stdio.h>
 #include <sys/types.h>
 #include <unistd.h>

 int main(){
    pid_t pid,pFork;
    pid = getpid();
    printf("Father's pid is %d.\n",pid);
    pFork = fork();
    if(pFork > 0){
      printf("This is FartherProcess, pid is %d.\n",getpid());
    }else if(!pFork){
      printf("This is SonProcess, pid is %d.\n",getpid());
    }
    
    return 0;
 }

输出:

Father's pid is 14532.
This is FartherProcess, pid is 14532.
This is SonProcess, pid is 14533.
 

vfork()

vfork直接使用父进程内存空间,不拷贝。vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行。

AME
       vfork - create a child process and block parent

SYNOPSIS
       #include <sys/types.h>
       #include <unistd.h>

       pid_t vfork(void);

   Feature    Test   Macro   Requirements   for   glibc   (see   fea‐
   ture_test_macros(7)):

       vfork():
           Since glibc 2.12:
               (_XOPEN_SOURCE >= 500) && ! (_POSIX_C_SOURCE >= 200809L)
                   || /* Since glibc 2.19: */ _DEFAULT_SOURCE
                   || /* Glibc versions <= 2.19: */ _BSD_SOURCE
           Before glibc 2.12:
               _BSD_SOURCE || _XOPEN_SOURCE >= 500

 示例:与fork()区分

使用fork():

 #include <stdio.h>
 #include <sys/types.h>
 #include <unistd.h>

 int main(){
    pid_t pid,pFork;
    pid = getpid();
    printf("Father's pid is %d.\n",pid);
    pFork = fork();
    while(1){
      sleep(1);
      if(pFork > 0){
        printf("This is FartherProcess, pid is %d.\n",getpid());
      }else if(!pFork){
        printf("This is SonProcess, pid is %d.\n",getpid());
      }
      
    }
    return 0;
 }

输出:都可以运行 

查看进程 都为S+ ,都在运行

若改为使用vfork(),让子进程运行三秒退出:

 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/types.h>
 #include <unistd.h>


 int main(){
  int cnt = 0;
  pid_t pid,pFork;
  pid = getpid();
  printf("Father's pid is %d.\n",pid);
  pFork = vfork();
  while(1){
    sleep(1);
    if(pFork > 0){
      printf("This is FartherProcess, pid is %d.\n",getpid());
    }else if(!pFork){
      printf("This is SonProcess, pid is %d.\n",getpid());
      cnt++;
      if(cnt == 3)  exit(0);
    }
    
  }
  return 0;
 }

运行:子进程运行了3次,子进程退出之前父进程阻塞,子进程退出之后,父进程才开始运行。

但是可以注意到一个现象,子进程退出后,子进程会变为Z+,即Zombie僵尸进程。

所以引出我们在进程编程中的一个问题,即 父进程等待子进程退出 并收集子进程退出状态 ,如果子进程退出状态不被收集,变成僵尸进程。

exit();_exit();_Exit();

NAME
       exit - cause normal process termination

SYNOPSIS
       #include <stdlib.h>

       void exit(int status);
NAME
       _exit, _Exit - terminate the calling process

SYNOPSIS
       #include <unistd.h>

       void _exit(int status);

       #include <stdlib.h>

       void _Exit(int status);

   Feature    Test   Macro   Requirements   for   glibc   (see   fea‐
   ture_test_macros(7)):

       _Exit():
           _ISOC99_SOURCE || _POSIX_C_SOURCE >= 200112L

abort()

NAME
       abort - cause abnormal process termination

SYNOPSIS
       #include <stdlib.h>

       void abort(void);

wait()

NAME
       wait, waitpid, waitid - wait for process to change state

SYNOPSIS
       #include <sys/types.h>
       #include <sys/wait.h>

       pid_t wait(int *wstatus);

       pid_t waitpid(pid_t pid, int *wstatus, int options);

       int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
                       /* This is the glibc and POSIX interface; see
                          NOTES for information on the raw system call. */

示例:使用fork();wait();exit();

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

int main(){
  int cnt = 0;
  pid_t pid,pFork;
  pid = getpid();
  printf("Father's pid is %d.\n",pid);
  pFork = fork();
  wait(NULL);
  while(1){
    sleep(1);
    if(pFork > 0){
      printf("This is FartherProcess, pid is %d.\n",getpid());
    }else if(!pFork){
      printf("This is SonProcess, pid is %d.\n",getpid());
      cnt++;
      if(cnt == 3)  exit(0);
    }
  }
  return 0;
}

现象:使用fork后正常应该父子进程都在运行,但是因为wait,所以父进程等待子进程退出。

(如果使用waitpid(),一个选项可以使父进程不阻塞) 

也不会产生僵尸进程


那么,status这个参数又是什么?

status为状态参数:子进程退出状态放在它所指向的地址中,空则为不关心其退出状态 

示例:让我们试着传递status,记得想读status,要用 WEXITSTATUS() 这个宏。

关于这个宏不多看,man 2 wait,就知道为什么了,可以顺带看看其它宏干嘛的

代码:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

int main(){
  int cnt = 0;
  int status = 0;
  pid_t pid,pFork;
  pid = getpid();
  printf("Father's pid is %d.\n",pid);
  pFork = fork();
  wait(&status);
  if(pFork > 0) printf("ChildProcess exit, exit_status : %d.\n",WEXITSTATUS(status));
  while(1){
    sleep(1);
    if(pFork > 0){
      
      printf("This is FartherProcess, pid is %d.\n",getpid());
    }else if(!pFork){
      printf("This is SonProcess, pid is %d.\n",getpid());
      cnt++;
      if(cnt == 3)  exit(3);
    }
  }
  return 0;
}

运行:读取到状态值为3

waitpid()

上面刚刚提到了使用waitpid(),一个选项可以使父进程不阻塞

同理man 2 wait,option怎么填,WNOHANG就是不阻塞,大家可以自行实验。

       The value of options is an OR of zero or more of the following constants:

       WNOHANG
              return immediately if no child has exited.

       WUNTRACED
              also return if a child has stopped (but not traced via ptrace(2)).  Status for traced children which have  stopped  is
              provided even if this option is not specified.

       WCONTINUED (since Linux 2.6.10)
              also return if a stopped child has been resumed by delivery of SIGCONT.

       (For Linux-only options, see below.)

概念:孤儿进程

父进程在子进程退出之前就退出了,子进程会变为孤儿进程;Linux为避免系统存在过多孤儿进程,init进程将收留孤儿进程,变成其父进程。

示例:getppid()可以获取父进程id

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

int main(){
  int cnt = 0;
  int status = 0;
  pid_t pid,pFork;
  pid = getpid();
  printf("Father's pid is %d.\n",pid);
  pFork = fork();
  while(1){
    sleep(1);
    if(pFork > 0){
      cnt++;
      if(cnt == 3)  exit(0);
    }else if(!pFork){
      printf("SonProcess' pid is %d,my father's pid : %d.\n",getpid(),getppid());
    }
  }
  return 0;
}

现象:父进程退出后,子进程的父进程变成了1519 

kill ‘pid’        指令可以终止这个子进程 

exec族函数

Linux系统编程中很重要的一系列函数

linux进程---exec族函数(execl, execlp, execle, execv, execvp, execvpe)_分别用execlp()、execl()和execv()函数实现命令“find / -name abc-CSDN博客

 总结来说就是在调用进程内部执行一个文件

NAME
       execl, execlp, execle, execv, execvp, execvpe - execute a file

SYNOPSIS
       #include <unistd.h>

       extern char **environ;

       int execl(const char *pathname, const char *arg, ...
                       /* (char  *) NULL */);
       int execlp(const char *file, const char *arg, ...
                       /* (char  *) NULL */);
       int execle(const char *pathname, const char *arg, ...
                       /*, (char *) NULL, char *const envp[] */);
       int execv(const char *pathname, char *const argv[]);
       int execvp(const char *file, char *const argv[]);
       int execvpe(const char *file, char *const argv[],
                       char *const envp[]);

execle和execvpe初学用的少,涉及到对环境变量的修改。

exec函数族的函数执行成功后不会返回,调用失败时,会设置errno并返回-1,然后从原程序的调用点接着往下执行。

参数说明:
path:可执行文件的路径名字
arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。

exec族函数参数极难记忆和分辨,函数名中的字符会给我们一些帮助:
l :   使用参数列表
p:使用文件名,并从PATH环境进行寻找可执行文件
v:应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。
e:多了envp[]数组,使用新的环境变量代替调用进程的环境变量

execl() 

需要文件路径,不知道可以使用 whereis XXX 指令来寻找 

示例:调用一个不存在的文件

使用了execl() 和 perror()

//文件execl.c
#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("/bin/echoarg","echoarg",NULL,NULL) == -1)
    {
        printf("execl failed!\n");
        perror("why");
    }
    printf("after execl\n");
    return 0;
}

输出:

before execl
execl failed!
why: No such file or directory
after execl

示例:使用execl()调用ls指令

//文件execl.c
#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("/bin/ls","ls",NULL,NULL) == -1)
    {
        printf("execl failed!\n");
        perror("why");
    }
    printf("after execl\n");
    return 0;
}

运行:成功调用ls,并且 after execl为被打印,因为调用之后进程被调用文件取代

示例:获取系统时间 

将ls换为date即可,可以看见执行了date指令

execlp()

可以直接在环境变量中寻找,不需要加路径。

示例:execlp()调用ps指令

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
int main(void)
{
    printf("before execlp\n");
 
    if(execlp("ps","ps","-l",NULL) == -1){
        printf("execl failed!\n");
        perror("why");
    }
    printf("after execlp\n");
 
    return 0;
}

如何添加文件到环境变量呢?

echo $PATH        查看环境变量

pwd        查看当前目录

export PATH=$PATH:你要添加到环境变量的目录

 可以看到添加了之后,我就可以用我自己实现的cp指令复制文件了(此实现详见我上一篇文章 文件编程)

execv()

区别在于使用数组指针传参

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
int main(void)
{
    char *argv[] = {"cp", "file", "file1",NULL};
    printf("before execv\n");

    if(execv("/bin/cp",argv) == -1){
        printf("execv failed!\n");
        perror("why");
    }
    printf("after execv\n");
 
    return 0;
}

system()

更加简单粗暴

system()函数的返回值如下: 成功,则返回进程的状态值; 当sh不能执行时,返回127; 失败返回-1;

NAME
       system - execute a shell command

SYNOPSIS
       #include <stdlib.h>

       int system(const char *command);

示例:使用system()调用rm指令删除一个文件

#include <stdio.h>
#include <stdlib.h>
 
int main(void)
{
    if(system("rm file1") == -1){
        printf("fail\n");
    }
    return 0;
}

popen()

比system()的优势:可以获取程序执行的结果

popen() 函数通过创建一个管道,调用 fork 产生一个子进程,执行一个 shell 以运行命令来开启一个进程。

这个进程必须由 pclose() 函数关闭,而不是 fclose() 函数。pclose() 函数关闭标准 I/O 流,等待命令执行结束,然后返回 shell 的终止状态。如果 shell 不能被执行,则 pclose() 返回的终止状态与 shell 已执行 exit 一样。

也就是,popen创建管道,执行shell命令将文件流中的某些数据读出

NAME
       popen, pclose - pipe stream to or from a process

SYNOPSIS
       #include <stdio.h>

       FILE *popen(const char *command, const char *type);

       int pclose(FILE *stream);

参数说明:

command: 是一个指向以 NULL 结束的 shell 命令字符串的指针。命令将被传到 bin/sh 并使用 -c 标志,shell 将执行这个命令,比如sh -c ls

type: 只能是读或者写中的一种,得到的返回值(标准 I/O 流)也具有和 type 相应的只读或只写类型。如果 type 是 “r” 则文件指针连接到 command 的标准输出;如果 type 是 “w” 则文件指针连接到 command 的标准输入。

返回值:
如果调用 fork() 或 pipe() 失败,或者不能分配内存将返回NULL,否则返回一个读或者打开文件的指针。

示例:使用popen()调用ls指令并获取其输出结果 

#include <stdio.h>
#include <unistd.h>

int main(int argc ,char **argv){
	
	char ret[1024] = {0};
	FILE *fp;

	//FILE *popen(const char *command, const char *type);
	fp = popen("ls -l","r");

	//size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
	int nread = fread(ret,1,1024,fp);
	printf("read ret %d byte,\n%s\n",nread,ret);
	
	return 0;
}

运行结果 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值