Unix 进程 API 介绍

简介: Unix 进程 API 介绍

本文,主要介绍进程创建的几个接口,带领大家了解进程创建与控制过程。


fork 系统调用


如下,为一个fork调用基本示例:


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv)
{
    int pid = -1;
    pid = getpid();
    printf("hello world (pid:%d)\n", pid);
    int ret = -1;
    ret = fork();
 if (ret < 0) {
      fprintf(stderr, "fork failed\n");
      exit(1);
 } else if (ret == 0) { /* child: new process */
      pid = getpid();
      printf("hello, I am child (pid:%d)\n", pid);
 } else {               /* parent process */
      pid = getpid();
      printf("hello, I am parent of %d (pid:%d)\n", ret, pid);
 }
  return 0;
}


image.png


当这个程序运行时,首先输出hello world信息,以及自己的进程描述符(PID = 21281)。


紧接着进程掉调用了 fork() 系统调用,这是操作系统提供的创建进程的方法。新创建的进程几乎与调用进程完全一样,对于操作系统来说,这是看起来两个完全一样的程序在运行,并且都从fork系统调用返回。


新创建的进程称为子进程(child)、原来的进程称为父进程(parent),子进程不会从main函数开始执行,而是直接从fork系统调用返回,就好像它自己调用了fork()。父进程获得返回值是创建新进程的PID,而子进程获得返回值是0.


子进程并不是完全拷贝父进程。虽然它拥有自己的地址空间、寄存器、程序寄存器等,但是它从fork()返回值是不同的。


注意:子进程被创建后,它们的输出先后是随机的,这涉及到CPU调度,决定了哪个时刻该运行哪个程序。


wait 系统调用


fork()系统调用,只是创建了一个子进程,其他什么也没做。有时候,我们需要父进程等待子进程执行完毕。这项任务由wait()系统调用(或者更完整的兄弟接口waitpid())完成。


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv)
{
    int pid = -1;
    pid = getpid();
    printf("hello world (pid:%d)\n", pid);
    int ret = -1;
    ret = fork();
 if (ret < 0) {
      fprintf(stderr, "fork failed\n");
      exit(1);
 } else if (ret == 0) { /* child: new process */
      pid = getpid();
      printf("hello, I am child (pid:%d)\n", pid);
 } else {               /* parent process */
      int wc = wait(NULL);
      pid = getpid();
      printf("hello, I am parent of %d (wc:%d) (pid:%d)\n", ret,wc,  pid);
 }
  return 0;
}


image.png


在这个例子中,父进程调用wait(),延迟自己的执行,直到子进程执行完毕。当子进程完结束后,wait()才返回父进程。


exec 系统调用


exec() 系统调用,它也是创建进程API的一个重要组成部分。这个系统调用可以让子进程执行与父进程不同的程序。(exec有几种变体,execl、execle、execlp、execv和execvp)


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char *argv)
{
    int pid = -1; 
    pid = getpid();
    printf("hello world (pid:%d)\n", pid);
    int ret = -1; 
    ret = fork();
    if (ret < 0) {
        fprintf(stderr, "fork failed\n");
        exit(1);
    } else if (ret == 0) { /*child: new process*/
        pid = getpid();
        printf("hello, I am child (pid:%d)\n", pid);                                                                                                                                                        
        char *myargs[3];
        myargs[0] = strdup("wc");  /* program: wc (word count) */
        myargs[1] = strdup("exec.c");/* argumnt: file count*/
        myargs[2] = NULL;             /* marks end of array*/
        execvp(myargs[0], myargs);  /* runs word count */
        printf("this is shouldn't print out");
    } else {               /* parent process */
        int wc = wait(NULL);
        pid = getpid();
        printf("hello, I am parent of %d (wc:%d) (pid:%d)\n", ret,wc, pid);
    }
    return 0;
}


image.png            

                 

在这个例子中,子进程调用execvp()来运行字符计数程序wc。实际上,它针对源码文件 exec.c 运行wc,从而告诉我们该文件有多少行,多少单词,以及多少字节。


exec调用,对于给定可执行程序的名称(如wc)以及需要的参数后,就会从可执行程序中加载代码和静态数据,并用它覆写自己的代码段(以及静态数据),堆、栈及其他内存空间也会被重新初始化。然后操作系统就执行该程序,将参数通过argv传递给进程。


注:想象一下,可执行程序的加载,是不是很像?


因此,它并没有创建新的进程,而是直接将当前运行的程序(以前的a.out)替换为不同的运行程序(wc)。


子进程执行exec()后,几乎就像exec.c 从未运行过一样。对exec的成功调用永远也不会返回。


为什么要这样设计API?


事实证明,这种分离fork()以及exec()的做法在构建UNIX shell的时候非常有用,因为这给了 shell 在fork 之后 exec 之前运行代码的机会,这些代码可以在运行新程序前改变环境。


shell 也是一个用户程序。它首先是一个显示提示符(prompt),然后等待用户输入。你可以向它输入命令(一个可执行程序的名称及参数),大多数情况下,shell 可以在文件系统中找到这个程序,调用fork()创建新进程,并调用exec()的某个变体来执行这个可执行程序,调用wait()等待该命令的完成。子进程执行结束后,shell 从wait()返回并再次输出提示符,等待用户输入下一个命令。


fork() 和 exec()的分离,让shell 可以方便地实现很多有用的功能。比如:


/* 将输出结果重定向到 newfile.txt*/
wc exec.c > newfile.txt


  • 创建子进程。


  • shell 在调用 exec() 之前,关闭了标准输出,打开了文件 newfile.txt


  • 然后将结果写到 newfile.txt.


如下展示了重定向的基本原理:


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <fcntl.h>
int main(int argc, char *argv)
{
    int pid = -1;
    pid = getpid();
    printf("hello world (pid:%d)\n", pid);
    int ret = -1; 
    ret = fork();
    if (ret < 0) {
        fprintf(stderr, "fork failed\n");
        exit(1);
    } else if (ret == 0) { /*child: new process*/
        /*pid = getpid();*/
        /*printf("hello, I am child (pid:%d)\n", pid);*/
        close(STDOUT_FILENO);
        open("./redirect.output", O_CREAT | O_WRONLY | O_TRUNC, S_IRWXU);
        // now exec wc
        char *myargs[3];
        myargs[0] = strdup("wc");  /* program: wc (word count) */
        myargs[1] = strdup("redirect.c");                   myargs[2] = NULL;
        execvp(myargs[0], myargs);
      } else {               /* parent process */
          int wc = wait(NULL);
          //pid = getpid();
          // printf("hello, I am parent of %d (wc:%d) (pid:%d)\n", ret,wc, pid);
      }
      return 0;
}



UNIX 系统从0 开始寻找可以使用的文件描述符。在这个例子中,STDOUT_FILENO将成为第一个可用的文件描述符。因此在OPEN()被调用时,得到赋值,然后子进程向标准输出文件的写入(例如printf()这样的函数),都会被透明转向新打开的文件,而不少屏幕。


总结


本文只是从较高层面简单介绍了进程API,关于这些系统调用的细节,需要深入学习可了解。另外,除了fork()、exec()、wait()之外,在UNIX中还有其他许多与进程交互的方式。

相关文章
|
10月前
|
安全 API
7.4 通过API枚举进程权限
GetTokenInformation 用于检索进程或线程的令牌(Token)信息。Token是一个数据结构,其包含有关进程或线程的安全上下文,代表当前用户或服务的安全标识符和权限信息。GetTokenInformation函数也可以用来获取这些安全信息,通常用于在运行时检查某个进程或线程的权限或安全信息。
79 1
|
10月前
|
安全 API Windows
7.5 通过API判断进程状态
进程状态的判断包括验证进程是否存在,实现方法是通过枚举系统内的所有进程信息,并将该进程名通过`CharLowerBuff`转换为小写,当转换为小写模式后则就可以通过使用`strcmp`函数对比,如果发现继承存在则返回该进程的PID信息,否则返回-1。
58 0
|
8月前
|
Unix 程序员 Linux
【OSTEP】动态内存开辟 | 内存API常见错误 | UNIX: brk/sbrk 系统调用 | mmap创建匿名映射区域 | mmap创建以文件为基础的映射区域
【OSTEP】动态内存开辟 | 内存API常见错误 | UNIX: brk/sbrk 系统调用 | mmap创建匿名映射区域 | mmap创建以文件为基础的映射区域
199 0
|
8月前
|
存储 SQL Shell
【OSTEP】Abstraction Process | 进程 | 虚拟化 | 进程API
【OSTEP】Abstraction Process | 进程 | 虚拟化 | 进程API
35 0
|
2月前
|
消息中间件 监控 安全
探究如何在Linux系统中修改进程资源限制:四种方法调整进程限制,让你的系统高效运行(包含应用层getrlimit和setrlimit API)
探究如何在Linux系统中修改进程资源限制:四种方法调整进程限制,让你的系统高效运行(包含应用层getrlimit和setrlimit API)
383 0
|
2月前
|
Java API 调度
Java多线程基础(线程与进程的区别,线程的创建方式及常用api,线程的状态)
Java多线程基础(线程与进程的区别,线程的创建方式及常用api,线程的状态)
65 0
Java多线程基础(线程与进程的区别,线程的创建方式及常用api,线程的状态)
|
10月前
|
API
7.3 通过API枚举进程
首先实现枚举当前系统中所有进程信息,枚举该进程的核心点在于使用`CreateToolhelp32Snapshot()`函数,该函数用于创建系统进程和线程快照,它可以捕获当前系统中进程和线程相关的信息(如PID、线程数量、线程ID等),在对这些信息进行处理后,可以获得很多有用的数据,如当前系统中所有正在执行的进程的信息列表,以及每个进程各自的详细信息(如CPU、内存占用量等)。
49 1
|
10月前
|
安全 API Windows
7.2 通过API创建新进程
创建新的进程是`Windows`程序开发的重要部分,它可以用于实现许多功能,例如进程间通信、并行处理等。其中,常用的三种创建进程的方式分别是`WinExec()`、`ShellExecute()`和`CreateProcessA()`,这三种创建进程的方式各有特点。如果需要创建简单进程或从其他程序启动新进程,可以使用`WinExec()`或`ShellExecute()`函数。如果需要对新进程进行更精细的配置,例如控制进程参数、指定安全级别、传递特定的命令和参数等,可以使用`CreateProcessA()`函数。
81 0
|
Java API 调度
Java多线程基础(线程与进程的区别,线程的创建方式及常用api,线程的状态)
每一个线程都是一个执行流,都按照自己的顺序执行自己的代码,多个线程之间“同时”(并发并行)的执行多份代码。Java中的线程是以轻量级进程来实现的。
Java多线程基础(线程与进程的区别,线程的创建方式及常用api,线程的状态)
|
17天前
|
监控 Linux 应用服务中间件
探索Linux中的`ps`命令:进程监控与分析的利器
探索Linux中的`ps`命令:进程监控与分析的利器