"从创建到管理,Linux进程编程是你掌握系统资源的金钥匙!"#Linux系统编程之进程【上】
前言
本篇博文将带您踏上Linux系统编程的旅程,聚焦进程管理的精髓(上篇)。从进程的基本概念出发,我们深入探索fork函数的神奇之处,揭示其如何创造新进程及其背后的复杂机制。了解进程创建的每一个细节,包括内存空间的复制、文件描述符的继承等,让您对Linux的并发处理能力有更深的认知。随后,我们将fork函数的实际应用场景娓娓道来,并总结其使用要点。此外,我们还特别介绍了vfork这一特殊的进程创建方式,让您对Linux的进程创建机制有更全面的掌握。最后,我们讨论进程的优雅退出,确保您能够正确处理进程生命周期的结束阶段。无论您是Linux系统编程的新手还是寻求进阶的开发者,这篇博文都将为您提供宝贵的知识和见解。期待您的点赞与关注,一同探索Linux系统编程的无限可能!
预备知识
一、C变量
二、基本输入输出
三、流程控制
四、函数
五、指针
六、字符串
七、Linux系统基本操作命令如mkdir,ls -l等。
八、计算内存知识:堆,栈等
如果以上知识不清楚,请自行学习后再来浏览。如果我有没例出的,请在评论区写一下。谢谢啦!
一、 进程相关概念
1.1 什么是程序,什么是进程,右什么区别?
程序是静态的概念,gcc xxx.c –o pro
磁盘中生成pro文件,叫做程序
进程是程序的一次运行活动,通俗点意思是程序跑起来了,系统中就多了一个进程
1.2 如何查看系统中有那些进程?
使用ps指令查看,实际工作中,配合grep来查找程序中是否存在某一个进程
ps -aux|grep init
命令运行结果
CLC@Embed_Learn:~$ ps -aux|grep init
Warning: bad ps syntax, perhaps a bogus '-'? See http://procps.sf.net/faq.html
root 1 0.0 0.1 24704 2596 ? Ss Aug05 0:01 /sbin/init
CLC 41147 0.0 0.0 13592 960 pts/0 S+ 23:05 0:00 grep --color=auto init
使用top指令查看,类似windows任务管理器
top
命令运行结果
Tasks: 331 total, 1 running, 330 sleeping, 0 stopped, 0 zombie
Cpu(s): 0.7%us, 1.4%sy, 0.0%ni, 97.8%id, 0.0%wa, 0.1%hi, 0.0%si, 0.0%st
Mem: 2189824k total, 1986184k used, 203640k free, 128396k buffers
Swap: 5998588k total, 160k used, 5998428k free, 860572k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
2460 CLC 9 -11 418m 5984 4180 S 3 0.3 334:19.61 pulseaudio
1281 root 20 0 449m 274m 11m S 2 12.8 55:03.10 Xorg
31296 CLC 20 0 610m 24m 13m S 1 1.2 2:26.43 gnome-terminal
41149 CLC 20 0 17468 1500 980 R 1 0.1 0:00.07 top
2448 CLC 20 0 1371m 112m 38m S 0 5.3 52:35.04 compiz
1 root 20 0 24704 2596 1344 S 0 0.1 0:01.49 init
2 root 20 0 0 0 0 S 0 0.0 0:00.08 kthreadd
3 root 20 0 0 0 0 S 0 0.0 0:01.55 ksoftirqd/0
5 root 0 -20 0 0 0 S 0 0.0 0:00.00 kworker/0:0H
7 root 20 0 0 0 0 S 0 0.0 1:55.71 rcu_sched
8 root 20 0 0 0 0 S 0 0.0 1:44.82 rcuos/0
9 root 20 0 0 0 0 S 0 0.0 1:03.66 rcuos/1
10 root 20 0 0 0 0 S 0 0.0 0:58.21 rcuos/2
11 root 20 0 0 0 0 S 0 0.0 0:48.37 rcuos/3
12 root 20 0 0 0 0 S 0 0.0 0:00.00 rcuos/4
13 root 20 0 0 0 0 S 0 0.0 0:00.00 rcuos/5
14 root 20 0 0 0 0 S 0 0.0 0:00.00 rcuos/6
15 root 20 0 0 0 0 S 0 0.0 0:00.00 rcuos/7
16 root 20 0 0 0 0 S 0 0.0 0:00.00 rcuos/8
17 root 20 0 0 0 0 S 0 0.0 0:00.00 rcuos/9
18 root 20 0 0 0 0 S 0 0.0 0:00.00 rcuos/10
19 root 20 0 0 0 0 S 0 0.0 0:00.00 rcuos/11
20 root 20 0 0 0 0 S 0 0.0 0:00.00 rcuos/12
21 root 20 0 0 0 0 S 0 0.0 0:00.00 rcuos/13
22 root 20 0 0 0 0 S 0 0.0 0:00.00 rcuos/14
23 root 20 0 0 0 0 S 0 0.0 0:00.00 rcuos/15
注意:这个结果会实时改变的,和Windows是一样的,都会刷新。
1.3 什么是进程标识符
每个进程都有一个非负整数表示的唯一ID,叫做pid,类似身份证
Pid=0: 称为交换进程(swapper)
作用—进程调度
Pid=1:init进程
作用—系统初始化
编程调用getpid函数获取自身的进程标识符,getppid获取父进程的进程标识符
程序代码
#include <stdio.h>
使用getpid函数必须包含以下头文件
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
//pid_t getpid(void);
pid = getpid(); 调用getpid函数获取当前进程标识符
printf("pid = %d\n",pid); 输出进程标识符
while(1);
return 0;
}
程序运行结果
1.4 什么叫父进程,什么叫子进程?
进程A创建了进程B,那么A叫做父进程,B叫做子进程,父子进程是相对的概念,理解为人类中的父子关系。
1.5 C程序的存储空间是如何分配?
二、 创建进程函数fork的使用
2.1 fork函数介绍
2.1.1 函数功能
创建一个新的子进程。
子进程是父进程的几乎完全相同的副本,包括代码、数据、堆、栈、文件描述符等。
父子进程从fork函数的返回点开始独立执行。
2.1.2 返回值
fork函数的一个独特之处在于它只被调用一次,但会返回两次:
在父进程中,fork返回新创建的子进程的进程ID(PID)。
在子进程中,fork返回0。
如果出现错误(如系统资源不足),fork返回-1,并设置errno以指示错误类型。
2.1.3 进程间关系
子进程是父进程的副本,但它们各自拥有独立的内存空间和资源管理。
父子进程之间通过共享某些资源(如文件描述符)进行通信和协作。
2.2 fork函数编程实战
2.2.1 单独调用fork和多使用一次getpid
程序代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
//pid_t getpid(void);
pid = getpid(); 获取进程标识符
//pid_t fork(void);
fork(); 创建新进程
printf("pid1 = %d\n pid2 = %d\n",pid,getpid()); 输出第一次和第二次获取到的进程标识符
return 0;
}
程序运行结果
CLC@Embed_Learn:~/Linux_System_Programming/Linux_process_programming/02_Process_creat$ gcc Process_programming.c
CLC@Embed_Learn:~/Linux_System_Programming/Linux_process_programming/02_Process_creat$ ./a.out
pid1 = 41609
pid2 = 41609
pid1 = 41609
pid2 = 41610
从输出结果可以观察到,printf函数实际上被调用了多次,但其中关键的是,它首次被调用时仅代表父进程的上下文,输出了父进程的标识符。随后,在fork函数调用之后,由于fork的特性,它会在父进程中返回子进程的PID(进程标识符),而在新创建的子进程中返回0。因此,在达到fork函数的返回点之后,printf的第二次调用分别在父进程和子进程中独立执行,导致在控制台上既输出了父进程的标识符,也输出了子进程的标识符(在子进程中,由于fork返回0,通常会通过特定的逻辑处理来区分并输出相应的信息)。这样,就清晰地展示了父子进程从fork函数的返回点开始独立执行的行为。
2.2.2 通过getpid的返回值判断父子进程
程序代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
//pid_t getpid(void);
pid = getpid();
//pid_t fork(void);
fork();
if(pid == getpid()) 判断父进程
{
printf("This is the parent process,and its PID is %d\n",getpid());
}
else 判断子进程
{
printf("This is a child process,and the child process is %d\n",getpid());
}
return 0;
}
程序运行结果
CLC@Embed_Learn:~/Linux_System_Programming/Linux_process_programming/02_Process_creat$ gcc Process_programming2.c
CLC@Embed_Learn:~/Linux_System_Programming/Linux_process_programming/02_Process_creat$ ./a.out
This is the parent process,and its PID is 41710
This is a child process,and the child process is 41711
2.2.3 通过fork函数返回值判断父子进程
fork函数调用成功,返回两次;返回值为0,代表当前进程是子进程;返回值非负数,代表当前进程为父进程。
程序代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid_t fk;
//pid_t getpid(void);
//pid_t fork(void);
fk = fork();
if(fk > 0)
{
printf("This is the parent process,and its PID is %d\n",getpid());
}
else if(fk == 0)
{
printf("This is a child process,and the child process is %d\n",getpid());
}
return 0;
}
程序运行结果
CLC@Embed_Learn:~/Linux_System_Programming/Linux_process_programming/02_Process_creat$ gcc Process_programming3.c
CLC@Embed_Learn:~/Linux_System_Programming/Linux_process_programming/02_Process_creat$ ./a.out
This is the parent process,and its PID is 41747
This is a child process,and the child process is 41748
2.2.4 fork返回值补充
程序代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid_t retfk;
pid_t pid2;
//pid_t getpid(void);
pid = getpid();
printf("before fork: pid = %d\n",pid);
//pid_t fork(void);
retfk = fork();
pid2 = getpid();
printf("after fork: pid = %d\n",pid2);
if(pid == pid2)
{
printf("This is the parent process: retfk = %d\n",retfk);
}
else
{
printf("This is a child process,and the child process is %d retfk = %d\n",getpid(),retfk);
}
return 0;
}
程序运行结果
CLC@Embed_Learn:~/Linux_System_Programming/Linux_process_programming/02_Process_creat$ gcc Process_programming4.c
CLC@Embed_Learn:~/Linux_System_Programming/Linux_process_programming/02_Process_creat$ ./a.out
before fork: pid = 41912
after fork: pid = 41912
This is the parent process: retfk = 41913
after fork: pid = 41913
This is a child process,and the child process is 41913 retfk = 0
通过fork()系统调用,程序成功地在父进程中创建了一个新的子进程。随后,父进程和子进程都继续执行了fork()调用之后的代码。在父进程中,fork()返回了子进程的PID,而子进程中fork()返回了0,分别用于区分父进程和子进程的身份。通过getpid()的调用,父进程和子进程各自打印了它们的PID,以及fork()的返回值,清晰地展示了进程创建的过程和父子进程间的关系。
三、 进程创建发生了什么事
3.1 新进程内存分配
3.2 编程验证内存空间独立
3.2.1 程序代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid_t fk;
int data = 10; 初始化data的值为10
//pid_t getpid(void);
printf("father : id = %d\n",getpid());
//pid_t fork(void);
fk = fork();
if(fk > 0)
{
printf("This is the parent process,and its PID is %d\n",getpid());
}
else if(fk == 0)
{
printf("This is a child process,and the child process is %d\n",getpid());
data = data + 100; data在子进程中加100
}
printf("data = %d\n",data); 父子进程分别输出data的值
return 0;
}
3.2.2 程序运行结果
CLC@Embed_Learn:~/Linux_System_Programming/Linux_process_programming/03_New_process_memory_allocation$ gcc Process_programming.c
CLC@Embed_Learn:~/Linux_System_Programming/Linux_process_programming/03_New_process_memory_allocation$ ./a.out
father : id = 42213
This is the parent process,and its PID is 42213
data = 10
This is a child process,and the child process is 42214
data = 110
运行结果表明,data只在子进程中加100,在父进程中没有做任何改变。数码父子进程间的内存空间是独立的。
四、 创建新进程的实际应用场景及fork总结
4.1 创建新进程的实际应用场景
如下图
4.1.1 上图(1)的解释
如下图
4.1.2 对上图(1)编程实战
程序代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid_t fk;
int data;
//pid_t getpid(void);
printf("father : id = %d\n",getpid());
//pid_t fork(void);
while(1)
{
puts("Please enter a request");
scanf("%d",&data);
if(data == 1) data=1时客户端接入
{
fk = fork(); 创建子进程
if(fk > 0) 主进程什么都不做
{
}
else if(fk == 0) 子进程进行连接并输出自己的pid
{
while(1)
{
printf("This is a child process,and the child process is %d\n",getpid());
sleep(3); 睡眠3秒防止刷屏
}
}
}
else data != 1就等待接入
{
puts("Wait for input!");
}
}
return 0;
}
程序运行结果
如下图:
4.2 fork函数总结
如下图
五、 vfork创建进程
5.1 vfork和fork的区别
关键区别一:
vfork 直接使用父进程存储空间,不拷贝。
关键区别二:
vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行。
5.2 使用fork观察进程调度情况
5.2.1 程序代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t fk;
//pid_t getpid(void);
//pid_t fork(void);
fk = fork();
if(fk > 0)
{
while(1) 一直重复发出父进程的进程标识符
{
printf("This is the parent process,and its PID is %d\n",getpid());
sleep(1);
}
}
else if(fk == 0)
{
while(1) 一值重复发出子进程的进程标识符
{
printf("This is a child process,and the child process is %d\n",getpid());
sleep(1);
}
}
return 0;
}
5.2.2 程序运行结果
CLC@Embed_Learn:~/Linux_System_Programming/Linux_process_programming/05_Vfork_creat_process$ gcc Process_programming.c
CLC@Embed_Learn:~/Linux_System_Programming/Linux_process_programming/05_Vfork_creat_process$ ./a.out
This is the parent process,and its PID is 45607
This is a child process,and the child process is 45608
This is a child process,and the child process is 45608
This is the parent process,and its PID is 45607
This is a child process,and the child process is 45608
This is the parent process,and its PID is 45607
This is a child process,and the child process is 45608
This is the parent process,and its PID is 45607
This is the parent process,and its PID is 45607
This is a child process,and the child process is 45608
This is a child process,and the child process is 45608
This is the parent process,and its PID is 45607
This is a child process,and the child process is 45608
This is the parent process,and its PID is 45607
This is a child process,and the child process is 45608
This is the parent process,and its PID is 45607
This is a child process,and the child process is 45608
This is the parent process,and its PID is 45607
This is a child process,and the child process is 45608
This is the parent process,and its PID is 45607
This is a child process,and the child process is 45608
This is the parent process,and its PID is 45607
^C
从以上运行结果可以看出,程序在执行过程中,父进程(PID为45607)和子进程(PID为45608)都在不断执行并打印出各自的进程ID和角色信息。这表明fork()调用在父进程中成功创建了一个子进程,并且父进程和子进程都进入了自己的执行路径,不断地打印出各自的信息。也表明了父子进程各自空间独立,互不干扰。
5.3 使用vfork观察进程调度情况
5.3.1 程序代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t fk;
//pid_t getpid(void);
//pid_t fork(void);
fk = vfork(); 使用vfork创建子进程,返回值与fork一致
if(fk > 0)
{
while(1)
{
printf("This is the parent process,and its PID is %d\n",getpid());
sleep(1);
}
}
else if(fk == 0)
{
while(1)
{
printf("This is a child process,and the child process is %d\n",getpid());
sleep(1);
}
}
return 0;
}
5.3.2 程序运行结果
CLC@Embed_Learn:~/Linux_System_Programming/Linux_process_programming/05_Vfork_creat_process$ gcc Process_programming3.c
CLC@Embed_Learn:~/Linux_System_Programming/Linux_process_programming/05_Vfork_creat_process$ ./a.out
This is a child process,and the child process is 45705
This is a child process,and the child process is 45705
This is a child process,and the child process is 45705
This is the parent process,and its PID is 45704
This is the parent process,and its PID is 45704
This is the parent process,and its PID is 45704
This is the parent process,and its PID is 45704
This is the parent process,and its PID is 45704
^C
从以上运行结果可以看出,程序在执行过程中,父进程(PID为45704)和子进程(PID为45705)。且子进程执行3次后退出才执行的父进程。因为子进程直接使用父进程存储空间,不拷贝,必须保证子进程先运行,当子进程调用exit退出后,父进程才执行。
5.4 查看控制子进程变量cnt的值验证子进程使用父进程空间
5.4.1 程序代码
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t fk;
//pid_t getpid(void);
int cnt = 0;
//pid_t fork(void);
fk = vfork();
if(fk > 0)
{
while(1)
{
printf("This is the parent process,and its PID is %d\n",getpid());
sleep(1);
printf("cnt == %d\n",cnt); 输出cnt的值
}
}
else if(fk == 0)
{
while(1)
{
printf("This is a child process,and the child process is %d\n",getpid());
sleep(1);
cnt++;
if(cnt == 3)
{
exit(0); 退出子进程
}
}
}
return 0;
}
5.4.2 程序运行结果
CLC@Embed_Learn:~/Linux_System_Programming/Linux_process_programming/05_Vfork_creat_process$ gcc Process_programming4.c
CLC@Embed_Learn:~/Linux_System_Programming/Linux_process_programming/05_Vfork_creat_process$ ./a.out
This is a child process,and the child process is 45782
This is a child process,and the child process is 45782
This is a child process,and the child process is 45782
This is the parent process,and its PID is 45781
cnt == 3
This is the parent process,and its PID is 45781
cnt == 3
This is the parent process,and its PID is 45781
cnt == 3
This is the parent process,and its PID is 45781
cnt == 3
This is the parent process,and its PID is 45781
cnt == 3
This is the parent process,and its PID is 45781
^C
从以上运行结果可以看出,程序中的自进程是直接使用父进程的内存空间。
六、进程退出
6.1 进程退出的方式
如下图
6.2 进程退出补充
如下图
结束语
非常感谢您的耐心阅读!在您即将离开之际,如果您觉得内容有所收获或启发,不妨动动手指,点个赞再走,这将是对我莫大的鼓励和支持。衷心感谢您的点赞与关注,期待未来能继续为您带来更多有价值的内容!谢谢您!