进程相关API

1、fork函数

  • pid_t fork(void)
  • 返回值: 若成功调用一次则返回两个值,子进程返回0,父进程返回子进程ID;否则,出错返回-1
  • 调用fork函数后,子进程是父进程的一个副本,它将获得父进程数据空间、堆、栈等资源的副本;由于在复制时复制了父进程的堆栈段,所以两个进程都停留在fork函数中,等待返回。调用fork函数后,数据、堆栈共有两份,但是代码段两个进程共享。当父子进程有一个想要修改数据或堆栈时,两个进程真正分裂。
  • 但是由于fork后常常跟着exec,所以现在的很多实现并不执行一个父进程数据的、堆和栈的完全复制,作为替代,使用了写时复制(Copy-On-Write,COW)技术,这些区域由父子进程共享,内核将它们的权限设为只读,如果父子进程中有一个试图修改这些区域,则内核只为修改的那段内存制作副本。
  • 调用fork函数后父子进程哪一个先运行,依赖于系统的实现。

示例代码

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
    pid_t pid;

    pid=fork();

    if(pid>0)
    {
        printf("father fork return %d\n",pid);
        printf("this is the father pid:%d\n",getpid());
    }
    else if(pid==0)
    {
        printf("this is the child return fork %d\n",pid);
        printf("this is the child pid:%d\n",getpid());
    }

    return 0;
}


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

int main()
{
    int data=10;
    pid_t pid =fork();
    if(pid>0)
    {
        printf("return fork:%d\n",pid);
        printf("%d\n",getpid());

    }
    else if(pid==0)
    {
        printf("return fork:%d\n",pid);
        printf("%d\n",getpid());
        data+=100;
    }
    printf("%d\n",data);

    return 0;
}

运行结果
return fork:57080
57079
10
return fork:0
57080
110
父进程创建子进程后,由于子进程是父进程的副本,所以在子进程中修改data的值并不影响父进程中的data。

2、vfork函数

  • pid_t vfork(void);
  • 返回值,如果vfork()成功则在父进程会返回新建立的子进程代码(PID),
    而在新建立的子进程中则返回0。如果vfork失败则直接返回
  • vfork创建的子进程直接使用父进程的内存空间不拷贝,并不是像fork函数一样作为父进程的一个副本,子进程在父进程的栈空间上运行,所以子进程不能进行写操作。
  • 进程使用父进程的内存和数据。这意味着子进程可能破坏父进程的数据结构或栈,造成失败。
  • vfork保证子进程先运行,子进程调用exit退出后父进程才可以运行(因为子进程是使用父进程的栈空间)

3、wait函数

  • 在Linux系统中,一个子进程结束了,但是它的父进程没有等待,则子进程将会成为僵尸进程,此时init进程将会替代成为其父进程,并对子进程进行清除。
  • 在子进程还没有结束前,父进程就已经结束,则子进程变成孤儿进程,将由init进程收留孤儿进程成为子进程的父进程。
  • 为什么子进程会成为僵尸进程呢?
  • 因为在子进程结束后,内核会释放该进程的所有资源,包括打开的文件,占用的内存等,但是仍然会保留一定的信息,包括进程号,退出状态,运行时间等,如果不调用wait/waitpid来获取信息的话,这段信息就不会被释放,而系统的进程号是有限的,如果被大量占用,则可能造成系统的崩溃。僵尸进程是每个子进程都会经过的阶段。
  • 一个进程调用exit结束时,并不是真正的结束,而是会留下一个称为僵尸进程的数据结构。
  • 调用此函数时,如果父进程的所有子进程都在运行,则会阻塞;如果子进程已经终止,正等待父进程从获取其状态,则获取后立即返回;如果没有子进程,则会出错返回。
  • wait调用成功并返回子进程的pid,如果失败则返回-1;
函数原型:
int wait(int* wstatus);
wait函数会将子进程的退出状态记录在status中,
使用WEXITSTATUS(wstatus)宏来解析wstatus的值

4、waitpid函数

  • 函数原型:pid_t waitpid(pid_t pid,int *status,int options);
  • pid<-1 等待进程组识别码为 pid 绝对值的任何子进程。
    pid=-1 等待任何子进程,相当于 wait()。
    pid=0 等待进程组识别码与目前进程相同的任何子进程。
    pid>0 等待任何子进程识别码为 pid 的子进程。
  • 如果不在乎子进程的结束状态,则status可以设置为NULL。
  • 对于options有几种不同的参数
参数功能
WNOHANG若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若结束,则返回该子进程的ID。
WUNTRACED若子进程进入暂停状态,则马上返回,但子进程的结束状态不予以理会。
WCONTINUED将调用此函数的进程挂起,等待一个进程的终止或一个结束的进程收到SIGCONT信号时重新开始执行。
  • 子进程的结束状态返回后存于 status,底下有几个宏可判别结束情况:
  • WIFEXITED(status)如果若为正常结束子进程返回的状态,则为真;对于这种情况可执行WEXITSTATUS(status),取子进程传给exit或_eixt的低8位。
  • WEXITSTATUS(status)取得子进程 exit()返回的结束代码,一般会先用 WIFEXITED 来判断是否正常结束才能使用此宏。
  • WIFSIGNALED(status)若为异常结束子进程返回的状态,则为真;对于这种情况可执行WTERMSIG(status),取使子进程结束的信号编号。
  • WTERMSIG(status) 取得子进程因信号而中止的信号代码,一般会先用 WIFSIGNALED 来判断后才使用此宏。
  • WIFSTOPPED(status) 若为当前暂停子进程返回的状态,则为真;对于这种情况可执行WSTOPSIG(status),取使子进程暂停的信号编号。
  • WSTOPSIG(status) 取得引发子进程暂停的信号代码,一般会先用 WIFSTOPPED 来判断后才使用此宏。
    如果执行成功则返回子进程识别码(PID) ,如果有错误发生则返回
    返回值-1。失败原因存于 errno 中。

5、exec族函数

  • exec函数的原型如下:
    int execl(const char * path,const char * arg,…);
    int execle(const char * path,const char * arg,char * const envp[]);
    int execlp(const char * file,const char * arg,…);
    int execv(const char * path,char * const argv[]);
    int execve(const char * path,char * const argv[],char * const envp[]);
    int execvp(const char * file,char * const argv[]);
  • 参数说明:
    path:要执行的程序路径。可以是绝对路径或者是相对路径。在execv、execve、execl和execle这4个函数中,使用带路径名的文件名作为参数。
    file:要执行的程序名称。如果该参数中包含“/”字符,则视为路径名直接执行;否则视为单独的文件名,系统将根据PATH环境变量指定的路径顺序搜索指定的文件。
    argv:命令行参数的矢量数组。
    envp:带有该参数的exec函数可以在调用时指定一个环境变量数组。其他不带该参数的exec函数则使用调用进程的环境变量。
    arg:程序的第0个参数,即程序名自身。相当于argv[O]。
    …:命令行参数列表。调用相应程序时有多少命令行参数,就需要有多少个输入参数项。注意:在使用此类函数时,在所有命令行参数的最后应该增加一个空的参数项(NULL),表明命令行参数结束。
  • 返回值:一1表明调用exec失败,无返回表明调用成功。

以execl为例:

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


int main()
{
    printf("before execl\n");
    if(execl("./demo","demo",NULL)==-1)
    {
        printf("execl fail!\n");
    }
    printf("after execl!\n");
    return 0;
}
//demo.c
#include<stdio.h>
int main()
{
    printf("hello world!\n");
    return 0;

}                                                                   

execvp:

 #include<stdio.h>
 #include<unistd.h>
 #include<stdlib.h>
 int main()
 {
     char *argv[]={"pwd",NULL};
     printf("before execl\n");
     if(execvp("pwd",argv)==-1)
     {
         printf("execl fail!\n");
     }
     printf("after execl!\n");
     return 0;
 }
                                        

6、system函数

函数源码:

int system(const char * cmdstring)
{
  pid_t pid;
  int status;

  if(cmdstring == NULL){
      
      return (1);
  }


  if((pid = fork())<0){

        status = -1;
  }
  else if(pid == 0){
    execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);//bin/sh 一般是一个软连接,指向某个具体的shell,
    //比如bash,-c选项是告诉shell从字符串command中读取命令;
    exit(127);//子进程正常执行则不会执行此函数
    }
  else{
        while(waitpid(pid, &status, 0) < 0){
          if(errno != EINTER){
            status = -1;
            break;
          }
        }
    }
    return status;
}
  • 在调用函数时,若cmdstring为NULL,则会返回1,若执行成功,system调用fork函数创建子进程来运行execl函数。

7、popen函数

  • 虽然system函数简单粗暴,但是无法获得更多的信息等。popen() 函数通过创建一个管道,调用 fork 产生一个子进程,执行一个 shell 以运行命令来开启一个进程。这个进程必须由 pclose() 函数关闭.

  • poen()函数允许一个程序将另一个程序作为新进程来启动,并可以传递数据给它或者通过它接收数据。command是要运行的程序名和相应的参数。open_mode只能是"r(只读)"和"w(只写)"的其中之一。注意,popen()函数的返回值是一个FILE类型的指针,而Linux把一切都视为文件,也就是说我们可以使用stdio I/O库中的文件处理函数来对其进行操作。

  • 如果open_mode是"r",主调用程序就可以使用被调用程序的输出,通过函数返回的FILE指针,就可以能过stdio函数(如fread)来读取程序的输出;如果open_mode是"w",主调用程序就可以向被调用程序发送数据,即通过stdio函数(如fwrite)向被调用程序写数据,而被调用程序就可以在自己的标准输入中读取这些数据。

  • pclose()函数用于关闭由popen创建出的关联文件流。pclose()只在popen启动的进程结束后才返回,如果调用pclose()时被调用进程仍在运行,pclose()调用将等待该进程结束。它返回关闭的文件流所在进程的退出码。

  • 函数原型:

FILE * popen ( const char * command , const char * type );
int pclose ( FILE * stream )//用来关闭打开的stream流;

示例代码:

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define BUFSIZE 128 

int main()
{
    FILE *read_fp = NULL;
    FILE *write_fp = NULL;
    char buffer[BUFSIZ + 1];
    int chars_read = 0;
 
    // 初始化缓冲区
    memset(buffer, '\0', sizeof(buffer));
     
    // 打开ls和grep进程
    read_fp = popen("cat demo1.c", "r");
    write_fp = fopen("text.c", "w");
     
    // 两个进程都打开成功
    if (read_fp && write_fp)
    {
        // 读取一个数据块
        chars_read = fread(buffer, sizeof(char), BUFSIZE, read_fp);
        while (chars_read > 0)
        {
            buffer[chars_read] = '\0';
             
            // 把数据写入grep进程
            fwrite(buffer, sizeof(char), chars_read, write_fp);
             
            // 还有数据可读,循环读取数据,直到读完所有数据
            chars_read = fread(buffer, sizeof(char), BUFSIZE, read_fp);
        }
         
        // 关闭文件流
        pclose(read_fp);
        pclose(write_fp);
         
        exit(EXIT_SUCCESS);
    }
     
    exit(EXIT_FAILURE);
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值