🔥个人主页:guoguoqiang. 🔥专栏:Linux的学习
文章目录
一、进程的概念
1.什么是进程
课本概念:程序的一个执行实例,正在执行的程序等
内核观点:担当分配系统资源(CPU时间,内存)的实体
简单来说,进程==PCB(进程控制块)+进程对应的代码和数据,一个进程对应一个PCB操作系统对进程的管理,最终变成对链表的增删查改。
注意:可执行程序加载到内存不是进程,只是进程对应的代码和数据
2.在这里插入代码片
多进程管理
操作系统中可以存在大量的进程,操作系统需要管理这些进程,以确保系统资源(如CPU时间,内存等)的合理分配。管理进程的本质是对进程数据的管理
3.描述进程-PCB
进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。课本上称之为PCB(process control block), Linux操作系统下的PCB是: task_struct
struct task_struct {
volatile long state;
void *stack;
atomic_t usage;
unsigned int flags;
unsigned int ptrace;
unsigned long ptrace_message;
siginfo_t *last_siginfo;
int lock_depth;
// ... 其他属性
};
在Linux中描述进程的结构体叫做task_struct。
task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息(如进程状态、优先级、程序计数器、内存指针、上下文数据、I/O状态信息和记账信息等)
所有运行在系统里的进程都以task_struct链表的形式存在内核里。
2.查看进程与管理进程
1.使用指令查看进程
ps指令:
语法 ps[选项]
功能:显示当前终端会话中属于当前用户的进程列表
选项:
常见格式: ps -axj |head -1 && ps -ajx |grep可执行文件
也可以通过系统文件然后用pid查看 ls /proc/
2.通过系统调用函数查看pid
操作系统对进程进行管理,但是用户不能直接访问操作系统,因此需要使用系统提供的系统调用函数来管理进程。
查看pid的函数为 getpid();返回值为pid_t
可以通过man getpid 查询
发现通过命令查和通过通过系统调用查看的结果一样
3.杀进程
方法1: crtl + c ;方法2: kill -9 pid
注意:kill掉任意一个进程不会影响另一个进程!!!(保证进程的独立性)
4.ppid(父进程id)
为什么进程每次启动pid会变,但是ppid不会变呢???
我们先查看一下父进程是什么,输入该命令: ps -axj | head -1 && ps -axj | grep ppid 通过查看可以看到该进程是bash进程(命令行解释器),因此就很好理解了。
当我们运行一个进程时,命令行解释器会把这个指令解释成bash的子进程。
接着再由这个bash的子进程执行对应的命令。
即:每一条命令被执行,都属于bash的子进程,只是子进程不一样。
为了更好看到执行程序与进程信息,可以使用shell脚本,隔一秒查一次进程
while : ; do ps ajx | head -1 && ps ajx | grep myprocess; sleep 1; done
3.创建进程
fork() 是 Unix 系统中用于创建新进程的系统调用。它会创建一个与当前进程几乎完全相同的新进程,新的进程称为子进程。父进程和子进程会继续从 fork() 调用的下一行代码开始执行。
在 shell 中,你可以直接运行命令来创建进程。每个命令都会在系统中创建一个新进程。例如:
$ ls
$ gcc my_program.c -o my_program
$ ./my_program
这些命令分别创建了 ls、gcc 和 ./my_program 进程。每个命令的执行都涉及到创建一个新进程。
fork()的工作原理:
1.创建子进程
当进程调用 fork() 时,操作系统会创建一个新的进程,这个新的进程被称为子进程。子进程是父进程的几乎完整的拷贝,但有一些区别。
新进程会获得一个新的进程标识符(PID),这是与父进程不同的。
2.复制父进程的上下文
fork() 调用时,操作系统会复制父进程的内存空间、文件描述符、文件偏移量、信号处理器等信息到子进程中。这包括堆栈、代码段和数据段。
这称为“写时复制”(Copy-on-Write,COW)技术,实际上,操作系统在最初并不会立即复制内存中的所有数据,而是只在需要修改时才进行复制,以提高效率。
3.内存共享与复制
初始时,父进程和子进程共享相同的内存页。只有当其中一个进程试图修改这些页时,操作系统才会为该进程创建这些页的独立副本,这就是写时复制的机制。
这种机制可以节省内存和加快进程创建速度。
4.返回值
fork() 系统调用会在父进程和子进程中返回不同的值:
在父进程中,fork() 返回新创建的子进程的 PID。
在子进程中,fork()返回 0。
这个返回值可以用于区分父进程和子进程,进而决定不同的执行路径。
5.进程调度
在 fork() 之后,父进程和子进程将分别从 fork() 调用的下一行代码开始执行。操作系统的调度器会决定哪个进程先运行。
6.资源分配
子进程会继承父进程的所有资源描述符(如打开的文件描述符、信号处理器等),但这些描述符会有独立的引用计数。子进程和父进程的文件描述符指向同一个文件表项,因此对文件的读写操作可能会影响两个进程。
例子:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
fork();//创建进程
printf("hello world,pid: %d,ppid: %d\n",getpid(),getppid());//查看进程对应信息
}
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main() {
pid_t pid = fork(); // 创建一个新进程
if (pid < 0) {
// fork 失败
perror("fork failed");
return 1;
} else if (pid == 0) {
// 子进程执行的代码
printf("This is the child process. PID: %d\n", getpid());
} else {
// 父进程执行的代码
printf("This is the parent process. PID: %d, Child PID: %d\n", getpid(), pid);
}
return 0;
}
正常一个if在c语言中只会执行一次,而这里执行了两次。说明有两个进程。
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
printf("process is running,only me!,pid: %d\n", getpid());
sleep(3);
pid_t id = fork();
if (id == -1) return -1; //进程创建错误直接退出
else if (id == 0)
{
//child 子进程代码
while (1)
{
printf("id: %d,I am child process,pid: %d,ppid: %d\n", id, getpid(), getppid());
sleep(1);
}
}
else
{
//parent 父进程代码
while (1)
{
printf("id: %d,I am parent,pid: %d,ppid: %d\n", id, getpid(), getppid());
sleep(2);
}
}
return 0;
}
一次性创建多个进程
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
void RunChild()
{
while (1)
{
printf("I am a child process,pid: %d,ppid: %d\n", getpid(), getppid());
sleep(1);
}
}
int main()
{
int num = 5;
while(num--)
{
pid_t id = fork();
if (id == 0)
{
RunChild();//运行子进程代码
}
sleep(1);
}
while (1)
{
sleep(1);
printf("I am parent,pid: %d,ppid: %d\n", getpid(), getppid());
}
return 0;
}
为了更好看到执行程序与进程信息,可以使用shell脚本,隔一秒查一次进程,且不查看grep进程信息。
while : ; do ps ajx | head -1 && ps ajx | grep myprocess | grep -v grep; sleep 1; done
-v是反向选择
子进程”在操作系统中扮演着重要的角色,它们的创建和使用有很多实际的需求和好处。下面是一些主要的原因和应用场景,解释了为什么要为什么要创建子进程:
- 任务分离和并行处理
任务分离:通过创建子进程,可以将复杂的任务分解成多个独立的子任务,从而提高程序的结构化和可维护性。例如,在 Web 服务器中,主进程可以处理网络连接,而每个子进程可以处理单独的请求,这样可以避免主进程因为某个请求的处理而阻塞。
并行处理:子进程可以在多核处理器上并行执行任务,提高程序的执行效率。利用子进程并行处理多个计算密集型任务,可以显著提升性能。例如,科学计算中的大量数据处理可以通过多个子进程并行执行。
- 避免阻塞
非阻塞操作:在许多应用程序中,某些操作可能会阻塞主进程的执行。通过创建子进程,主进程可以继续执行其他任务而不会被阻塞。例如,数据库操作或长时间运行的网络请求可以放在子进程中处理,以避免影响主进程的响应能力。
权限限制:子进程可以以不同的用户权限运行,从而限制对系统资源的访问 - 资源隔离
资源管理:子进程可以拥有独立的资源和内存空间,避免了不同任务之间的干扰。例如,在进行文件操作时,如果文件处理出现问题,子进程的崩溃不会影响主进程的运行,这有助于提高系统的稳定性和鲁棒性。 - 安全性和隔离
安全隔离:通过子进程,系统可以将不同的任务隔离开来,避免潜在的安全风险。例如,浏览器通常会为每个标签页创建独立的子进程,这样即使某个标签页出现了问题,也不会影响其他标签页或浏览器的主进程。 - 提高响应能力
多任务处理:在用户交互密集的应用中(例如图形用户界面应用程序),创建子进程可以让主进程保持响应性。子进程可以处理后台任务或长时间运行的计算,从而确保主界面不会因为这些任务而变得不响应。 - 简化编程模型
简化设计:在一些系统设计中,通过子进程可以简化任务的设计和管理。子进程的创建和管理可以使得系统的设计更加模块化、清晰。例如,在 Unix 系统中,守护进程(daemon)通常通过子进程机制来运行后台服务。 - 进程生命周期管理
灵活性:子进程的创建和管理提供了灵活的进程生命周期控制。父进程可以在子进程完成任务后进行必要的清理和资源回收,或者根据子进程的执行结果决定后续的操作。
总结
创建子进程在现代操作系统中是一个强大且灵活的机制,允许程序以高效和结构化的方式处理多任务、多线程和资源隔离等复杂问题。通过使用子进程,系统能够提高响应能力、并行处理任务、增加安全性以及简化编程模型,从而实现更高效和稳定的软件系统。
4.task_struct内容分类
★标示符: 描述本进程的唯一标示符,用来区别其他进程。
★状态: 任务状态,退出代码,退出信号等。
★优先级: 相对于其他进程的优先级。
★程序计数器: 程序中即将被执行的下一条指令的地址。
★内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
★上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
★I/ O状态信息: 包括显示的I/O请求,分配给进程的I/ O设备和被进程使用的文件列表。
★记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
★其他信息
5.查看进程内容
◉ls /proc/pid -d # 按照目录查看
◉ ls /proc/pid -l # 查看进程内容
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
while (1)
{
printf("I am process,pid: %d,ppid: %d\n", getpid(), getppid());
sleep(1);
}
return 0;
}
cwd当前目录
exe对应的是可执行程序目录
1.如果我们在此处把可执行程序给删除,进程还会不会运行呢???
此处删除程序并不会造成影响,因为程序是加载到内存的,也就是内存拷贝了一份,这里是不会造成影响的,因为所占内存小,但如果内存过大运行就会出现问题
2.当前工作路径有什么用呢?
我们在C语言中学习文件操作,fopen(“log.txt”,“w”);默认是在当前目录创建文件,但是我们不一定每次都在当前目录创建文件,那怎么才能在其他目录下创建文件呢?
修改文件创建的路径,我们需要一个命令:chdir
#include<stdio.h>
#include<unistd.h>
int main()
{
chdir("/home/gwq");//更改工作目录为/home/gwq
FILE* pf = fopen("log.txt", "w");//创建文件
(void)pf;// ignore system warning
fclose(pf);
while (1)
{
printf("I am a process,pid: %d\n", getpid());
sleep(1);
}
return 0;
}
cwd当前目录 进程路径
exe对应的是可执行程序路径
因此我们对文件的理解不应该是在C语言上,而是应该在操作系统上