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

相关推荐
wu~97010 小时前
Kafka底层解析:可靠性与高性能原理
分布式·kafka·操作系统
岑梓铭16 小时前
考研408《操作系统》复习笔记,第二章《2.3 进程调度》
笔记·考研·操作系统·os
望获linux1 天前
【Linux基础知识系列:第一百五十九篇】磁盘健康监测:smartctl
linux·前端·数据库·chrome·python·操作系统·软件
又见野草2 天前
软件设计师知识点总结:操作系统
操作系统
海棠蚀omo2 天前
Linux操作系统-父进程的等待:一个关于回收与终结的故事
linux·操作系统
SmoothSailingT3 天前
操作系统—内存管理(1)
操作系统·内存管理
添砖java‘’5 天前
vim高效编辑:从入门到精通
linux·编辑器·操作系统·vim
崎岖Qiu6 天前
【OS笔记11】:进程和线程9-死锁及其概念
笔记·操作系统·os
fakerth6 天前
【OpenHarmony】sensors_miscdevice小器件模块架构
架构·操作系统·openharmony
叶凡要飞6 天前
RTX5060Ti安装双系统ubuntu22.04各种踩坑点(黑屏,引导区修复、装驱动、server版本安装)
人工智能·python·yolo·ubuntu·机器学习·操作系统