进程基本介绍
概念
程序:
存放在磁盘上的指令和数据的有序集合(文件),静态的。
进程:
进程就是程序的一次执行过程,动态的,包括创建、调度、执行和消亡。
进程的内容
每当我们在Linux上开启一个进程后,一个进程的在内存空间中有如下内容:
- 程序段:存储程序编译后的二进制文件
- BSS段:存储未初始化的全局变量
- 数据段:存储已完成初始化的全局变量
- 堆:程序中动态分配的空间
- 栈:存储程序中的局部变量,如形参、函数中定义的变量、函数的返回值
- 进程控制块:存储进程的相关信息,如进程id、进程用户、状态和优先级等
进程的类型
- 交互进程:在shell下启动。以在前台运行,也可以在后台运行
- 批处理进程:和在终端无关,被提交到一个作业队列中以便顺序执行
- 守护进程:和终端无关,一直在后台运行
进程的状态
- 运行态:进程正在运行,或者准备运行
- 等待态:进程在等待一个事件的发生或某种系统资源,此状态下还可细分为不可中断态和可中断态
- 停止态:进程被中止,收到信号后可继续运行
- 死亡态:已终止的进程,但pcb没有被释放
进程基本操作
创建子进程
在linux下,子进程为由另外一个进程(对应称之为父进程)所创建的进程。
在C语言中通过
fork函数
来创建子进程。创建失败时,函数返回值为EOF(-1);创建成功时,父进程返回子进程的进程号,子进程返回0,通过fork的返回值区分父进程和子进程。创建完子进程后,子进程会将父进程中fork函数后面的代码复制一份然后执行这份代码,此时父子进程的执行顺序不定,由Linux操作系统决定。
函数返回值和所需头文件:
#include <unistd.h>
pid_t fork();
代码示例:
#include <stdio.h>
#include <unistd.h>
//创建子进程
int main()
{
pid_t pid;
printf("before fork\n");
pid = fork();
//fork后子进程会执行下面的代码,不影响父进程
//通过fork返回的pid值来判断当前是哪个进程
if(pid == 0) {
//子进程
printf("This is child process\n");
printf("pid=%d\n", (int)pid);
while(1) {
sleep(1);
printf("child process\n");
}
} else if(pid > 0){
//父进程
printf("This is father process\n");
printf("pid=%d\n", (int)pid);
while(1){
sleep(1);
printf("father process\n");
}
} else{
//创建失败
printf("fork error\n");
}
printf("after fork\n");
while(1) {
sleep(1);
}
return 0;
}
父子进程关系
-
子进程继承(复制)了父进程的内容
-
父子进程有独立的地址空间,互不影响
-
若父进程先结束 子进程成为孤儿进程,被init进程收养,子进程变成后台进程
-
若子进程先结束 父进程如果没有及时回收,子进程变成僵尸进程
进程退出
在C语言中,可以使用
exit(status)
函数来完成进程的退出操作,该函数主要进行一些进程资源回收的操作。调用该函数后会立即结束当前的进程并将status返回。
使用该函数结束进程时会刷新(流)缓冲区。
函数返回值和所需头文件:
#include <stdlib.h>
#include <unistd.h>
void exit(int status);
代码示例:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
printf(“using exit…\n”);
printf(“This is the end”);
exit(0);
}
注意:C语言中的主进程(main函数)执行到 return 0
的时候会自动调用exit()
函数完成主进程的资源回收。
进程回收
子进程结束时由父进程回收,孤儿进程由init进程回收,若没有及时回收会出现僵尸进程。
使用
wait(status)
函数来回收子进程。成功时返回回收的子进程的进程号;失败时返回EOF(-1)。
若子进程没有结束,父进程一直阻塞 若有多个子进程,哪个先结束就先回收。
status 指定保存子进程返回值和结束方式的地址,status为NULL表示直接释放子进程PCB,不接收返回值。
函数返回值和所需头文件:
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int option);
代码示例:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
//进程的回收
int main()
{
pid_t pid, rpid;
int status;
pid = fork();
if(pid < 0){
perror("fork");
return -1;
} else if(pid == 0){
sleep(1);
printf("child process will exit\n");
//回收子进程
exit(2);
} else {
rpid = wait(&status);
//注意:这里要获取子进程的返回值需要使用宏WEXITSTATUS(status)
printf("child process status = %d\n", WEXITSTATUS(status));
}
while(1)
{
sleep(1);
}
return 0;
}
通过宏获取子进程回收状态:
- WIFEXITED(status):判断子进程是否正常结束
- WEXITSTATUS(status):获取子进程返回值
- WIFSIGNALED(status):判断子进程是否被信号结束
- WTERMSIG(status):获取结束子进程的信号类型
注意:wait
函数是回收父进程下的任意子进程,要想回收指定的进程可以通过 waitpid
函数来实现。
waitpid函数使用:
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int option);
成功时返回回收的子进程的pid或0;
失败时返回EOF
pid可用于指定回收哪个子进程或任意子进程
status指定用于保存子进程返回值和结束方式的地址
option指定回收方式,0 或 WNOHANG
exec函数族
在上面我们创建子进程后,子进程会执行主函数中fork之后的代码,那我们想让主进程和子进程分别执行不同的代码又要怎么办呢?这时我们可以使用
exec函数族
中的函数来解决这个问题。进程调用exec函数族执行某个程序后,当前进程的内容会被指定的程序替换。
实现父子进程执行不同的程序:
- 父进程创建子进程
- 子进程调用exec函数族
- 父进程不受影响
函数:
#include <unistd.h>
int execl(const char *path, const char *arg, …); //path需要写全路径
int execlp(const char *file, const char *arg, …); //path不需要写全路径,去系统变量PATH中查找
参数说明:
- path:执行的程序名称,包含路径
- arg…:传递给执行的程序的参数列表,这是一个可变参数
int execv(const char *path, char *const argv[]); //path需要写全路径
int execvp(const char *file, char *const argv[]); //path不需要写全路径,去系统变量PATH中查找
参数说明:
- path:执行的程序名称,包含路径
- argv:封装成指针数组的形式
#include <stdlib.h>
int system(const char *command);
参数说明:
- command:要执行的指令
返回值:
- 成功时返回命令command的返回值;失败时返回EOF
注意:
- 当前进程等待command执行结束后才继续执行
代码示例:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
//exec函数族的使用
int main()
{
#if 0
if(execl("/bin/ls", "ls", "-a", "-l", "./", NULL) == EOF)
perror("execl");
puts("");
if(execlp("/ls", "ls", "-a", "-l", "./", NULL) == EOF)
perror("execl");
puts("");
#endif
#if 0
char* args[] = {
"ls", "-a", "-l", NULL};
if(execv("/bin/ls", args) == EOF)
perror("execv");
#endif
system("ls -al ./");
return 0;
}
守护进程
守护进程(Daemon Process)是Linux三种进程类型之一。是 Linux 中的后台服务进程。是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。
其特点是:
- 始终在后台运行
- 独立于任何终端
- 周期性的执行某种任务或等待处理特定事件
相关概念:
- 进程组(Process Group): 进程集合,每个进程组有一个组长(Leader),其进程 ID 就是该进程组 ID。
- 会话(Session): 进程组集合,每个会话有一个组长,其进程 ID 就是该会话组 ID。
- 控制终端(Controlling Terminal):每个会话可以有一个单独的控制终端,与控制终端连接的 Leader 就是控制进程(Controlling Process)。
创建守护进程步骤:
-
创建子进程,父进程退出:子进程变成孤儿进程,被init进程收养并在后台运行。
-
子进程创建新会话:子进程成为新的会话组长并脱离原先的终端。
-
更改当前工作目录:守护进程一直在后台运行,其工作目录不能被卸载 重新设定当前工作目录cwd。(这步可以不做)
-
重设文件权限掩码:文件权限掩码设置为0,只影响当前进程。(这步可以不做)
-
关闭打开的文件描述符:关闭所有从父进程继承的打开文件并脱离终端,
stdin / stdout / stderr
无法再使用。
代码实现:
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
//创建一个守护进程
int main()
{
pid_t pid;
//1.创建一个子进程
pid = fork();
if(pid < 0) {
perror("fork");
return -1;
} else if(pid > 0) {
//2.父进程退出
exit(0);
}
//getpid 获取当前线程id
printf("before sid=%d, pid=%d, pgid=%d\n", getsid(getpid()), getpid(), getpgid(getpid()));
//3.开启一个新的会话(终端)
if(setsid() < 0) {
perror("setsid");
exit(0);
}
printf("after sid=%d, pid=%d, pgid=%d\n", getsid(getpid()), getpid(), getpgid(getpid()));
//4.修改工作目录[选做]
chdir("/");
//5.设置初始文件操作权限[选做]
if(umask(0) < 0)
{
perror("umask");
exit(0);
}
//6.关闭默认的标准io流
for(int i =0; i < 3; i++)
close(i);
printf("last.....");
sleep(100);
return 0;
}
文章评论