智能手表:给用户提供了很多功能,比如心率实时检测、计步等功能,时间、消息、身体健康等数据展示,那么这里面涉及到不同功能之间的资源调用以及相互独立方面的问题。要求用户交互时手表能够及时反馈、如果心率功能不能使用,也不能影响到其他功能的正常使用,也就是说手表的程序编写涉及到功能之间的独立性、交互快速、资源的高效利用有很高的要求,而这些都离不开对进程和线程的理解和熟练运用
进程的好处:
进程是操作系统中的一个基本概念。它是操作系统进行资源分配和调度的基本单位,是一个程序关于某数据集合上的一次运行活动,它是系统进行操作分派及资源分配的独立单位。
进程是操作系统分配资源和调度的一个独立单位。在技术上,它是一个执行中的程序的实例。具体来说,进程表示一个程序的执行过程,它包括程序代码及其所用数据的内存映像,系统资源,以及这个程序执行时的线程或线程集合。不同进程之间是相互独立的
理解:也就是说进程是一个运行中的程序,进程包含了运行这个程序所需要的一切资源,无论是加载的程序代码本身以及分配的内存资源或者是其他的东西,都是属于进程的一部分。
总结:
补充
进程和线程的区别对比
对比维度 | 进程 | 线程 |
---|---|---|
最小单位类型 | 资源分配 | CPU 调度(执行) |
资源占用 | 独立内存和系统资源 | 共享进程资源,仅拥有自己的栈和寄存器 |
通信方式 | 复杂(IPC) | 简单(直接访问共享内存) |
并发性 | 进程间并发 | 线程间并发(更轻量) |
开销 | 高(创建 / 销毁 / 切换) | 低(仅需保存 / 恢复上下文) |
总结
进程是系统进行资源分配和调度的基本单位,而线程是 CPU 调度和分派的基本单位。理解这一点有助于优化程序性能(如多线程并行计算)和资源管理(如避免进程间过度切换)。
进程由程序、数据集合和进程控制块三部分组成:
(1)常见状态
这些状态的转换是由操作系统内核的调度程序(scheduler)和其他机制控制和管理的。通过合理地管理进程状态,操作系统能够确保CPU资源得到高效地利用,同时也使得多个进程能够并发执行
(2)额外的进程状态
一些其他的操作系统(如Linux系统)还有额外的两个状态:
在这些系统中,操作系统通过挂起就绪和挂起阻塞状态,可以将不活跃的进程移出物理内存,暂时存储在磁盘上的交换区(swap space),从而为其他更高优先级或者活跃的进程释放宝贵的内存资源。当被挂起的进程需要恢复执行时,操作系统会将它们重新加载到内存中。这种机制提高了系统的灵活性和资源利用率
(1)一般情况下的进程生命周期
图:
生命周期的大概过程:
(2)一些状态的切换
问:就绪状态是如何进入运行状态中
处于就绪状态的进程被选中并分配CPU时间是通过操作系统内部的一个重要机制——CPU调度程序(CPU Scheduler)或调度器来实现的
以上步骤反复进行,确保每个就绪状态的进程都有机会获得CPU时间。调度算法的选择对于确保系统公平性和效率是非常关键的。在不同的操作系统和不同的场景中,可能会采用不同的调度策略以满足具体的系统性能要求
(3)示例:执行一个c文件
#include
#include
#include
#include
#include
#include
#include
//当前的父进程也是另外一个父进程的子进程,getpid是获取当前进程的pid,getppid是获取自己父进程的pid
//如果子进程中的父进程和原本父进程中的pid不同,是因为父进程先退出,子进程被领养,导致 getppid() 返回领养进程的 PID
int main()
{
// 创建子进程
pid_t pid = fork();
if(pid > 0)
{
puts("----------父进程执行---------");
printf("这里是父进程里,执行的代码 :父进程的pid是:%d\n", getpid());
// 父进程,等待子进程结束:
int status;
pid_t temp_pid = wait( &status );
printf("--- 父进程结束,子进程的结束码是:%d ,子进程的pid是:%d\n", status, temp_pid);
puts("-----------父进程结束------------");
}
else if(pid == 0)
{
puts("===== 子进程执行 =====");
printf("这里是子进程里,执行的代码 :子进程的pid是:%d\n", getpid() );
printf("这里是子进程里,执行的代码 :父进程的pid是:%d\n", getppid() );
sleep(1);
puts("=== 子进程结束了 ===");
}
else
{
perror("进程创建失败!");
exit(EXIT_FAILURE);
}
printf("----------end:%d-----------\n", pid );
return 0;
}
(1)语法
#include
#include
pid_t fork(void);
当我们使用fork函数时,就相当于创建了一个子进程,接下来我们来梳理父子进程之间的关系以及工作流程。
(1)fork进程的特点
当进程是通过fork进程创建之后,具有一下特点:
如果在程序的某个时候想要结束子进程,那么可以使用一下方式
(1)exit函数
#include
void exit(int status);
功能: 结束一个进程,先释放缓冲区
参数:
status: 结束进程时的状态,
返回值:
正常结束用0
非正常结束-1
(2)_exit
include <unistd.h>
void _exit(int status);
功能: 结束一个进程,不会释放缓冲区直接结束 (相当于暴力结束)
参数:
status: 结束进程时的状态,
return
正常结束用0
非正常结束-1
(3)atexit
include <stdlib.h>
int atexit(void (*function)(void));
功能: 注册一个进程结束后的运行函数 // 当进程结束时会调用 function 这个函数
参数:
指向返回值时void类型参数时void 的一个函数指针
返回值:
成功: 返回 0
失败: 返回一个非 0 值
(1)wait
概念:阻塞等待子进程结束,然后回收子进程的资源
语法
#include
pid_t wait(int *status);
参数
返回值
特点
pid_t
是一种数据类型,通常用于存储进程ID(process ID)。在头文件pid_t
作为进程ID的数据类型。其具体的实现细节(例如是长整型,整型还是短整型)可能因不同的系统或编译器而不同,但一般来说,它通常被定义为一个整数类型相关宏
WIFEXITED(status):如果进程正常终止,返回真。这种情况下可以使用宏
WEXITSTATUS(status)获取进程的退出状态码。
WIFSIGNALED(status):如果进程是被信号打断,返回真。这时可以使用宏
WTERMSIG(status)获取打断进程信号的编号。
可以使用命令给进程发送信号:kill -信号编号 进程的pid
(2)waitpid
概念: 阻塞等待指定子进程结束,然后回收子进程的资源
语法
#include
pid_t waitpid(pid_t pid, int *status, int options);
参数
返回值
(3)getpid
概念:获取当前进程的PID
语法
#include
pid_t getpid();
返回值
(4)getppid
概念:获取父进程的pid
语法
#include
pid_t getppid();
返回值
(1)概念
是指一种特殊状态下的子进程。该子进程的父进程已经结束,但子进程仍未结束,那么这样的子进程就是孤儿进程,即没有老爸了。
(2)特点
但按照Unix和Linux的规定,孤儿进程的父进程将被设置为init进程,也就是PID为1的进程。也就是说当一个子进程变成孤儿进程后,会强行将pid为1的进程作为子进程的老爸,也就是继父
注意:init进程是在系统启动时创建的,其负责真正地删除所有的孤儿进程。当一个孤儿进程的父进程终止时,其的父进程会自动设置为init。当孤儿进程结束时,init进程会处理孤儿进程的退出状态(return code),从而确保系统资源的正确释放。也就是说孤儿进程本身只是状态比较特殊,本身对系统没有什么危害
(1)概念
是指这样的子进程,子进程已经结束,但父进程一直忙于自己的事没有清理该子进程的资源,导致子进程的资源(比如pcb)没有被释放。那么这样的子进程被称为僵尸进程,似死未死
父进程回收子进程:简单来说,进程的生命周期是这样的:创建 -> 运行 -> 结束。当一个进程结束时,它的进程描述符(包括进程状态、退出码等信息)仍然会留在系统中,这就需要父进程来读取这些信息并完成清理工作,这个过程称为“回收”子进程。
(2)僵尸进程危害
如果父进程没有回收子进程,那么子进程虽然已经结束,但是其进程描述符仍然在,它就变成了僵尸进程。每个僵尸进程在内核中占用一定的资源,如果大量僵尸进程存在,可能会消耗尽系统的进程资源。
子进程在执行结束后大部分资源已经被释放,但是如进程ID、结束状态(也就是其进程控制块PCB中的一部分信息)等还会保留在系统中,需要父进程来回收。一旦父进程完成了这个回收工作,那么这个进程的所有资源就都被清除,它在系统中就不存在了。但是如果父进程没有处理的话,就会导致僵尸进程的产生,一小部分还好,但是如果有大量僵尸进程,还是会产生一定影响。
(3)释放僵尸进程
通常,可以通过编程方式避免僵尸进程的产生,比如在父进程中通过调用wait()
或waitpid()
函数等待子进程结束,并回收其资源.或者也可以在父进程执行完后结束父进程,那么僵尸进程会变成孤儿进程,从而被init进程释放掉。但推荐在代码中也保证子进程正常的结束。
(1)概念
守护进程是一种在后台运行的特殊进程(周期性的进程),它独立于控制终端并周期性地执行某种任务或者等待处理某些发生的事件。守护进程通常在系统引导时启动,并在系统关闭时终止。它们通常用于执行一些系统级的任务,比如邮件服务器、日志处理、定时任务等。
Linux系统在启动时就已经创建了很多守护进程。比如systemd、NetworkManager、sshd等,以下是其中几种守护进程的说明
(2)作用
后台服务:守护进程最常见的功能就是提供后台服务,例如web服务器、数据库服务器、文件服务器等,他们都是以守护进程形式运行,接收和处理来自用户或其他程序的请求。
定时任务:某些守护进程用于定时执行某些任务,例如cron守护进程可以周期性的执行预定义的命令或脚本。
事件响应:有些守护进程用于监听系统或硬件的某些事件,当这些事件发生时执行相应的操作。例如,ACPID守护进程在接收到电源状态改变的消息时会做出相应的响应。
资源管理:守护进程也常常用于系统资源管理,例如系统日志守护进程rsyslogd负责系统日志的收集、筛选、转发和保存,网络管理守护进程NetworkManager负责网络设备的管理和网络连接的自动配置。
硬件交互:有些守护进程用于支持和管理与硬件设备的交互,例如打印机守护进程CUPS,可帮助用户设置打印任务,处理与打印机的交互。
总结:能够在后台周期执行任务,给系统不同的功能模块提供持续的服务。比如定时清理、日志备份等
(3)创建守护进程
步骤
kill
命令来结束守护进程。
kill -9 1276
或kill 1276
。其中1276是进程PID,-9表示发送的sigkill信号。SIGKILL
是Unix和类Unix系统中的一个信号,它的值通常为9。此信号发送给一个进程后,会立即终止该进程(有可能进程资源释放会异常)。如果没有-9
,那么表示发送的SIGKILL
信号值为15,即SIGTERM
,也是默认值。会更优雅 的结束进程(会先释放进程所占资源,然后再结束)。即-9:暴力结束 。 -15 或不写:优雅结束创建守护进程代码示例:
#include
#include
#include
#include
#include
#include
#include
#include
void createKeep()
{
// 创建子进程
pid_t pid = fork();
if(pid > 0)
{
exit(0);
}
else if(pid == 0)
{
// 创建守护进程,只需要子进程:
pid_t sid = setsid(); // 创建新会话,并使调用进程成为该会话的领头进程。这也会使进程脱离任何控制终端。
if(sid == -1)
{
perror("set sid error!");
exit(1);
}
// 这个pid就是守护进程的id
pid_t keep_id = getpid();
printf("守护进程的pid是:%d\n", keep_id );
// 切换工作目录 :
if(chdir("/") < 0)
{
perror("change dir error!");
exit(1);
}
// 最后关闭文件描述符
close(STDIN_FILENO);
close(STDERR_FILENO);
}else
{
perror("进程创建失败!");
exit(EXIT_FAILURE);
}
}
int main() {
puts("==== 守护进程创建中 ====");
createKeep();
while(true)
{
puts("守护进程执行中......");
sleep(3);
}
return 0;
}
通过查看进程id来看守护进程是否存在:
ps aux
systemd
服务)。stdin/stdout/stderr
通常重定向到/dev/null
)。nginx
、数据库mysql
)。sshd
(远程登录服务)、crond
(定时任务服务)。