进程代码初识

proc.c

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <unistd.h>

int main() {
while(1) {
printf("I'm a process!\n");
sleep(1);
}
return 0;
}

1.#include <stdio.h>#include <unistd.h>:这两行是C语言的预处理指令,它们告诉编译器在编译程序之前将标准输入输出库(stdio.h)和Unix系统调用库

2.sleep(1);:这是一个系统调用,它使进程休眠1秒钟。也就是说,每次循环执行后,程序会休眠1秒钟,然后再次执行循环。

这个程序是一个简单的无限循环,每秒打印一次"I’m a process!",并在每次打印后休眠1秒钟。这可以用于演示一个运行中的进程,它以1秒的间隔输出一条消息。如果要终止程序,您可以手动中断它,例如按下Ctrl+C

Makefile

1
2
3
4
5
proc:proc.c 
gcc -o proc proc.c
.PHONY:clean
clean:
rm -f proc
  1. proc: proc.c:这一行指示Make工具如何构建目标"proc"。它告诉Make工具,要生成目标"proc",它依赖于"proc.c"文件。如果"proc.c"文件发生了变化,Make工具将重新编译"proc"目标。
  2. gcc -o proc proc.c:这是实际的编译命令。它告诉gcc编译器将"proc.c"源文件编译成一个可执行文件,文件名为"proc"。"-o"选项用于指定输出文件的名称。
  3. .PHONY: clean:这一行定义了一个伪目标,即"clean"。伪目标通常用于执行一些特殊的任务,而不是生成文件。在这种情况下,它用于清理生成的可执行文件。
  4. clean::这一行表示"clean"目标的开始。它告诉Make工具,下面的命令将执行"clean"操作。
  5. rm -f proc:这是"clean"目标的命令部分。它使用rm命令来删除名为"proc"的文件。-f选项告诉rm命令在文件不存在时不报错,以避免出现错误消息。这个命令的效果是删除生成的可执行文件。

执行

上述的Makefile文件本身并不产生输出,它用于编译和清理程序。要执行该Makefile文件并生成可执行文件,请按照以下步骤进行操作:

  1. 创建一个名为proc.c的C源代码文件,将其中的内容设置之前提供的程序代码。
  2. 在同一目录下创建一个名为Makefile的文件,并将其内容设置为您之前提供的Makefile代码。
  3. 打开终端,并导航到包含proc.cMakefile的目录。
  4. 执行以下命令以编译程序:
1
make

这将使用Makefile中的规则来编译proc.c并生成可执行文件proc

  1. 一旦编译完成,您可以运行生成的可执行文件proc
1
./proc

这将启动程序,它会每秒输出一次"I’m a process!",并无限循环运行

如果您想要清理生成的可执行文件,可以运行以下命令:

1
make clean

这将删除名为proc的可执行文件。

请注意,输出将在终端上显示,以每秒一次的频率打印"I’m a process!"。要停止程序的运行,您可以在终端中按下Ctrl+C来中断它。

结果

1
ps axj | head -1 && ps axj | grep proc | grep -v grep
  1. ps axj | head -1:这部分命令首先运行ps axj,它用于显示所有进程的详细信息。然后,通过管道|将其输出传递给head -1head命令用于显示输出的前几行,-1表示只显示第一行,即进程表的标题行。
  2. &&:这是逻辑操作符,用于在前一个命令成功执行后才执行下一个命令。
  3. ps axj | grep proc | grep -v grep:这部分命令运行ps axj以获取进程列表,然后通过两次grep过滤出包含关键字"proc"的进程。具体来说:
    • 第一个grep命令grep proc用于筛选包含"proc"关键字的行。
    • 第二个grep命令grep -v grep用于排除包含"grep"关键字的行,以避免匹配到自身的grep进程。

最终的输出显示了进程列表中包含关键字"proc"的进程信息,包括它们的父进程ID(PPID)、进程ID(PID)、进程组ID(PGID)、会话ID(SID)、终端(TTY)、终端进程组ID(TPGID)、状态(STAT)、用户ID(UID)、累计CPU时间(TIME)和命令(COMMAND)。

根据输出,可以看到一个进程具有PID 24055,它是通过执行./proc命令启动的。

终止进程

1
kill -9 24055 //24055为上述查询到的进程id

执行kill -9 24055命令会发送一个**强制终止信号(SIGKILL)**给进程PID为24055的进程。这会立即终止该进程,而不会给予它进行清理或保存数据的机会。

请注意,使用kill -9是一种强制终止进程的方法,应该小心使用,因为它不允许进程进行善后工作。只有在有必要时才应该使用这种信号,例如当一个进程不响应其他终止信号并且必须立即停止时。

在执行kill -9 24055后,进程PID 24055将被终止,不再运行。如果需要终止其他进程,请将其PID替换为所需的PID

系统调用初识

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <unistd.h>

int main() {
while (1) {
printf("I'm a process! My pid:%d\n", getpid());
sleep(1);
}
return 0;
}
  1. #include <unistd.h>:这是包含Unix标准库的预处理指令,其中包含了sleep函数,用于在程序中添加延迟。
  2. #include <unistd.h>:这是包含Unix标准库的预处理指令,其中包含了 getpid() 函数和 sleep() 函数的声明。
  3. printf("I'm a process! My pid:%d\n", getpid());:这是一个输出语句,用于在终端上打印一条消息。getpid()函数用于获取当前进程的进程ID(PID),并将其插入到消息中。

结果

getpid()

getpid() 是一个C标准库函数,通常用于获取当前进程的进程ID(PID)。PID是一个唯一标识运行中进程的正整数值。

以下是对getpid()函数和示例程序的详细解释:

  1. 包含头文件:为了使用getpid()函数,首先需要包含合适的头文件。通常,我们会包含 <unistd.h> 头文件,因为该头文件包含了getpid()函数的声明。
1
#include <unistd.h>
  1. 声明变量:在示例程序中,我们声明了一个名为pid的变量,其类型是 pid_tpid_t 是一个数据类型,通常用于表示进程ID。
1
pid_t pid;
  1. 使用getpid()函数:接下来,我们使用getpid()函数来获取当前进程的PID,并将其存储在pid变量中。
1
pid = getpid();
  1. 打印PID:最后,我们使用printf函数将获取到的PID打印到终端上,以便查看。
1
printf("My PID is: %d\n", pid);

示例程序中的完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <unistd.h>

int main() {
pid_t pid;

pid = getpid(); // 获取当前进程的PID

printf("My PID is: %d\n", pid);

return 0;
}

当您运行这个程序时,它会执行以下操作:

  1. 调用getpid()函数,该函数返回当前进程的PID,并将其存储在pid变量中。
  2. 使用printf函数将获取到的PID打印到终端上,形成一条类似 “My PID is: 12345” 的输出,其中12345是当前进程的实际PID。
  3. 然后,程序返回0作为退出状态码,表明程序正常退出。

通过这个示例程序,可以获得当前运行进程的PID,这在许多进程管理和通信场景中非常有用。每个运行的进程都有一个唯一的PID,允许操作系统和其他程序识别和管理它们。

getppid()

getppid 函数用于获取当前进程的父进程的进程ID(PID)。后续与fork()函数一起讲解。

  1. 返回类型getppid 函数返回一个 pid_t 类型的整数,表示父进程的PID。pid_t 是一个有符号整数类型,通常用于表示进程ID。

  2. 用途:主要用于进程间通信和进程控制的场景,它允许一个进程获取其父进程的PID。这在一些编程任务中非常有用,如父子进程之间的协作或监视。

  3. 父进程:父进程是启动当前进程的进程。**在典型情况下,父进程是终端 shell 进程,因为终端 shell 通常是用户启动程序的方式。**但也可以通过其他方式启动进程(例如创建子进程),因此父进程可能不仅仅是终端 shell。

  4. 使用示例:下面是一个使用 getppid 函数的示例程序,它获取当前进程的父进程的PID并输出到标准输出:

    1
    2
    3
    4
    5
    6
    7
    8
    #include <stdio.h>
    #include <unistd.h>

    int main() {
    pid_t parent_pid = getppid();
    printf("My parent's PID: %d\n", parent_pid);
    return 0;
    }

    这个程序将输出当前进程的父进程的PID。

  5. 常见场景getppid 函数在编写守护进程、进程间通信、子进程创建和控制等任务中常常被使用。它允许进程在运行时获取有关其父进程的信息,以便执行不同的操作,例如向父进程报告状态或请求资源。

总之,getppid 函数是一个用于获取父进程PID的系统调用,可以用于进程间的信息交流和控制。它允许一个进程了解其创建者或启动者,并在需要时与之进行交互。

查看进程方式

当需要查看进程时,可以使用以下命令和方法来获取详细信息:

  1. ps命令ps命令用于列出当前终端会话中的进程。您可以使用不同的选项来获取不同层次的信息:
    • ps aux:显示所有用户的所有进程的详细信息,包括进程ID(PID)、CPU使用率、内存使用等。
    • ps -ef:类似于ps aux,也会列出所有用户的所有进程。
    • ps -e | grep process_name:通过进程名称查找特定进程。
    • ps axj:显示进程的详细信息,包括父进程ID(PPID)、进程组ID(PGID)、会话ID(SID)、终端、状态、用户ID(UID)、运行时间、命令等
  2. top命令top命令提供实时的进程监视和系统性能信息。启动top后,您可以看到进程列表以及CPU、内存等资源使用情况。按键盘上的不同键可以对进程列表进行排序和筛选,例如按P键按CPU使用率排序,按M键按内存使用率排序。
  3. htop命令htoptop的交互式版本,提供更多功能和可视化选项。它使用颜色和更直观的界面来显示进程信息,还允许您通过鼠标或键盘进行交互式操作。
  4. pgrep命令pgrep命令用于根据进程名称查找进程ID(PID)。例如,pgrep firefox将返回所有名为 “firefox” 的进程的PID。
  5. /proc文件系统:Linux系统中的/proc文件系统包含了有关系统中运行的进程的详细信息。您可以使用文件浏览器或命令行来查看/proc目录中的进程信息。每个进程都有一个以其PID命名的目录,其中包含有关该进程的各种文件,例如/proc/PID/status包含了进程的状态信息。

这些方法可以根据您的需求选择使用,以获取有关正在运行的进程的详细信息。通常,pstophtop是最常用的工具,而pgrep/proc文件系统可以用于更具体的任务。

fork()初识

fork 函数是一个用于创建新进程的系统调用,它在Unix-like操作系统中非常常见。以下是对 fork 函数的详细解释:

  1. 函数原型

    1
    2
    #include <unistd.h>
    pid_t fork(void);
    • fork 函数返回一个 pid_t 类型的值,表示新进程的PID。在父进程中,返回新进程的PID;在子进程中,返回0;如果出现错误,返回-1。
  2. 功能

    • 当调用 fork 函数时,操作系统会创建一个新的进程,新进程是调用进程(父进程)的副本。父进程和子进程将在之后的执行中独立运行,彼此不会相互影响。
    • 父进程和子进程之间的主要区别在于返回值:父进程接收子进程的PID,而子进程接收0作为返回值,这样可以通过返回值来区分两者。
    • 子进程继承了父进程的大部分属性,包括代码、数据、文件描述符、环境变量等,但它们之间的资源(如文件描述符的位置、内存映射等)是独立的,不会相互干扰,会发生写诗拷贝。
  3. 用途

    • 创建多进程程序,同时执行不同的任务。
    • 在服务器应用中,父进程通常用于接受客户端连接,而子进程用于处理每个客户端的请求。
    • 在一些情况下,fork 用于实现并行计算,将工作分配给多个子进程以提高性能。
  4. 示例: 下面是一个示例程序,演示如何使用 fork 函数创建一个子进程:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    #include <stdio.h>
    #include <unistd.h>

    int main() {
    pid_t child_pid = fork();

    if (child_pid == -1) {
    // 错误处理
    perror("fork");
    return 1;
    }

    if (child_pid == 0) {
    // 子进程代码
    while (1) {
    printf("Child: My PID=%d, My parent's PID=%d\n", getpid(), getppid());
    sleep(3); // 可以添加适当的延迟,以控制打印速率
    }
    } else {
    // 父进程代码
    while (1) {
    printf("Parent: My PID=%d, My parent's PID=%d\n", getpid(), getppid());
    sleep(3); // 可以添加适当的延迟,以控制打印速率
    }
    }

    return 0;
    }

    在上述示例中,父进程调用 fork 创建了一个子进程,父子进程会分别输出不同的消息,同时打印自己的PID。

总之,fork 函数是一个关键的系统调用,用于创建新进程,使其在独立的执行环境中运行。它是多进程编程的基础,并在Unix-like操作系统中广泛使用。

结果

解析

从实验结果可以看出,此时父子进程同时运行并在控制台输出父子进程id。同时右侧通过指令显示当前实验运行的进程的相关信息与左边输出结果形成对应。

父进程通过fork函数的返回值可以获取成功创建的子进程id,同时给子进程返回0。

-bash 是一个命令行终端的名称,它表示一个交互式的 Bash Shell 会话。这是一个用于与操作系统交互的命令行界面,用户可以在其中执行各种命令和操作。

在此输出中,16856 是一个进程的PID,它是Bash Shell的父进程。

具体来说,16856 是当前终端会话(pts/2)的父进程的PID。在这个终端会话中,16857 是Bash Shell的PID,而16856 是启动了这个Bash Shell会话的进程的PID。

通常情况下,Bash Shell的父进程是终端程序,它负责启动并管理终端会话。在这里,16856 表示终端程序,而 16857 表示Bash Shell。 Bash Shell 是用户在终端中输入命令并与系统交互的主要界面,而终端程序负责管理这个终端会话。

在Bash Shell中,用户可以输入命令来执行各种操作,例如文件操作、进程管理、软件安装等。Shell是与操作系统交互的主要界面之一,它允许用户以文本方式与计算机进行通信和控制。