Linux 进程

Linux 进程

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

冯诺依曼结构

John von Neumann

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

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

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

操作系统的功能

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

进程和线程的概念

资源

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

调度

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

进程的创建(进程克隆)

  • fork 函数

#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

  • 信号
#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 函数族

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 操作则为解除请求,信号量自增。

#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);
  • 共享内存
  • 消息队列
// 消息结构
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 自同一父进程的进程之间或父子进程间。又称匿名管道。
    • 有名管道:形式上类似于文件,操作上类似管道。又称命名管道。
// 无名管道
#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

#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 @ 2019-12-07 17:40  王牌饼干  阅读(211)  评论(0编辑  收藏  举报