进程
进程状态转换
一般来说,一个进程的开始都是从其父进程调用fork()函数开始,所以在系统一上电运行时,init进程就开始工作,在系统运行过程中,会不断启动新的进程(要么由init进程启动,要么由被init进程启动的其他进程所启动)。init进程的PCB是从内核的启动镜像文件中直接加载的,系统中的所有其他进程都是init进程的后代。
一个进程被启动后都是处于可运行状态(但此时进程并未占用CPU运行),处于该状态的进程可以是正在进程等待队列中排队(就绪态),也可以占用CPU正在运行(运行态)。
当系统产生进程调度时,处于就绪态的进程可以占用CPU的使用权,处于运行态。但每个进程运行时间是有限的(时间片),当进程的时间片已经耗光,如果进程还没有结束运行,那么会被系统重新放入等待队列中等待,处于就绪态,等待下一次进程的调度。另外,正处于运行态的进程即使时间片没有耗光,也可能被别的更高优先级的进程抢占,被迫重新回到等待队列中等待。
处于运行态的进程可能会因为等待某些事件、信号或资源而进入可中断睡眠态(比如进程要读取一个管道文件数据而管道为空时,或进程要获得一个锁资源而当前锁不可获取时,甚至是进程自己调用sleep()函数来强制将自己进入睡眠等)。
可中断睡眠态:可以被中断的,能响应信号的睡眠状态。在特定条件发生后,进程状态就会转变为“就绪态”(比如其他进程向管道文件写入数据,或锁资源可以被获取了,或睡眠时间到达等)。
处于运行态的进程也可能会进入不可中断睡眠态,即进程不能响应信号。但这种状态非常短暂,我们几乎无法通过ps命令将其显示,一般处于这种状态的进程都是在等待输入或输出(I/O)完成,在等待完成后自动进入就绪态。
当进程收到SIGSTOP或SIGTSTP中的其中一个信号时,进程状态会被置为暂停态,不再参与调度,但系统资源不会被释放,直到收到SIGCONT信号后被重新置为就绪态。
当进程被追踪时(常见是使用调试器调试应用程序时)收到任何信号状态都会被置为TASK_TRACED状态,不再参与调度,但系统资源不会被释放,直到收到SIGCONT信号后被重新置为就绪态。
进程在完成任务后会退出,那么此时进程状态为退出态(属于正常退出,如main()函数return,或调用exit()函数,或线程调用pthread_exit()函数)。
当不正常退出时,那么此时进程状态为僵尸进程(如进程收到kill信号)。其实不管怎么死,内核都会调用do_exit()函数来使进程状态变为僵尸进程。
僵尸进程的僵尸指的是进程的进程控制块PCB。为什么一个进程死掉之后还要把PCB留下呢?因为进程在退出时,系统会将其退出信息都保存在PCB中(比如死亡原因),得以让父进程去排查(父进程之所以要启动该进程,很大原因是要让进程去干某一件事情,当该进程死亡,父进程当然要知道那一件事情办得怎样)
当父进程去处理僵尸进程时,会将这个僵尸进程的状态设置为EXIT_DEAD,即退出态,系统才能去回收僵尸进程的内存空间,否则系统将存在越来越多的僵尸进程,最后导致系统内存不足而崩溃。
当父进程由于太忙而没能及时去处理僵尸进程时,可以考虑使用信号异步通知机制(让一个孩子在变成僵尸时给其父进程发一个信号,父进程接收到这个信号后再对其进行处理)。
当父进程先一步于子进程退出时,子进程将变成孤儿进程(没有父进程),孤儿进程将被祖先进程(init)收养。所以当孤儿进程退出时,init进程将回收资源。
进程状态
执行ps -ux可查出进程的状态。
状态 | 说明 |
R | 可运行状态。表示进程在运行队列中,处于正在运行或即将运行的状态。 只有在该状态才可能在CPU上运行,同一时刻可能有多个进程处于可运行状态 |
S | 可中断睡眠态。处于这个状态的进程可能因为等待某种事件的发生而被挂起,比如进程在等待信息 |
D | 不可中断睡眠态。通常是在等待输入或输出(I/O)完成,处于这种状态的进程不能响应异步信号 |
T | 停止态。通常是被Shell的工作信号控制,或因为处于调试器的控制下进程被追踪 |
Z | 退出态。进程成为僵尸进程 |
X | 退出态。进程即将被回收 |
s | 进程是会话其首进程 |
l | 进程是多线程的 |
+ | 进程属于前台进程组 |
< | 高优先级任务 |
启动新进程
system()函数
简单,但效率低下而且具有不容忽视的完全风险。
system.c文件
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
pid_t result;
result = system("ls -l");
return result;
}
Makefile文件
ARCH?=x86
ifeq ($(ARCH), x86)
CC = gcc
else
CC = arm-linux-guneabihf-gcc
endif
TARGET=system
BUILD_DIR=build
SRC_DIR=module
INC_DIR=include
CFLAGS = $(patsubst %,-I %,$(INC_DIR))
INCLUDES = $(foreach dir, $(INC_DIR), $(wildcard $(dir)/*.h))
SOURCES = $(foreach dir, $(SRC_DIR), $(wildcard $(dir)/*.c))
OBJS = $(patsubst %.c, $(BUILD_DIR)/%.o, $(notdir $(SOURCES)))
VPATH = $(SRC_DIR)
$(BUILD_DIR)/$(TARGET):$(OBJS)
$(CC) $(^) -o $(@)
$(BUILD_DIR)/%.o:%.c $(INCLUDE) | create_build
$(CC) -c $< -o $@ $(CFLAGS)
.PHONY:clean create_build
clean:
rm -r $(BUILD_DIR)
create_build:
mkdir -p $(BUILD_DIR)
执行过程
fork()函数
复杂,但提供更好地弹性、效率和安全性。