一、进程初探
# ps ajx | head -1
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
root@hcss-ecs:~# ps ajx | head -1; ps ajx | grep proc
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
24696 24707 24707 24679 pts/2 24707 S+ 1000 0:00 ./proc
24756 24806 24805 24756 pts/3 24805 S+ 0 0:00 grep --color=auto proc
root@hcss-ecs:~# ps ajx | head -1 && ps ajx | grep proc
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
24696 24707 24707 24679 pts/2 24707 S+ 1000 0:00 ./proc
24756 24810 24809 24756 pts/3 24809 S+ 0 0:00 grep --color=auto proc
root@hcss-ecs:~# ps ajx | head -1 && ps ajx | grep proc | grep -v grep
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
24696 24813 24813 24679 pts/2 24813 S+ 1000 0:00 ./proc
运行程序本质上是在操作系统中创建一个进程。
某些命令如 `ls` 和 `pwd` 完成操作后即终止其进程。
常驻进程(如后台服务)则持续运行,直至用户主动终止。
getpid() 是一个系统调用,用于获取当前进程的进程标识符(Process ID, PID)。
使用 kill -9 [pid] 可以立即终止指定的进程,其中 [pid] 是该进程的进程ID。此操作发送一个不可忽略的 SIGKILL 信号给目标进程,强制其立即停止运行。
二、理解 cwd
在当前项目中新建文件通常指的是在进程的当前工作目录(Current Working Directory, CWD)中创建文件。
chdir() 是一个系统调用,用于改变当前进程的工作目录。调用 chdir() 函数时需要传递一个新的目录路径作为参数,如果调用成功,则该进程后续执行的涉及文件操作的行为(如打开文件、创建文件等)都会基于这个新设置的工作目录进行。chdir() 对于需要临时改变目录进行特定操作的应用程序非常有用,但是需要注意在多线程环境下可能引发的问题,以及改变目录后对程序其他部分的影响。使用 chdir() 后,可以通过 getcwd() 函数来确认当前的工作目录是否已经更改至预期的路径。
三、/proc
/proc 并不是一个磁盘级别的文件系统,而是一个伪文件系统(virtual file system),用于向进程提供信息以及对进程进行控制。/proc 文件系统的内容由内核动态生成,反映了当前系统的状态信息,包括运行中的进程信息、系统设备信息、系统状态统计等。
pid_t getppid(void)
在Linux系统中,启动之后,新创建任何进程的时候,都是由自己的父进程创建的
因为命令是在一个由 bash
启动的shell会话中执行的。当您登录到Linux系统并输入命令时,这些命令通常是由 bash
解释并执行的。因此,如果您在一个交互式的 bash
shell中运行诸如 ps
或者 pgrep
这样的命令来查找父进程ID,那么这些命令的直接父进程往往就是 bash
本身。
命令行中,执行命令/程序,本质是bash的进程创建的子进程,由子进程来执行我们的代码
使用系统调用,创建进程
fork() 是一个系统调用,用于创建一个新进程,它是Unix及类Unix操作系统中进程创建的核心机制之一。fork() 创建的新进程称为子进程,而创建它的进程称为父进程。
功能:fork() 创建一个与父进程几乎完全相同的副本(子进程),两者共享相同的程序代码但拥有独立的数据空间。
This is a proc, its pid = 29740, its ppid = 29706
This is a child proc, its pid = 29740, its ppid = 29706
This is a child proc, its pid = 29741, its ppid = 29740
PID 为 29740 的进程是由 PID 为 29706 的进程创建的。
PID 为 29741 的进程是由 PID 为 29740 的进程创建的,因此它是 PID 为 29706 的进程的“孙子”进程。
This is a proc, its pid = 29804, its ppid = 29706
This is a father proc, its pid = 29804, its ppid = 29706, ret id = 29805
This is a child proc, its pid = 29805, its ppid = 29804, ret id = 0
返回值:fork() 调用会返回两次,一次在父进程中,一次在子进程中。在父进程中返回的是子进程的PID,在子进程中返回的是0。
父 : 子 = 1 : n
fork() → 两个进程 → 父子关系 → 一般而言,代码是会共享的,但是数据是各自私有一份的
【Q】为何数据是各自私有一份的呢?
【A】进程具有很强的独立性,多个进程之间,即便是父子关系,运行时也是互不影响
用途:常用于实现进程间的并行操作、创建子进程执行不同任务等。
#include <stdio.h>
#include <unistd.h>
int main(){
int gval = 0;
printf("This is a proc, its pid = %d, its ppid = %d\n", getpid(), getppid());
pid_t id = fork();
if(id > 0){
while(1){
printf("This is a father proc, its pid = %d, its ppid = %d, ret id = %d, gval = %d\n", getpid(), getppid(), id, gval);
gval += 10;
sleep(1);
}
}
else if(id == 0){
while(1){
printf("This is a child proc, its pid = %d, its ppid = %d, ret id = %d, gval = %d\n", getpid(), getppid(),id, gval);
++gval;
sleep(1);
}
}
return 0;
}
以上代码展示了父进程和子进程的并行执行。父进程不断地增加一个值并打印自己的状态,而子进程则以不同的速度增加同一个值并打印自己的状态。虽然它们共享一个变量 gval
,但是它们的修改是独立的,因为每个进程有自己的内存空间。
四、创建多进程简例
#include <iostream>
#include <sys/types.h>
#include <vector>
#include <unistd.h>
using namespace std;
const int num = 10;
void SubProcRun(){
while(true){
cout << "This is a sub proc, its pid is " << getpid() << ", its ppid is " << getppid() << endl;
sleep(5);
}
}
int main(){
vector<pid_t> allchild;
for(int i = 0; i < num; ++i){
pid_t id = fork();
if(id == 0){
SubProcRun();
}
//只有父进程会在这里执行
allchild.push_back(id);
}
//父进程
cout << "Each child is above:" << endl;
for(auto e: allchild){
cout << e << " ";
}
cout << endl;
sleep(10);
while(true){
cout << "This is the father proc, its pid is " << getpid() << endl;
sleep(1);
}
return 0;
}
ps ajx | grep mproc
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 1 48073 48072 46010 ? -1 S 1000 0:00 ./mproc 1 48074 48072 46010 ? -1 S 1000 0:00 ./mproc 1 48075 48072 46010 ? -1 S 1000 0:00 ./mproc 1 48076 48072 46010 ? -1 S 1000 0:00 ./mproc 1 48077 48072 46010 ? -1 S 1000 0:00 ./mproc 1 48078 48072 46010 ? -1 S 1000 0:00 ./mproc 1 48079 48072 46010 ? -1 S 1000 0:00 ./mproc 1 48080 48072 46010 ? -1 S 1000 0:00 ./mproc 1 48081 48072 46010 ? -1 S 1000 0:00 ./mproc 1 48082 48072 46010 ? -1 S 1000 0:00 ./mproc 72826 73911 73911 72826 pts/1 73911 S+ 0 0:00 ./mproc 73911 73912 73911 72826 pts/1 73911 S+ 0 0:00 ./mproc 73911 73913 73911 72826 pts/1 73911 S+ 0 0:00 ./mproc 73911 73914 73911 72826 pts/1 73911 S+ 0 0:00 ./mproc 73911 73915 73911 72826 pts/1 73911 S+ 0 0:00 ./mproc 73911 73916 73911 72826 pts/1 73911 S+ 0 0:00 ./mproc 73911 73917 73911 72826 pts/1 73911 S+ 0 0:00 ./mproc 73911 73918 73911 72826 pts/1 73911 S+ 0 0:00 ./mproc 73911 73919 73911 72826 pts/1 73911 S+ 0 0:00 ./mproc 73911 73920 73911 72826 pts/1 73911 S+ 0 0:00 ./mproc 73911 73921 73911 72826 pts/1 73911 S+ 0 0:00 ./mproc 73756 73934 73933 73756 pts/2 73933 S+ 0 0:00 grep --color=auto mproc
五、结合系统接口理解子进程的创建
1. id 的返回值,给父(subproc)给子(0)
2. 函数 fork() 怎么会有两个返回值?
#include <unistd.h>
pid_t fork(void)
- 函数实现里多了一个 task_struct:创建子进程的时候,子进程的 task_struct 是拷贝自父进程的
- 调整新的 task_struct 的部分属性
- task_struct 连入到进程列表中 子进程已经创建
父子进程都已经运行了,代码是只读共享的,各自进行了 return id; 语句的执行,所以有两个返回值
3. 一个变量,怎么会有不同的值?
(1)进行需要独立性
(2)它是怎么做到的 (to be continued)
fork 之后,谁先运行,由OS的调度器自主决定