C语言进程概念,孤儿进程,僵尸进程,exec函数族,守护进程
【进程】
-
进程
进程是一个程序的一次执行的过程
每一个进程都分配一个虚拟的4G内存
0-3G : 用户
3G-4G : 内核 -
进程和程序的区别
程序是静态的
进程是动态的
-
进程的内存管理
-
正文段、用户数据段、系统数据段
-
进程号
唯一的标识一个进程
-
进程的类型
交互进程(ctrl+z / jobs -l / bg / fg / & / kill -l)
ctrl + z : 使进程进入挂起状态(T), 被挂起的进程称为作业
jobs -l: 查看挂起的进程
bg % 作业号 :使作业恢复前台运行, 不能ctrl+c结束
fg % 作业号 : 使作业恢复前台运行, 可以ctrl+c结束
kill -l : 查看信号种类
kill -9 PID : 杀死进程
ps -ajx : 查看进程的运行状态
批处理进程(运维)
守护进程( 1 init )
- 进程的运行状态
ps -ajx
运行态R:此时进程或者正在进行,或者准备运行
内核调度程序到CPU上执行 running
等待态:此时进程需要满足一些条件,如果不满足就等待
可中断S:如果进程收到信号会醒来 ctrl+c
不可中断D:如果进程收到信号不会醒来
停止态T:此时进程被中止SIGSTOP
死亡态Z:已终止的进程、僵尸进程
但还在进程向量数组中占有一个task_struct结构
task_struct{
pid_t pid;
R;
…
};
< 高优先级
N 低优先级
L 有些页被锁进内存
s 会话组组长
+位于前台的进程组
l 多线程,克隆线程
ctrl + alt + f1 - f6 : 打开字符终端
用户名:user
密码:passworld
主要是为了多用户使用计算机
结束字符终端: alt + f7
top
-
top -p PID : 动态查看进程状态
-
renice -5 PID : 改变进程的NI值(默认0)
- 进程相关的函数
-
/fork/exit
创建进程、退出进程 -
wait/waitpid
回收进程资源
pid_t fork(void);
/*********************************************
*功能: 创建一个进程
*参数: 无
*返回值:
-
成功 0 :子进程 > 0 :父进程
-
失败 -1
**********************************************/
/*=============================================================================
#
# 创建者: 荆卫
#
# QQ : 1329177433
#
# Last modified: 2021-07-05 15:11
#
# Filename: fork.c
#
# Description(描述): 进程 fork
#
=============================================================================*/
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
printf("开始创建进程\n");
pid_t pid;
pid = fork();
// 创建进程失败
if (pid < 0)
{
perror("fork");
return -1;
}
else
if (pid > 0) // 父进程
{
while(1){
printf("i'am father!!!\n");
sleep(1);
}
}
else
if (pid == 0) // 子进程
{
while(1){
printf("i'am son!!!\n");
sleep(1);
}
}
return 0;
}
从fork函数往下分为两个进程开始运行。
父进程和子进程执行顺序是随机的。
-
fork函数特性:
1.子进程创建时,几乎拷贝了父进程全部内容,包括代码段、数据段、堆栈段、文件描述符、虚拟地址空间
2.同一个父进程创建的子进程都是属于同一个进程组 pkill -9 -g PGID
3.进程是管理资源的最小单位 -
思考:
int main()
{
fork();
fork();
}
会创建多少个进程,进程之间的关系是什么样的?
会创建4个进程,关系如下:
exit和_exit函数
exit/_exit:exit调用c库,会刷新缓冲区,而_exit不会刷新缓冲区
函数原型
#include <stdlib.h>
void exit(int status);
void fun()
{
printf("cccccc");
exit(0); // c库函数,可以刷新缓冲区
}
int main(int argc, const char *argv[])
{
printf("aaaaa\n");
fun();
printf("bbbbbb\n");
return 0;
}
+ 函数原型
#include <unistd.h>
void _exit(int status);
void fun()
{
printf("cccccc");
_exit(0); // 系统调用,不会刷新缓冲区
}
int main(int argc, const char *argv[])
{
printf("aaaaa\n");
fun();
printf("bbbbbb\n");
return 0;
}
wait函数和waitpid函数
函数原型
pid_t wait(int *status);
/**************************************
* 功能:父进程等待子进程退出,并回收子进程资源(僵尸进程),阻塞
* 参数: @status 获取子进程退出状态
返回值:
结束返回pid
没结束返回0
***************************************/
WEXITSTATUS(status) 获取子进程返回值,
WIFEXITED(status) 判断子进程是否正常结束
int main(int argc, const char *argv[])
{
pid_t pid;
pid = fork();
if (pid < 0)
{
perror("fork");
exit(-1);
}
else if (pid > 0)
{
printf("father wait....\n");
// wait函数
printf("----------------\n");
int status;
wait(&status);
// 获取子进程的返回值
printf("WEXITSTATUS:%d\n",WEXITSTATUS(status));
// 获取子进程是否正常结束 1正常 0异常
printf("wIFEXITED:%d\n",WIFEXITED(status));
}
else{
sleep(2);
printf("son.......\n");
exit(20);
}
return 0;
}
pid_t waitpid(pid_t pid, int *status, int options);
等待指定进程退出
waitpid(pid_t pid,NULL/int *status,0/WNOHANG);
/**************************************
*参数说明:
* pid=-1 时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
*
* 0:阻塞 / WNOHANG:非阻塞
*
*返回值:
* 结束返回pid
* 没结束返回0
***************************************/
代码示例:
int main(int argc, const char *argv[])
{
pid_t pid;
pid = fork();
if (pid < 0)
{
perror("fork");
exit(-1);
}
else if (pid > 0)
{
printf("father wait....\n");
// wait函数
printf("----------------\n");
int status;
// wait(&status); // 阻塞
waitpid(-1, NULL, WNOHANG); // 非阻塞
// 获取子进程的返回值
// printf("WEXITSTATUS:%d\n",WEXITSTATUS(status));
// 获取子进程是否正常结束 1正常 0异常
// printf("wIFEXITED:%d\n",WIFEXITED(status));
}
else{
sleep(2);
printf("son.......\n");
exit(20);
}
return 0;
}
僵尸进程和孤儿进程
子进程先与父进程退出---父进程未回收资源---子进程会变成僵尸进程
危害:占用进程号、内存空间、PCB进程控制块等
解决:wait / waitpid / 信号
注意:任何进程结束都会变成僵尸进程,只是时间有长有短
父进程先与子进程退出---子进程会变成孤儿进程---被init进程接管(收养)
init进程:系统启动后运行的第一个用户空间进程,pid=1,会定期扫描系统,收养孤儿进程。
注意:孤儿进程一般没什么危害
exec函数簇
-
概念:
函数族提供了一种在进程中启动另一个程序执行的方法。
它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段,在执行完之后,原调用进程的内容除了进程号外,其他全部被新程序的内容替换了。另外,这里的可执行文件既可以是二进制文件,也可以是Linux下任何可执行脚本文件。比如bash用到了exec函数来执行我们的可执行文件。
-
在Linux中使用exec函数族主要有以下两种情况
当进程认为自己不能再为系统和用户做出任何贡献时,就可以调用任何exec 函数族让自己重生。
如果一个进程想执行另一个程序,那么它就可以调用fork函数新建一个进程,然后调用任何一个exec函数使子进程重生。
- 函数
#include <unistd.h>
int execl(const char *path, const char *arg, …);
int execv(const char *path, char *const argv[]);
int execlp(const char *file, const char *arg, …);
int execvp(const char *file, char *const argv[]);
int execle(const char *path, const char *arg, …, char *const envp[]);
int execve(const char *path, char *const argv[], char *const envp[]);
返回值:
成功不返回
失败返回 -1 更新 errno
注意:
exec函数的参数表传递方式以函数名的第五位字母来区分:
字母为"l"(list)的表示逐个列举的方式;
字母为"v"(vertor)的表示将所有参数构造成指针数组传递;
以p结尾的函数可以只给出文件名
以"e"(enviromen)结尾的两个函数execle、execve就可以在
envp[]中设置当前进程所使用的环境变量
使用execle和execve可以自己向执行进程传递环境变量,但不会继承Shell进程的环境变量
事实上,这6个函数中真正的系统调用只有execve,其他5个都是库函数,它们最终都会调用execve这个系统调用
守护进程daemon
1.守护进程:
在linux中与用户交互的界面叫终端,从终端运行起来的程序都依附于这个终端,
当终端关关闭时,相应的进程都会被关闭,守护进程可以突破这个限制。
2.特点:
在后台服务的进程
生存期很长
守护进程独立于控制终端
比如:init进程 pid=1 开机运行 关机才结束
3.守护进程创建流程:
1. 创建子进程,父进程退出
fork(void);
2. 在子进程中创建新会话
setsid(void);
3. 修改工作目录
chdir("");
4. 修改umask (增加安全性)
umask();
5. 关闭文件描述(回收资源)
close();