OS Interlude: Process API

. overview

💡 本节介绍关于进程实际操作的API函数,关于如何创建和控制进程。


1. API

unistd.h 为Linux/Unix系统中内置头文件 ,包含了许多系统服务的函数原型,例如read函数、write函数和getpid函数等

1.1 fork()

fork函数用来创建一个子进程,在调用者这方返回子进程的PID,被调用方(子进程)会得到0。

arduino 复制代码
#include <unistd.h>
int main(int argc, char const *argv[])
{
    printf("hello world! %d\n", (int)getpid());
    **int rc = fork();**
    if (rc < 0)
    {
        printf("create the pro faliler!\n");
        exit(1);
    }
    else if (**rc == 0**)
    {
        printf("this is the child-proc:%d\n", (int)getpid());
    }
    else
    {
        printf("this is the parent-proc:%d", 
								"my child is %d\n", (int)getpid(), rc);
    }
    /* code */
    return 0;
}

输出:

kotlin 复制代码
hello world! 829734
this is the parent-proc:829734, my child is 829735
this is the child-proc:829735

此时当前进程(父进程)的PID为829734,调用fork后,将创建子进程,得到rc为子进程的PID。→要#include <unistd.h>

此时:该函数创建子进程时,子进程将会拷贝父进程的整个上下文信息,此时看起来就像是该运行程序的两个单独拷贝并独立运行

  1. 两个进程都从fork函数处返回,继续往下执行。所以此时子线程不会从main函数从头执行,而是从fork函数后开始执行。
  2. 从fork函数处返回后,父进程得到的是子进程的PID(这里保留在rc变量),创建的子进程将返回得到0(保留在rc变量)。

💡 此时会有两个进程同时运行,所以也就会出现**并发的导致的一些现象**。如可能子进程先输入,也可能父进程先输出


1.2 wait()

有时,事实证明,对于父进程来说,等待子进程完成它一直在做的事情是非常有用的。→调用wait函数,返回得到等待的pid

要包含#include <sys/wait.h>

此时就是一个同步问题了。

arduino 复制代码
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
    printf("hello world! %d\n", (int)getpid());
    int rc = fork();
    if (rc < 0)
    {
        printf("create the pro faliler!\n");
        exit(1);
    }
    else if (rc == 0)
    {
        printf("this is the child-proc:%d\n", (int)getpid());
    }
    else
    {
        int wait_pid = wait(NULL);
        printf("this is the parent-proc:%d, my child is %d\n", (int)getpid(), rc);
    }
    /* code */
    return 0;
}

此时一定是子进程先print,父进程才会print。


1.3 exec()

在fork()后,将会拷贝与父进程一样的一份代码,并运行。但是有时候想让子进程运行不同的程序代码,此时execvp()函数就起作用了。如下,将参数和执行代码文件名封装在args中。此时会运行一个函数名为word_count的函数,其参数是a.txt。

scss 复制代码
else if (rc == 0)
    {
        printf("this is the child-proc:%d\n", (int)getpid());
        char *args[3];
        args[0] = strdup("word_count");
        args[1] = strdup("a.txt");
        args[1] = NULL;
        execvp(args[0], args);
    }

💡 通过使用fork和exec函数,可以在运行fork之后,exec之前初始化一些进程其他的参数和变量,从而能够调用exec实现不同的函数。

fork() 和 exec() 的组合非常简单且功能强大。


1.4 shell命令行运行程序的例子

一、命令行运行程序

就像命令行窗口,它向您显示提示,然后等待您在其中键入内容。

  1. 然后,您键入一个命令(即,可执行程序的名称,以及任何参数;
  2. 在大多数情况下,shell 然后找出可执行文件在文件系统中的位置,调用 fork() 创建一个新的子进程来运行命令。
  3. 调用 exec() 的某个函数变体来运行命令
  4. 然后通过调用 wait() 等待命令完成。当子进程完成时,shell 将从 wait() 返回并再次打印出提示符,为下一个命令做好准备。

二、重定向输出

$ wc p3.c > newfile.txt

在上面的示例中,程序wc的输出被重定向到输出文件newfile.txt(大于符号是表示重定向的方式)。shell 完成此任务的方式非常简单:创建子进程时,在调用 exec() 之前,shell 会关闭标准输出并打开文件 newfile.txt。通过这样做,即将运行的程序wc中的任何输出都将发送到文件而不是屏幕。

此重定向有效的原因是由于对操作系统如何管理文件描述符的假设。具体来说,UNIX 系统开始以零的位置寻找空闲的文件描述符。在这种情况下,STDOUT FILENO 将是第一个可用的 FILENO,因此在调用 open() 时被分配。


1.5 其他对进程的控制→signal

(除了root)用户只能发送signal信号去中断自己发出的程序进程。

在 C 语言中,可以使用 kill 函数向指定进程发送信号。要中断另一个进程,可以将该进程的进程 ID 作为 kill 函数的第一个参数,将中断信号 SIGINT 或其他信号作为第二个参数:

arduino 复制代码
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
    pid_t pid = 1234;  // 要中断的进程的进程 ID
    int ret = kill(pid, SIGINT);  // 发送中断信号
    if (ret == -1) {
        // 发送信号失败,处理错误
    }
    return 0;
}

可通过ps , top查看进程信息

相关推荐
别说我什么都不会14 小时前
鸿蒙轻内核M核源码分析系列十五 CPU使用率CPUP
操作系统·harmonyos
袁庭新1 天前
CentOS7通过yum无法安装软件问题解决方案
centos·操作系统
别说我什么都不会2 天前
鸿蒙轻内核M核源码分析系列十二 事件Event
操作系统·harmonyos
qq_437896433 天前
动态内存分配算法对比:最先适应、最优适应、最坏适应与邻近适应
操作系统
别说我什么都不会3 天前
鸿蒙轻内核M核源码分析系列十一 (2)信号量Semaphore
操作系统·harmonyos
别说我什么都不会3 天前
鸿蒙轻内核M核源码分析系列十 软件定时器Swtmr
操作系统·harmonyos
别说我什么都不会4 天前
鸿蒙轻内核M核源码分析系列九 互斥锁Mutex
操作系统·harmonyos
别说我什么都不会4 天前
鸿蒙轻内核M核源码分析系列七 动态内存Dynamic Memory
操作系统·harmonyos
别说我什么都不会5 天前
鸿蒙轻内核M核源码分析系列六 任务及任务调度(3)任务调度模块
操作系统·harmonyos
徐徐同学5 天前
【操作系统】操作系统概述
操作系统·计算机系统