Linux 进程

Linux 进程

计算机体系结构与操作系统#

冯诺依曼结构#

John von Neumann

组件:输入设备输出设备存储器控制器运算器

工作:从输入设备输入指令到存储器中(即内存),控制器分析指令(取指令)并交由运算器执行运算任务,之后将相关结果写入存储器中,并由输出设备输出结果。

冯诺依曼体系结构是一种逻辑结构,它确定了计算机各个模块的职能。

操作系统的功能#

现代计算机装载了大量硬件设备,需要一套基本的程序集合来执行任务和管理资源。这一套程序集合就是操作系统,通常也称为内核。操作系统需要将必要的任务开放为接口以暴露给应用开发者调用,这些接口就被称为系统调用,如创建进程、调整进程优先级、执行 I/O 操作等。这些系统调用在一些高级语言中也被封装为各自的方法或函数,这些方法和函数就是我们常说的 API。现代操作系统的一个很重要的功能就是管理和调度进程。

进程和线程的概念#

资源#

进程是操作系统资源分配和调度的基本单位。这里的资源指的是包括内存空间、CPU 占用在内的所有运算资源。进程控制块PCB)为操作系统用来描述进程的数据结构,记录进程的状态和控制需要的所有信息。进程状态包括新建就绪运行中等待终止五种,由调度程序管理。程序计数器记录进程接下来要执行的指令地址。进程控制块还记录了进程的调度信息、优先级页表等。

调度#

在引入线程的操作系统中,进程调度的基本单位为线程。调度方法主要有先来先服务最短作业优先轮转等策略。引起调度的事件一般包括执行完毕或异常退出、提出 I/O 请求、主动请求挂起、时间片结束等。调度程序的任务包括建立进程信息和调度队列、采用调度策略对应的算法来选中进程、完成上下文切换(恢复现场)。

进程的创建(进程克隆)#

  • fork 函数

Copy
#include <unistd.h> #include <sys/types.h> // 进程克隆 pid_t fork(void); // 对于原进程,返回值为新进程的 pid // 对于新进程,返回值为 0 // 若执行失败,返回值为 -1 // 等待子进程结束(任意一个子进程结束即返回) pid_t wait(int * status); // 返回值为子进程的 pid // 若未曾 fork 则返回 -1 // status 传入一个变量的地址用来接受子进程的退出状态(置为 NULL 以忽略退出状态) // 等待指定子进程结束 pid_t waitpid(pid_t pid, int * status, int option); // 对于参数 pid: // -1 等待所有 PGID 等于父进程 pid 的绝对值的⼦进程 // 1 等待所有⼦进程 // 0 等待所有 PGID 等于调⽤进程的⼦进程 // >0 等待 PID 等于 pid 的⼦进程 // option 指定选项,如 WNOHANG/WUNTRACED 等 // 暂停当前进程,直到接收到一个信号 int pause(void); // 挂起进程,单位为秒 unsigned int sleep(unsigned int); // 类似的,usleep 函数也用于挂起进程,单位为微秒

unistd.h 库中定义了 open / closeread / writefork / exec / sleep 等函数,参考 unistd.h;有关更多系统调用函数,另请参阅 System calls

  • 信号
Copy
#include <signal.h> // 注册信号,即针对特定信号注册回调函数 // 原型 void (* signal(int sig, void (* func) (int))) (int); // 等价于 typedef void (* sighandler_t) (int); sighandler_t signal(int signum, sighandler_t handler); // SIGABRT 异常终止; SIGFPE 算术异常; SIGILL 非法函数镜像; SIGINT 中断; SIGSEGV 非法访问; SIGTERM 终止; // 向指定进程发送信号 int kill(pid_t pid, int sig);

执行一个程序(进程替换)#

  • exec 函数族

Copy
int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(const char *path, const char *arg, ..., char *const envp[]); int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); // 以上函数在实现中都调用了 execve int execve(const char *path, char *const argv[], char *const envp[]); // path 要执行的文件路径(可以是相对路径) // file 同 path,但当其不包含 '/' 时会从 $PATH 环境变量中寻找 // argv 参数列表(以 NULL 结束) // envp 环境变量列表(以 NULL 结束)

异常情形(僵尸与孤儿)#

僵尸进程#

子进程退出,但父进程忽略了这个事件,因此操作系统为保护其状态,不会释放子进程的资源。在父进程中调用 wait 函数可避免。

孤儿进程#

与僵尸进程相反,当子进程仍在运行时,父进程先于其退出,这时子进程成为一个后台进程,其 ppid 将变为 1,即被 init 进程“收养”。孤儿进程对系统完全没有危害,常见于守护进程。

进程协作#

  • 信号灯/信号量

    • 二值信号灯:类似互斥锁,只能取 0 或 1。与互斥锁不同的是其允许其他的进程修改其值,而互斥锁必须由进程自身解锁。
    • 计算信号灯:可取任意非负值(有时也可取负值,表示饥饿进程的数量)。

信号灯的 P/V 操作是指对资源的请求与释放操作,表现为信号量的增减。P 操作表示请求资源,当条件允许时,申请成功,信号量自减;V 操作则为解除请求,信号量自增。

Copy
#include <sys/ipc.h> #include <sys/types.h> // 信号灯的结构如下 struct sem { // 当前值 int semval; // 最近一次操作的 pid int sempid; } // 文件名到键值 key_t ftok (char *pathname, char proj); int semget(key_t key, int nsems, int semflg); int semop(int semid, struct sembuf *sops, unsigned nsops); int semctl(int semid,int semnum,int cmd,union semun arg);
  • 共享内存
  • 消息队列
Copy
// 消息结构 struct my_message { // 消息类型 long int message_type; // 消息数据 // ... }; // 模式与访问权限结构 struct msgid_ds { uid_t shm_perm.uid; uid_t shm_perm.gid; mode_t shm_perm.mode; }; // 创建和访问队列 int msgget(key_t, key, int msgflg); // 添加消息(msg_sz 为消息数据的大小而不是结构体大小) int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg); // 接收消息 int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg); // 控制 int msgctl(int msgid, int command, struct msgid_ds *buf);
  • 管道
    • 无名管道:仅适用于 fork 自同一父进程的进程之间或父子进程间。又称匿名管道。
    • 有名管道:形式上类似于文件,操作上类似管道。又称命名管道。
Copy
// 无名管道 #include <unistd.h> // 创建管道 int pipe(int file_descriptor[2]); // 读写数据(使用 read/write 系统调用) ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count); // 修改描述符 int dup(int file_descriptor); int dup2(int file_descriptor_one, int file_descriptor_two); // 有名管道 #include <sys/types.h> #include <sys/stat.h> // 创建 int mkfifo(const char *filename, mode_t mode); int mknod(const char *filename, mode_t mode | S_IFIFO, (dev_t)0); // 打开/关闭操作同普通文件,使用系统调用 read/write int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode); int close(int fd);

线程管理#

  • pthread 线程库(POSIX 标准)

更多内容可参考 POSIX Threads Programming

Copy
#include <pthread.h> // 相关类型 // 线程句柄,即 tid,本质上为 int 类型 // 考虑兼容性,使用 pthread_equal() 比较大小 pthread_t tid; int pthread_equal(pthread_t t1, pthread_t t2); // 线程属性,包括挂起属性、调度策略、有效范围和优先级等 pthread_attr_t attr; // 互斥锁,确保操作的原子性 pthread_mutex_t mutex; // 条件变量,类似于信号,可用于线程等待直到条件成立 pthread_cond_t cond; // 相关函数 // 创建线程 int pthread_create(pthread_t *tidp,const pthread_attr_t *attr, (void*)(*start_rtn)(void*),void *arg); // 获取自身 tid pthread_t pthread_self(void); // 线程退出,value_ptr 用于接收返回值 void pthread_exit(void *value_ptr); // 线程取消(终止) int pthread_cancel(pthread_t thread); // 等待线程结束(类似进程 wait) int pthread_join(pthread_t thread, void **value_ptr); // 挂起线程 int pthread_detach(pthread_t thread); // 初始化/注销线程属性 int pthread_attr_init(pthread_attr_t *attr); int pthread_attr_destroy(pthread_attr_t *attr); // 获取/设置线程属性 int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate); int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate); // 创建和注销互斥锁、互斥锁属性 int pthread_mutexattr_init(pthread_mutexattr_t *mattr); int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr); // 必须在锁处于开放状态时注销 int pthread_mutex_destroy(pthread_mutex_t *mutex); int pthread_mutexattr_destroy(pthread_mutexattr_t *attr); // 互斥锁相关操作 int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex);
posted @   王牌饼干  阅读(219)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示
CONTENTS