Lecture 4: Processes 1

回顾:

  1. 内核模式代码比用户模式代码拥有更多的权限
  2. 中断改变了调用内核代码的执行流程
  3. 系统调用允许我们运行内核代码来访问操作系统的服务

本章小结:

  • 介绍进程及其实现

  • 进程状态和状态转换

  • 进程管理的系统调用

进程介绍

定义

进程(process)程序运行实例的抽象

  • 程序(program)是被动的,“位于”磁盘上
  • 进程(process)具有与其相关联的控制结构,可能是活动的,并且可能有分配给它的资源(例如I/O设备、内存、处理器)。

管理进程所需的所有信息都由内核存储在进程控制块(process control block, PCB)中。
所有的进程控制块都记录在进程表中。

进程的内存映像

一个进程的内存映像包含:

  • 程序代码(可以在运行相同代码的多个进程之间共享)
  • 数据段、堆栈(stack)堆(heap)

每个进程都有自己的逻辑地址空间,其中堆栈被放置在相对的两侧,以允许它们增长

进程在内存中的表示

进程的生命周期 

进程状态和转换

状态

  • 刚刚创建了一个新(new)进程。它有一个PCB,正在等待被承认,尽管它可能还没有在内存中。
  • 就绪(ready)进程正在等待CPU可用。
  • 当前正在运行(running)的进程的指令由CPU执行。
  • 阻塞(blocked)的进程无法继续,例如正在等待I/O
  • 终止(terminated)的进程不再可执行。数据结构——PCB可暂时保存。
  • 中止(suspended)的进程被换出(不作进一步讨论)

转换

状态转换包括:

  1. New→ready:承认进程并投入执行
  2. Running → blocked:例如,进程正在等待输入或执行系统调用
  3. Ready→running:进程调度器(process scheduler, 指在操作系统中负责决定进程执行顺序的组件)选择该进程
  4. Blocked→ready:事件发生,例如I/O操作已经完成
  5. Running→ready:进程放弃CPU,例如由于中断(interrupt)暂停(pause)
  6. Running→exit:进程已经完成,例如程序结束或遇到异常

中断和系统调用驱动这些转换。

操作系统队列

OS Queues

进程的操作系统实现

上下文转接:多路编程

现代计算机是多路编程(multi-programming)系统
假设是单处理器系统,各个进程的指令是顺序执行的:

  • 多路编程可以追溯到“MULTICS”(多路存取计算机系统(Multiplexed Information and Computing Service))时代
  • 多路编程是通过交错执行进程,将CPU时间划分为时间片来实现的
  • 通过称为上下文切换(context switching)的过程在进程之间交换控制
  • 时间片的长度和上下文切换时间之间存在一种权衡
  • 真正的并行需要硬件支持

当发生上下文切换时,系统保存旧进程的状态加载新进程的状态(产生开销(overhead))。

  • 保存更新进程控制块
  • (重新)启动⇒进程控制块读取 

较短的时间片段会产生良好的响应时间,但有效利用率较低。例如,假设上下文切换和时间片都使用1ms。那么:

  • 100个进程中的最后一个开始运行需要99x (1+1) = 198ms。
  • CPU时间的一半(1/(1+1)=0.5)是在做有用的工作。

长时间切片会导致较差的响应时间,但会带来更好的有效利用。例如,假设上下文切换时间为1ms,时间片为100ms。那么: 

  • 100个进程中的最后一个开始运行需要99x (100+1) = 9999ms。
  • CPU时间的99%(100/(1+100)=0.99)是在做有用的工作。

进程控制块包含三种类型的属性

  • 进程标识 Process identification(PID, UID,父PID)
  • 进程控制信息 Process control information(进程状态、调度信息等)
  • 进程状态信息 Process state information(用户寄存器、程序计数器、堆栈指针、程序状态字、内存管理信息、文件等)

进程控制块内核数据结构,也就是说,它们是受保护的,只有在内核模式下才能访问!

  • 允许用户应用程序直接访问它们可能会损害它们的完整性
  • 操作系统通过系统调用(例如设置进程优先级)代表用户管理它们。

切换过程:

  1. 保存进程状态(程序计数器、寄存器)
  2. 更新PCB(running -> ready/blocked)
  3. 将PCB移动到适当的队列(ready/blocked)
  4. 运行调度程序,选择新进程
  5. 更新到新PCB中的运行(running)状态
  6. 更新内存管理单元(MMU)
  7. 恢复过程

进程实现

表和控制块

操作系统会将有关“资源”状态的信息存放在表格中:

  • 进程表 Process tables(进程控制块)
  • 内存表 Memory tables(内存分配、内存保护、虚拟内存)
  • 输入/输出表 I/O tables(可用性、状态、传输信息)
  • 文件表 File tables(位置、状态)

进程表为每个进程都保存着一个进程控制块,该控制块在进程创建时予以分配。 表由内核进行维护,并且通常会相互关联

系统调用

进程创建 

进程创建(Process Creation)

在计算机操作系统中,进程创建是指创建一个新的进程,以便执行某个程序或任务。

真正的系统调用被“包装”在操作系统库中(例如libc),遵循一个定义良好的接口(例如POSIX, WIN32 API)。

例如,在类unix操作系统上,调用fork来创建进程的副本。在Linux上,用于实现fork的底层系统调用是clone

Linux下的进程创建:Fork和Exec模式

fork()创建当前进程的完全复制

  • 子进程执行的第一条指令是fork调用后的第一条指令

fork()将子进程的进程标识符返回给父进程

fork()返回0子进程

常见的模式如下所示:

  • 调用fork()来创建当前进程的精确副本。
  • 在子进程中,调用“exec”函数之一,用一个新程序替换当前进程。例如execl("/bin/ls", "ls", "-l", 0)将当前进程替换为ls,这是bash等Unix shell中典型的调用模式。

  1. 父进程调用fork()创建子进程

  2. 子进程:

    • 打印"Child process"

    • 执行ls -l命令显示当前目录详细列表

    • 命令执行完毕后子进程退出

  3. 父进程:

    • 等待子进程结束

    • 打印子进程的退出状态

    • 然后父进程退出

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
    pid_t const pid = fork();

#include语句:包含必要的头文件

  • stdio.h:标准输入输出

  • unistd.h:提供fork()execl()等系统调用

  • sys/wait.h:提供waitpid()函数

main()函数:程序入口

pid_t const pid = fork();

  • 调用fork()创建新进程

  • 返回值:

    • <0:创建失败

    • =0:当前是子进程

    • >0:当前是父进程,返回值为子进程ID

错误处理: 

if(pid < 0){
    printf("Fork failure\n");
    return -1;
}

 子进程分支:

else if(pid == 0) {
    printf("Child process\n");
    execl("/bin/ls", "ls", "-l", 0);
}
  • 打印"Child process"

  • 使用execl()执行/bin/ls程序,参数为ls -l

  • execl()成功后不会返回,原进程映像被替换

 父进程分支:

else {
    int status = 0;
    waitpid(pid, &status, 0);
    printf("Child %d returned %d\n", pid, status);
}
  • 使用waitpid()等待指定子进程结束

  • 打印子进程ID和退出状态

进程终止

进程终止(Process Termination)

指计算机操作系统中一个运行中的程序或应用程序实例结束运行的过程。

通过exitabort的系统调用可以用来显式地通知OS进程已经终止

  • 必须取消资源分配
  • 输出必须刷新
  • 可能需要执行流程管理

一个系统调用来终止其他进程:

  • UNIX/Linux: kill()
  • Windows: TerminateProcess()
<think> 我们注意到用户尝试使用sudo pkill命令终止进程,但需要输入密码。由于用户可能没有sudo权限,或者不想每次输入密码,我们可以提供替代方案。 另外,用户可能希望在不使用sudo的情况下停止自己的进程。 解决方案: 1. 使用普通kill命令(无需sudo),前提是进程属于当前用户。 2. 优化之前的停止脚本,避免使用sudo。 我们之前已经提供了使用pgrep和kill的方法,但用户可能希望更便捷的方式。 因此,我们重新设计一个更友好的停止方法,包括: - 使用当前用户的权限 - 避免使用sudo - 确保彻底停止相关进程 我们将创建一个停止脚本,它不会要求sudo权限,并且只终止当前用户的进程。 注意:在Linux系统中,普通用户只能终止自己的进程,无需sudo权限。 改进的停止脚本(无需sudo): </think> # 安全终止进程的替代方案 当您尝试使用 `sudo pkill` 命令终止进程但遇到权限问题时,这里有几种更安全、无需 `sudo` 权限的替代方案。这些方法都能确保您只终止自己拥有的进程。 ## 1. 使用用户级查找和终止命令 ```bash # 查找当前用户的Python进程 pgrep -u "$(id -u)" -f "python3 CTCF_analyse.py" # 安全终止所有匹配进程 pkill -u "$(id -u)" -f "python3 CTCF_analyse.py" ``` **解释:** - `-u "$(id -u)"`:只查找当前用户的进程 - `pkill`:直接终止匹配进程,无需sudo权限 - 这种方式避免了权限问题,只影响您自己的进程 ## 2. 使用Python脚本终止进程 创建 `stop_analysis.py` 脚本: ```python #!/usr/bin/env python3 """ 安全终止分析进程 - 无需sudo权限 """ import os import sys import signal import psutil # 需要安装: pip install psutil def main(): # 查找所有匹配进程 target_processes = [] for proc in psutil.process_iter(['pid', 'name', 'username', 'cmdline']): try: # 只匹配当前用户和特定命令 if (proc.info['username'] == os.getlogin() and 'python3' in proc.info['name'] and 'CTCF_analyse.py' in ' '.join(proc.info['cmdline'] or [])): target_processes.append(proc) except (psutil.NoSuchProcess, psutil.AccessDenied): continue if not target_processes: print("❌ 未找到运行中的分析进程") return # 显示并终止进程 print("找到以下分析进程:") for proc in target_processes: print(f" PID: {proc.pid}, 命令行: {' '.join(proc.cmdline() or [])}") # 确认终止 confirm = input("\n确定要终止这些进程吗?(y/N): ").strip().lower() if confirm != 'y': print("操作已取消") return # 优雅终止 for proc in target_processes: try: print(f"终止进程 PID: {proc.pid}") proc.send_signal(signal.SIGTERM) # 发送终止信号 proc.wait(timeout=10) # 等待进程结束 except psutil.TimeoutExpired: print(f"⚠️ 进程 {proc.pid} 未响应,强制终止") proc.kill() # 强制终止 except psutil.NoSuchProcess: pass print("✅ 所有分析进程已终止") if __name__ == "__main__": main() ``` **使用方法:** ```bash # 安装依赖 pip install psutil # 运行脚本 python3 stop_analysis.py ``` ## 3. 使用系统工具(无需sudo) ### 方案A:使用 `killall`(仅限Linux) ```bash # 终止当前用户的所有匹配进程 killall -u "$USER" -I "python3 CTCF_analyse.py" ``` ### 方案B:使用进程树终止 ```bash # 查找主进程ID main_pid=$(pgrep -u "$USER" -f "python3 CTCF_analyse.py") # 终止整个进程树 if [ -n "$main_pid" ]; then kill -15 -- -$(ps -o pgid= $main_pid | grep -o '[0-9]*') fi ``` ## 4. 创建专用停止脚本(无依赖) 创建 `safe_stop.sh`: ```bash #!/bin/bash # 安全停止脚本 - 无需sudo # 获取当前用户 CURRENT_USER=$(whoami) # 查找所有匹配进程 PIDS=$(pgrep -u "$CURRENT_USER" -f "python3 CTCF_analyse.py") if [ -z "$PIDS" ]; then echo "❌ 未找到运行中的分析进程" exit 0 fi echo "找到以下进程:" ps -p $PIDS -o pid,user,cmd read -p "确定要终止这些进程吗?(y/N) " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then echo "操作已取消" exit 0 fi # 优雅终止 for PID in $PIDS; do echo "终止进程 $PID..." kill -15 $PID done # 等待进程退出 sleep 5 # 检查并强制终止未退出的进程 for PID in $PIDS; do if ps -p $PID > /dev/null; then echo "⚠️ 进程 $PID 未响应,强制终止" kill -9 $PID fi done echo "✅ 所有进程已终止" ``` **使用方法:** ```bash chmod +x safe_stop.sh ./safe_stop.sh ``` ## 5. 在原始脚本中添加退出信号处理 修改您的 `CTCF_analyse.py` 脚本,添加优雅退出功能: ```python # 在脚本开头添加 import signal import sys def handle_exit(signum, frame): """处理退出信号""" logger.info("🛑 收到终止信号,开始优雅退出...") # 保存当前状态 save_progress() sys.exit(0) # 注册信号处理 signal.signal(signal.SIGTERM, handle_exit) # 正常终止 signal.signal(signal.SIGINT, handle_exit) # Ctrl+C # 在脚本中添加保存进度函数 def save_progress(): """保存当前进度""" logger.info("💾 保存分析进度...") # 保存矩阵数据 co_matrix.to_csv("co_matrix_partial.csv") # 保存其他重要状态 with open("progress.state", "w") as f: f.write(f"Last processed line: {current_line}") logger.info("✅ 进度已保存") ``` ## 最佳实践总结 1. **权限最小化**:避免使用 `sudo` 终止进程 2. **用户级操作**:使用 `-u $USER` 或 `pgrep -u` 限定当前用户 3. **优雅终止**: - 优先使用 `SIGTERM` (15) - 添加超时机制 - 必要时才使用 `SIGKILL` (9) 4. **状态保存**:在脚本中添加信号处理,支持优雅退出 5. **确认机制**:在停止脚本中添加用户确认步骤 这些方法都能安全地终止您的分析进程,无需 `sudo` 权限,避免安全警告。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值