概念
进程的概念: 进程是运行的程序,是动态的,一个程序运行多次就是多个进程。
进程的属性
- 进程控制块
- 操作系统使用一个结构体记录进程的各类属性,该结构体被称为进程控制块
- 进程标识
- 进程id,每个进程的id都是唯一的
- 父进程id
- 地址空间
- 代码段的起始地址和长度
- 数据段的起始地址和长度
- 堆栈段的起始地址和长度
- 打开文件列表
- 打开文件时,在打开文件列表中记录被打开文件的信息
- 关闭文件时,在打开文件列表中删除被关闭文件的信息
- 进程终止时,操作系统遍历打开文件列表,将尚未关闭的文件关闭
进程的特性
并发性:
- 父进程和子进程并发运行
- 父进程创建子进程后,父子进程都处于运行状态中
- 两个进程的输出结果是交织在一起的
- 两者的代码段内容相同
- 父进程从fork()返回处执行,fork()返回为子进程的PID
- 子进程从fork()返回处执行,fork()返回0
隔离性:
- 进程的地址空间是互相隔离的
每个进程拥有自己的地址空间
进程仅能访问自己的地址空间
如果出现非法内存访问,仅仅当前进程受到影响 - 全局变量
全局变量存在于两个地址空间中,并非被两个进程共享
父进程和子进程访问的是自己的全局变量,互相不影响
相关系统调用函数
获取进程id
-
头文件
#include <sys/types.h>
#include <unistd.h>
-
函数
pid_t getpid(void);
pid_t getppid(void);
-
功能描述
getpid获取当前进程ID
getppid获取父进程ID
pid_t是C语言中用户自定义类型,在sys/types.h中定义typedef int pid_t;
创建子进程
- 头文件
#include <unistd.h>
- 函数
pid_t fork(void);
- 功能
- 创建一个子进程,父子进程并发运行
- 子进程复制父进程的如下属性
- 代码段、数据段的内容,父子进程拥有相同的代码和数据
- 打开文件列表
- 不复制进程的PID属性,父子的进程的PID是唯一的
- 返回值
- pid是进程ID的缩写,pid_t是使用typedef定义的进程ID类型
- 父进程从fork返回处继续执行,在父进程中,fork返回子进程PID
- 子进程从fork返回处开始执行,在子进程中,fork返回0
例子
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = fork();
printf("pid = %d\n", pid);
if (pid == 0)
printf("In child process\n");
else
printf("In parent process\n");
return 0;
}
输出两次pid = 是因为fork之后创建了子进程,子进程与父进程并行运行,所以打印了两次pid.
装入进程
execl函数
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
- 功能
将当前进程的地址空间的内容全部清空
将path指定的可执行程序的代码和数据装入到当前进程的地址空间 - 参数
该函数的参数个数可变
最后一个参数必须是NULL
第一个参数path指定被装入程序的路径
可以是命令的绝对路径
可以是命令的相对路径 - 返回值
装入失败后,返回值为-1
装入成功后,从被装入程序的main函数开始执行
例子
#include <stdio.h>
#include <unistd.h>
int main()
{
puts("before exec");
int error = execl("/bin/echo", "echo", "a", "b", "c", NULL);
if (error < 0)
perror("execl");
puts("after exec");
return 0;
}
最后的after exex没有打印是因为execl函数把原来程序的代码段、数据段等都清空了,加载新的echo的代码。
execlp函数
#include <unistd.h>
int execlp(const char *file, const char *arg, ...);
-
功能
与execl相同 -
execl和execlp的区别
在execl中,第一个参数指定可执行程序的路径
该路径可以是绝对路径
该路径可以是相对于当前工作目录的相对路径在execlp中,除此之外,该路径可以是PATH环境变量指定目录下的相对路径
#include <stdio.h>
#include <unistd.h>
int main()
{
puts("before exec");
int error = execlp("echo", "echo", "a", "b", "c", NULL);
if (error < 0)
perror("execl");
puts("after exec");
return 0;
}
execv函数
#include <unistd.h>
int execv(const char *path, const char *argv[]);
- 功能:与execl相同
- 区别:参数不同, 函数名execv末尾的v表示vector,参数以数组的形式传递给可执行程序
execvp函数
#include <unistd.h>
int execvp(const char *file, char *argv[]);
- 功能: 与execv相同
- execv和execvp的区别
在execvp中,第一个参数指定可执行程序的路径该路径可以是PATH环境变量指定目录下的相对路径。
退出程序
exit原型
#include <stdlib.h>
void exit(int status);
- 功能
正常退出当前进程
将status & 0xFF作为退出码返回给父进程 - 预定义常量
EXIT_SUCCESS,为0的数值,表示程序正常退出
EXIT_FAILURE,为非0的数值,表示程序执行过程发生了 错误,异常退出 - 在linux shell中,可以通过特殊的环境变量$?获得程序的退出码.
- 在正常的int main函数结束return时其实也调用了exit
atexit原型
#include <stdlib.h>
int atexit(void (*function)(void));
- 功能
- 注册一个回调函数function,进程正常结束时,function会被调用
- 如果注册多个回调函数,进程结束时,以与注册相反的顺序调用回调函数
例子
#include <stdio.h>
#include <stdlib.h>
void f1()
{
puts("f1");
}
void f2()
{
puts("f2");
}
void f3()
{
puts("f3");
}
int main()
{
atexit(f1);
atexit(f2);
atexit(f3);
puts("main");
return 0;
}
等待进程
wait原型
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
- 功能: 等待子进程结束
- 参数: 如果status不为NULL,子进程的退出码保存在status指向的变量中
退出码
-
进程可能由于不同的原因退出
- 主动调用exit正常退出
- 接受信号后退出
-
查询退出原因的宏
名称 | 功能 |
---|---|
WIFEXITED(status) | 如果进程通过调用exit正常退出,则返回真 |
WEXITSTATUS(status) | 如果进程通过调用exit正常退出,返回进程的退出码 |
WIFSIGNALED(status) | 如果进程接受信号后退出,则返回真 |
WTERMSIG(status) | 如果进程接受信号后退出,返回导致进程退出的信号 |
例子
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
void child()
{
exit(123);
}
int main()
{
int pid;
pid = fork();
if (pid == 0)
child();
int status;
wait(&status);
if (WIFEXITED(status)) {
printf("WIFEXITED = true\n");
printf("WEXITSTATUS = %d\n", WEXITSTATUS(status));
}
return 0;
}