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查看进程信息

相关推荐
修修修也20 小时前
【Linux】进程间通信
linux·运维·服务器·操作系统·进程通信
Pandaconda3 天前
【操作系统】每日 3 题(十八)
linux·服务器·开发语言·数据结构·笔记·后端·操作系统
vincent_woo3 天前
再学安卓 - 系统环境安装
操作系统
Raymond运维3 天前
第一章 Linux安装 -- 安装Debian 12操作系统(四)
linux·运维·服务器·操作系统·debian
小蜗的房子3 天前
一篇文章让你了解Linux中的用户和组权限
linux·运维·服务器·后端·学习·操作系统·基础
简鹿办公4 天前
Windows 怎么关机?这五种方法你需要了解一下
操作系统
星海幻影4 天前
linux基础-完结(详讲补充)
linux·服务器·网络·安全·操作系统
小林up5 天前
【MIT-OS6.S081笔记1】Chapter1阅读摘要:Operating system interfaces
笔记·操作系统
linhhanpy5 天前
自制操作系统(九、操作系统完整实现)
c语言·开发语言·汇编·c++·操作系统·自制操作系统
tt5555555555556 天前
操作系统学习笔记-5.1-IO设备
服务器·笔记·嵌入式硬件·学习·操作系统