程序:包含一系列信息的文件,这些信息描述了如何在运行时创建一个进程:
进程:正在运行的程序的实例。即操作系统为 该程序运行 分配的一些资源。(抽象概念)
单道、多道程序设计:
时间片(timeslice):“量子(quantum)"或“处理器片(processor slice) "
(面试题:Linux上有哪些进程调度的策略/算法?)
并行和并发:
进程控制块(PCB):
# 当前系统资源的上限
boyangcao@MyLinux:~$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 15407
max locked memory (kbytes, -l) 65536
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 15407
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
boyangcao@MyLinux:~$ ulimit -n 2048 # (可以修改open files 上限为2048)
进程状态反映进程执行过程的变化。这些状态随着进程的执行和外界条件的变化而转换。
进程相关的命令:
ps -aux/-ajx :显示终端上的所有进程:
ps -aux
ps -ajx
a:显示终端上的所有进程,包括其他用户的进程
u:显示进程的详细信息
x:显示没有控制终端的进程
j:列出与作业控制相关的信息
boyangcao@MyLinux:~$ ps -aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.2 225368 9072 ? Ss 10:20 0:02 /sbin/init splash
USER: 进程所属的用户
PID:进程ID
TTY:当前进程所属终端(可用 tty指令查询当前进程所属终端)
STAT:状态
D 不可中断Uninterruptible (usually IO)
R 正在运行,或在队列中的进程
S 处于休眠状态
T 停止或被追踪
Z 僵尸进程
W 进入内存交换(从内核2.6开始无效)
X 死掉的进程
< 高优先级
N 低优先级
s 包含子进程
+ 位于前台的进程组
START:开始时间
TIME: 持续时间
COMMAND:执行哪个命令开始运行的进程
boyangcao@MyLinux:~$ ps -ajx
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
0 1 1 1 ? -1 Ss 0 0:02 /sbin/init splash
PPID:父进程ID
PID:进程ID
PGID:进程组ID
SID:会话ID
top:实时显示进程动态:
可以在使用top命令时加上 -d 来指定显示信息更新的时间间隔,在top命令执行后,可以按以下按键对显示的结果进行排序:
kill:杀死进程
#杀死进程
kill [-signal] pid
kill -l 列出所有信号
# 信号:9)SIGKILL 强制杀死进程
kill -SIGKILL 进程ID
kill -9 进程ID
killall name 根据进程名杀死进程
进程号和相关函数:
pid_t getpid (void);
pid_t getppid (void) ;
pid_t getpgid(pid_t pid);
系统允许一个进程创建新进程,新进程即为子进程,子进程还可以创建新的子进程,形成进程树结构模型。
#include
#include
pid_t fork (void) ;
// fork() creates a new process by duplicating the calling process(复制调用的进程(父进程),及虚拟地址空间).
#include
#include
pid_t fork(void);
作用:用于创建子进程
返回值:
fork()的返回值会返回两次,一次在父进程中,一次在子进程中。
- 在父进程中返回创建的子进程的PID
- 在子进程中返回0
如何区分父进程和子进程,通过fork的返回值。
- 在父进程中返回-1,表示创建子进程失败,并设置errno。
父子进程之间的关系:
区别:
子进程会继续执行当前代码(从fork()创建子进程开始向下执行)
1. fork()函数的返回值不同
父进程中: >0 返回的子进程的ID
子进程中: =0
2. PCB中的一些数据有区别
当前进程的pid
当前进程的父进程ID ppid
信号集
共同点:
某些状态下:子进程刚被创建出来,还没有执行任何写数据的操作
- 用户区的数据相同
- 文件描述符表
父子进程对变量是不是共享的?
- 刚开始的时候是相同的,如果修改了数据就不共享了
- "读时共享(子进程被创建,两个进程没有做任何写的操作),写时拷贝"。
返回值:
失败的两个主要原因:
示例:
#include
#include
#include
int main()
{
// 创建子进程
pid_t pid = fork();
// 判断是子进程还是父进程
if(pid > 0)
{
printf("pid: %d\n", pid);
// 当前是父进程
// 如果大于0,返回的是创建的子进程的进程号PID
printf("i am parent process, pid : %d, ppid : %d\n", getpid(), getppid());
}
else if(pid == 0)
{
// 当前是子进程
printf("i am child process, pid : %d, ppid : %d\n", getpid(), getppid());
}
for(int i = 0; i < 5; i++)
{
printf("i: %d, pid = %d\n", i, getpid());
sleep(1);
}
return 0;
}
父子进程虚拟地址空间:
fork()以后,子进程的用户区数据和父进程—样。内核区也会拷贝过来,但是pid会不同;并且fork()返回值也不同,父进程返回创建的子进程的进程号PID,子进程返回0。
实际上,Linux的 fork()函数是通过读时共享 写时拷贝(copy- on-write)实现的。
写时拷贝是一种可以推迟甚至避免拷贝数据的技术。
内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间。
只用在需要写入的时候才会复制地址空间,从而使各个进程拥有各自的地址空间。也就是说,资源的复制是在需要写入的时候才会进行,在此之前,只有以只读方式共享。
注意:fork之后父子进程共享文件,fork产生的子进程与父进程是相同的文件描述符,指向相同的文件表,引用计数增加,共享文件偏移指针。
使用GDB调试的时候,GDB默认只能跟踪一个进程,可以在fork函数调用之前,通过指令设置GDB调试工具跟踪父进程或者是跟踪子进程,默认跟踪父进程。
#1. 设置调试父进程或者子进程
set follow-fork-mode parent(默认)
set follow-fork-mode child
#2. 设置调试模式
set detach-on-fork on
set detach-on-fork off
# - 默认为on,表示调试当前进程的时候,其它的进程继续运行;
# - 如果为off,调试当前进程的时候,其它进程被GDB挂起。(fork()处挂起)
#3. 查看调试的进程:
info inferiors
(gdb) info inferiors
Num Description Executable
* 1 process 4948 /home/boyangcao/Linux/Lesson18/hello (当前调试进程)
2 process 4952 /home/boyangcao/Linux/Lesson18/hello
#4. 切换当前调试的进程
inferior id
(gdb) inferiors 2
#5. 使进程脱离 GDB调试
detach inferiors id
# 移除进程id
remove inferiors id
(gdb) detach inferiors 1
exec函数族作用:根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。
(ps. 因为主进程中可能还有一些内容需要继续运行,所以单纯取代调用进程内容是不合理的):
exec函数族的函数执行成功后不会返回,因为调用进程的实体,包括代码段,数据段和堆栈等都已经被新的内容取代,只留下进程ID 等一些表面上的信息仍保持原样;只有调用失败了,它们才会返回 -1,从原程序的调用点接着往下执行。
// 标准C库函数(对execve()封装)
// l(list) 参数地址列表,以空指针结尾
int execl (const char *path,const char *arg, ...(char*) NULL);
// p(path) 按PATH环境变量指定的目录搜索可执行文件
int execlp(const char *file,const char *arg,...(char*) NULL);
// v(vector) 存有各参数地址的指针数组的地址
int execv (const char *path,char *const argv[] );
int execvp (const char *file,char *const argv[] );
// e(environment) 存有环境变量字符串地址的指针数组的地址
int execvpe (const char *file,char *const argv[], char *const envp[]);
int execle(const char *path,const char *arg,...,(char*) NULL,charconst envp[]);
// Linux/Unix 系统函数
int execve(const char *filename,char *const argv[], char *const envp[]);
#include
extern char **environ;
int execl(const char *path, const char *arg, ...);
参数:
- path: 需要指定的执行文件路径/名称。
相对路径:a.out
绝对路径:/home/boyangcao/a.out (推荐)
- arg: 是执行可执行文件所需的参数列表。
第一个参数一般没有作用 为了方便 一般写的是执行程序的名称。
第二个参数开始往后,就是执行程序所需的参数列表。
参数最后需要以NULL结束(NULL: 哨兵)
返回值:
只有当调用失败才会有返回值-1,并且设置errno
调用成功没有返回值。
eg. execl("/bin/ps", "ps", "-aux", NULL);
int execlp(const char *file, const char *arg, ...);
会到环境变量中查找指定的可执行文件,如果找到了就执行,找不到就执行不成功。
(环境变量 env -> PATH 中的路径)
参数:
- file: 需要执行的可执行文件的文件名
a.out
ps
- arg: 是执行可执行文件所需的参数列表。
第一个参数一般没有作用 为了方便 一般写的是执行程序的名称。
第二个参数开始往后,就是执行程序所需的参数列表。
参数最后需要以NULL结束(NULL: 哨兵)
返回值:
只有当调用失败才会有返回值-1,并且设置errno
调用成功没有返回值。
eg. execlp("ps", "ps", "-aux", NULL);
int execv (const char *path,char *const argv[] );
参数:
- path: 需要指定的执行文件路径/名称。
相对路径:a.out
绝对路径:/home/boyangcao/a.out (推荐)
- argv: 需要的参数的一个字符串数组
eg.
char* agrv[] = {"ps", "aux", "null"};
int execv ("/bin/ps", argv);
int execve(const char *filename,char *const argv[], char *const envp[]);
参数:
- envp: 自行指定查找可执行文件的路径
eg.
char* envp[] = {"/home/boyangcao", "home/aaa", "home/aaa"};
char* agrv[] = {"ps", "aux", "null"};
int execve ("ps", argv, envp);
示例:创建一个子进程,在子进程中执行exec函数族中的函数
#include
#include
int main(){
// 创建一个子进程,在子进程中执行exec函数族中的函数
__pid_t pid = fork();
if(pid > 0){
// 父进程
printf("i am parent process, pid: %d\n", getpid());
sleep(1);
}
else if(pid == 0){
// 子进程
// execl("/home/boyangcao/Linux/Lesson19/hello", "hello", NULL);
// 执行shell命令
execl("/bin/ps", "ps", "-aux", NULL);
printf("i am child process, pid = %d\n", getpid());
}
for(int i = 0; i < 3; i++)
printf("i = %d, pid = %d\n", i, getpid());
}
// 标准C库函数
#include
void exit (int status);
// Linux系统函数
#include
void _exit (int status);
//status参数:进程退出时的一个状态信息,父进程回收子进程资源时可以获得。
boyangc+ 8300 0.0 0.0 4512 760 pts/4 S+ 14:39 0:00 ./wait
boyangc+ 8301 0.0 0.0 0 0 pts/4 Z+ 14:39 0:00 [wait] <defunct>
boyangc+ 8302 0.0 0.0 0 0 pts/4 Z+ 14:39 0:00 [wait] <defunct>
boyangc+ 8303 0.0 0.0 0 0 pts/4 Z+ 14:39 0:00 [wait] <defunct>
boyangc+ 8304 0.0 0.0 0 0 pts/4 Z+ 14:39 0:00 [wait] <defunct>
boyangc+ 8305 0.0 0.0 0 0 pts/4 Z+ 14:39 0:00 [wait] <defunct>
解决办法:
#include
#include
pid_t wait(int *wstatus);
功能:等待任意一个子进程结束,如果任意一个子进程结束,此函数会回收子进程的资源
参数:
- int* wstatus: 进程退出时的状态信息,传入的是一个int类型地址,传出参数。
- NULL: 不需要得到状态信息
返回值:
- 成功:返回被回收的子进程的id
- 失败:返回-1.(所有子进程都结束了 & 调用函数失败)
调用wait()函数的进程会被挂起(阻塞),直到它的一个子进程退出或收到一个不能被忽略的信号时才被唤醒(相当于继续向下执行)
- 如果没有子进程,函数立刻返回-1.
- 如果子进程都已经结束了,也会立即返回-1.
退出信息相关宏函数:
WIFEXITEI (status) // 非0,进程正常退出
WEXITSTATUS (status) // 如果上宏为真,获取进程退出的状态(exit的参数)
wIFSIGNALED (status) // 非0,进程异常终止
WTERMSIG (status) // 如果上宏为真,获取使进程终止的信号编号
wIFSTOPPED (status) // 非0,进程处于暂停状态
wSTOPSIG (status) // 如果上宏为真,获取使进程暂停的信号的编号
wIFCONTINUED (status) // 非0,进程暂停后已经继续运行
示例:
#include
#include
#include
#include
#include
int main()
{
// 有一个父进程,创建5个子进程(兄弟)
pid_t pid;
// 创建5个子进程
for(int i = 0 ; i < 5; i++)
{
pid = fork();
if(pid == 0) break;
}
if(pid > 0)
{
// 主进程
while(1)
{
printf("parent process, pid == %d\n", getpid());
int st;
int ret = wait(&st);
if(ret == -1) break;
if(WIFEXITED(st))
{
// 是不是正常退出
printf("退出的状态码: %d\n", WEXITSTATUS(st));
}
if(WIFSIGNALED(st))
{
// 是不是异常终止
printf("被哪个信号干掉了:%d\n", WTERMSIG(st));
}
printf("child die, pid = %d\n", ret);
sleep(1);
}
}
else if(pid == 0)
{
// 子进程
printf("child process, pid == %d\n", getpid());
sleep(1);
exit(0);
}
return 0; // exit(0);
}
#include
#include
pid_t waitpid(pid_t pid, int *wstatus, int options);
功能:回收指定进程号的子进程,可以设置是否阻塞。
参数:
- pid:
pid > 0: 表示具体某个子进程的pid
pid = 0: 回收当前进程组的所有子进程pid
pid = -1: 回收所有的子进程,相当于wait() (最常用!)
pid < -1: 某个进程组的组id的绝对值,回收指定进程组(加负号)中的子进程。
- options: 设置阻塞或非阻塞
0: 阻塞
WNOHANG: 非阻塞(当前没有进程要回收的话立即返回)
- 返回值:
> 0: 返回子进程的id
= 0: options = WNOHANG, 表示还有子进程运行中
-1: 错误,或者没有子进程了。
eg.
int st;
int ret = waitpid(-1, &st, WNOHANG);