目录
1 进程启动和终止
- c 程序启动过程
- 内核启动特色例程
- 启动例程
- 在进程的main函数执行之前内核会启动
- 该例程放置在/lib/libc.so.***中
- 编译器在编译时会将启动例程编译进可执行文件中
- 启动例程作用
- 搜集命令行的参数传递给main函数中的argc和argv
- 搜集环境信息构建环境表并传给main函数
- 登记进程的终止函数
- 进程终止
- 正常终止
- 从main函数返回
- 调用exit(标准c库函数)
- 调用_exit或_Exit(系统调用)
- 最后一个线程从其启动例程返回
- 最后一个线程调用pthread_exit
- 异常终止
- 调用abort
- 接受到一个信号并终止
- 最后一个线程对取消请求做处理响应
- 进程返回
- 通常程序运行成功返回0,否则返回非0
- 在shell中可以查看进程返回值(echo $?)
- 正常终止
- 进程启动和终止过程
- 终止函数登记
2 进程状态、进程创建和进程分类
2.1 进程查看和进程状态
- 查看当前进程
ps
- 查看所有进程
ps -ef
- 查看进程状态
ps -aux
- 进程状态stat
2.2 进程调度和进程状态变化
- 进程调度
- 进程状态变化关系
2.3 进程标识
2.4 创建进程
/*******************************************************************
* Description: fork建立子进程例程
* 在父进程中fork返回的是子进程的pid
* 子进程(在子进程中返回的是0
* 父子进程不一谁先执行,根据系统调度算法决定
*******************************************************************/
int example2()
{
printf("pid: %d\n", getpid());
pid_t pid;
pid = fork(); //创建子进程
//在fork之后会运行两个进程(父进程和子进程)
if (pid < 0) {
perror("fork error\n");
} else if (pid > 0) { //父进程(在父进程中fork返回的是子进程的pid)
printf("I am parent process,pid is %d,ppid is %d, fork return is %d\n", getpid(), getppid(), pid);
} else { //子进程(在子进程中返回的是0)
printf("I am child process,pid is %d,ppid is %d, fork return is %d\n", getpid(), getppid(), pid);
}
printf("pid=%d\n", getpid());
sleep(1);
return 0;
}
/*******************************************************************
* Description: 父子进程交替运行
*******************************************************************/
int example3()
{
printf("current pid: %d\n", getpid());
pid_t pid;
pid = fork(); //创建子进程
//在fork之后会运行两个进程(父进程和子进程)
if (pid < 0) {
perror("fork error\n");
} else if (pid > 0) { //父进程
for (int i = 0; i < 10; i++) {
printf("I am parent process,pid is %d,ppid is %d, fork return is %d\n", getpid(), getppid(), pid);
sleep(1);
}
} else { //子进程
for (int i = 0; i < 10; i++) {
printf("I am child process,pid is %d,ppid is %d, fork return is %d\n", getpid(), getppid(), pid);
sleep(1);
}
}
return 0;
}
- 父进程fork子进程,子进程会继承父进程的一些信息。数据段、堆、栈是复制过去的,物理内存各自有各自的空间。
/*******************************************************************
* Description: 子进程的内存、堆、栈与父进程的关系
*******************************************************************/
int g_v = 30; //全局变量存放在数据段
int example4()
{
int a_v = 30; //局部变量存放在栈中
static int s_v = 30; //静态变量存放在数据段
printf("current pid: %d\n", getpid());
pid_t pid;
pid = fork(); //创建子进程
//在fork之后会运行两个进程(父进程和子进程)
if (pid < 0) {
perror("fork error\n");
} else if (pid > 0) { //父进程
g_v = 40;
a_v = 40;
s_v = 40;
printf("I am parent process,pid is %d,ppid is %d, fork return is %d\n", getpid(), getppid(), pid);
printf("g_v: %p, a_v: %p, s_v: %p\n", &g_v, &a_v, &s_v);
} else { //子进程
g_v = 50;
a_v = 50;
s_v = 50;
printf("I am child process,pid is %d,ppid is %d, fork return is %d\n", getpid(), getppid(), pid);
printf("g_v: %p, a_v: %p, s_v: %p\n", &g_v, &a_v, &s_v);
}
printf("pid: %d\n", getpid());
return 0;
}
可以观察到,父子进程的数据段、堆、栈中的内容被修改,而内存地址是相同的,可以知道fork出的子进程会复制父进程的虚拟内存,但不会复制物理内存。如下图,可直观描述父子进程的内存关系。
/****************************************************************
* Description: 父进程打开文件,设置偏移量,子进程追加内容
* ***************************************************************/
int example5(int argc, char* argv[])
{
if (argc < 2) {
fprintf(stderr, "usage: %s file [exit|_exit|return]\n", argv[0]);
exit(1);
}
int fd = open(argv[1], O_WRONLY);
if (fd < 2) {
perror("open error!\n");
exit(1);
}
pid_t pid;
pid = fork(); //创建子进程
if (pid < 0) {
perror("fork error\n");
} else if (pid > 0) { //父进程
//父进程将文件偏移量调整到文件尾部
if (lseek(fd, 0L, SEEK_END) < 0) {
perror("lseek error!");
exit(1);
}
} else { //子进程
//子进程从文件尾部追加内容
char* str = "hello world\n";
ssize_t size = strlen(str) * sizeof(char);
sleep(3); //等待父进程设置偏移量完成
//此处的fd是从父进程中复制过来的
//但是和父进程中的fd都是指向同一个文件
if (write(fd, str, size) != size) {
perror("write error!\n");
exit(1);
}
}
printf("pid: %d finish \n", getpid());
//父子进程都要关闭各自的文件描述符
close(fd);
return 0;
}
- 如果连续fork() n次,会产生2的n次方个子进程
2.6 进程链和进程扇
- 创建进程链
/****************************************************************
* Description: 进程链例程
* ***************************************************************/
int example6(int argc, char* argv[])
{
int counter = 0;
if (argc < 2) { //没有指定创建多少个进程,默认为2
counter = 2;
} else {
counter = atoi(argv[1]); //字符转换成整形
}
pid_t pid;
for (int i = 1; i < counter; i++) {
pid = fork();
if (pid < 0) {
perror("fork error!\n");
} else if (pid > 0) {
//父进程退出循环,由子进程创建子进程
break;
}
}
printf("pid: %d ,ppid: %d\n", getpid(), getppid());
while (1) {
sleep(1);
}
return 0;
}
- 创建进程扇
/****************************************************************
* Description: 进程链例扇
* ***************************************************************/
int example7(int argc, char* argv[])
{
int counter = 0;
if (argc < 2) { //没有指定创建多少个进程,默认为2
counter = 2;
} else {
counter = atoi(argv[1]); //字符转换成整形
}
pid_t pid;
for (int i = 1; i < counter; i++) {
pid = fork();
if (pid < 0) {
perror("fork error!\n");
} else if (pid == 0) {
//子进程退出循环,由父进程创建子进程
break;
}
}
printf("pid: %d ,ppid: %d\n", getpid(), getppid());
while (1) {
sleep(1);
}
return 0;
}
ps -ef | grep 进程名 #查看特定的进程
pstree #进程树,查看父子进程关系
2.7 守护进程、孤儿进程、僵尸进程
- 守护进程
- 孤儿进程:
父进程结束,子进程就变成了孤儿进程,会由1号进程(init进程)领养。 - 僵尸进程:
子进程退出了,但是父进程没有用wait或waitpid去获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中,这种进程称为僵尸进程。
3 进程的相关系统调用
3.1 wait函数
void out_status(const int status)
{
if (WIFEXITED(status)) {
printf("normal exit: %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("abnormal exit: %d\n", WTERMSIG(status));
} else if (WIFSTOPPED(status)) {
printf("stop exit: %d\n", WSTOPSIG(status));
} else {
printf("unknow exit\n");
}
}
/******************************************************************************************************************
* Description: wait()回收线程资源,通过宏判断进程终止类型
* WIFEXITED(status)、WIFSIGNALED(status)、WIFSTOPPED(status)判断是否发生相应类型的终止
返回值为true/false
* WEXITSTATUS(status)、WTERMSIG(status)、WSTOPSIG(status)能根据status解析处具体的终止信息
如果是正常终止,则返回exit传入的数字
* ****************************************************************************************************************/
int example8()
{
int status;
pid_t pid;
if ((pid = fork()) < 0) {
perror("fork error\n");
exit(1);
} else if (pid == 0) {
printf("pid: %d, ppid: %d\n", getpid(), getppid());
exit(3); //子进程终止运行(正常终止)
}
//父进程阻塞,等待子进程结束并回收
wait(&status);
out_status(status);
printf("-------------------------------------------------------------------------\n");
if ((pid = fork()) < 0) {
perror("fork error\n");
exit(1);
} else if (pid == 0) {
printf("pid: %d, ppid: %d\n", getpid(), getppid());
int i = 3, j = 0;
int k = i / j; //除零运算,非正常终止
}
//父进程阻塞,等待子进程结束并回收
wait(&status);
out_status(status);
printf("-------------------------------------------------------------------------\n");
if ((pid = fork()) < 0) {
perror("fork error\n");
exit(1);
} else if (pid == 0) {
printf("pid: %d, ppid: %d\n", getpid(), getppid());
pause(); //暂停终止
}
//此处为父进程执行,pid为子进程ID号
do {
pid = waitpid(pid, &status, WNOHANG | WUNTRACED);
if (pid == 0) //pid==0证明子进程尚未结束
sleep(1);
} while (pid == 0);
out_status(status);
printf("-------------------------------------------------------------------------\n");
return 0;
}
3.2 exec函数
/****************************************************************
* Description: exec函数例程
* ***************************************************************/
int example9()
{
//const char* const 定义常量字符串
const char* const cmd1 = "cat"; //相对路径
const char* const cmd2 = "/bin/cat"; //绝对路径
const char* const argv1 = "/etc/passwd";
const char* const argv2 = "/etc/group";
pid_t pid;
if ((pid = fork()) < 0) {
perror("fork error\n");
exit(1);
} else if (pid == 0) {
//子进程调用exec函数,执行新的程序
if (execl(cmd2, cmd1, argv1, argv2, nullptr) < 0) {
perror("execl error!\n");
exit(1);
}
printf("after execl...\n"); //如果execl调用成功,此句不会执行
}
sleep(1);
printf("----------------------------------------------------------------------\n");
char* argv[4] = { (char*)cmd1, (char*)argv1, (char*)argv2, NULL };
if ((pid = fork()) < 0) {
perror("fork error\n");
exit(1);
} else if (pid == 0) {
//子进程调用exec函数,执行新的程序
if (execvp(cmd2, argv) < 0) {
perror("execl error!\n");
exit(1);
}
printf("after execl...\n"); //如果execl调用成功,此句不会执行
}
sleep(1);
printf("----------------------------------------------------------------------\n");
wait(nullptr);
return 0;
}
3.3 system函数
- 文中所涉及例程及源码全部上传至以下链接中
https://download.csdn.net/download/qq_45601625/85300206