进程和程序的区别:程序和进程是两个不同的概念,他们的状态,占用的系统资源都是不同的。
CPU时间片是多任务操作系统中,每个进程被分配的执行时间段。通过调度算法,操作系统确保每个进程有机会执行,防止某个进程长时间占用CPU。时间片的大小影响系统响应和效率,是一种权衡考虑。
简而言之,并发是指多个任务交替执行,而并行是指多个任务同时执行。并发通常用于处理大量的任务,使系统更加响应,而并行则是通过同时执行多个任务来提高整体的处理速度。
PCB - 进程控制块(Processing Control Block),Linux内核的进程控制块本质上是一个叫做 task_struct的结构体。在这个结构体中记录了进程运行相关的一些信息
PCB 是进程存在的唯一标识,这意味着一个进程的存在,必然会有一个 PCB,如果进程消失了,那么 PCB 也会随之消失。
包含的信息: 进程id(pid), 进程的状态, 进程优先级, 进程对应的虚拟地址空间的信息等, PCB 的信息在进程切换时被保存和恢复,以确保操作系统能够正确地管理和调度多个进程。
进程一共有五种状态分别为:**创建态,就绪态,运行态,阻塞态(挂起态),退出态(终止态)**其中创建态和退出态维持的时间是非常短的,稍纵即逝。
ps: 显示当前进程的快照。
ps aux # 显示所有用户的所有进程
ps -ef # 显示所有进程的详细信息
top: 实时显示系统中正在运行的进程的信息,以及系统的整体性能。
top
kill: 终止一个进程。
kill PID # 使用进程ID(PID)终止进程
9号信号(SIGKILL)的行为是无条件杀死进程,想要杀死哪个进程就可以把这个信号发送给这个进程,操作如下:
# 无条件杀死进程, 进程ID通过 ps aux 可以查看
$ kill -9 进程ID
$ kill -SIGKILL 进程ID
Linux中进程ID为 pid_t 类型,其本质是一个正整数
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
#include <sys/types.h>
#include <unistd.h>
pid_t getppid(void);
#include <unistd.h>
pid_t fork(void);
fork
函数是在Unix/Linux操作系统中用于创建新进程的系统调用之一。调用 fork
会创建一个与调用进程几乎完全相同的新进程,这两个进程将在不同的内存空间中运行。
基本语法:
#include <unistd.h>
pid_t fork(void);
fork
返回新创建子进程的进程ID(PID)。fork
返回0。工作原理:
fork
被调用时,它会复制调用进程的内存和资源(文件描述符、环境变量等)。fork
返回不同的值,这样程序可以根据返回值来确定自己是父进程还是子进程。fork
返回的位置开始执行。示例:
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid;
pid = fork();
if (pid == -1) {
// 错误处理
perror("fork");
return 1;
} else if (pid > 0) {
// 父进程
printf("Parent process, child PID = %d\n", pid);
} else {
// 子进程
printf("Child process\n");
}
// 父子进程都会执行到这里
printf("This is common code for both parent and child\n");
return 0;
}
这段代码调用 fork
创建一个新的进程。父进程得到子进程的PID,而子进程得到0。接下来,父子进程都会执行相同的代码,但可以通过 if (pid > 0)
判断来执行不同的逻辑。
需要注意的是,fork
创建的新进程是父进程的副本,但并不共享父进程和子进程之间的变量。如果在父子进程中都修改同一个变量,它们互不影响。
父进程肯定是从main()函数开始运行的,子进程是在父进程中调用fork()函数之后被创建, 子进程就从fork()之后开始向下执行代码。
我们可以只让父进程创建子进程,如果是子进程不让其继续创建子进程,因此只需要在程序中添加关于父子进程的判断即可。
// 需要在上边的程序中控制不让子进程, 再创建子进程即可
// process_loop.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main()
{
pid_t pid;
// 在循环中创建子进程
for(int i=0; i<3; ++i)
{
pid = fork();
if(pid == 0)
{
// 不让子进程执行循环, 直接跳出
break;
}
}
printf("当前进程pid: %d\n", getpid());
return 0;
}
执行上述代码后会出现以下显示问题, 回车后即可恢复
修改后的代码:
执行结果:
当父进程创建一个子进程,那么父子进程之间可以通过全局变量互动,实现交替数数的功能吗?不过不确定可以写一段测试代码:
// number.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
// 定义全局变量
int number = 10;
int main()
{
printf("创建子进程之前 number = %d\n", number);
pid_t pid = fork();
// 父子进程都会执行这一行
printf("当前进程fork()的返回值: %d\n", pid);
//如果是父进程
if(pid > 0)
{
printf("我是父进程, pid = %d, number = %d\n", getpid(), ++number);
printf("父进程的父进程(终端进程), pid = %d\n", getppid());
sleep(1);
}
else if(pid == 0)
{
// 子进程
number += 100;
printf("我是子进程, pid = %d, number = %d\n", getpid(), number);
printf("子进程的父进程, pid = %d\n", getppid());
}
return 0;
}
结果:
两个进程中是不能通过全局变量实现数据交互的,因为每个进程都有自己的地址空间,两个同名全局变量存储在不同的虚拟地址空间中,二者没有任何关联性。如果要进行进程间通信需要使用:管道,共享内存,本地套接字,内存映射区,消息队列等方式。
在项目开发过程中,有时候有这种需求,需要通过现在运行的进程启动磁盘上的另一个可执行程序,也就是通过一个进程启动另一个进程,这种情况下我们可以使用 exec族函数
也就是说 exec族函数并没有创建新进程的能力,只是有大无畏的牺牲精神,让起启动的新进程寄生到自己虚拟地址空间之内,并挖空了自己的地址空间用户区,把新启动的进程数据填充进去。
#include <unistd.h>
// 变参函数
int execl(const char *path, const char *arg, ...);
参数:
返回值:如果这个函数执行成功, 没有返回值,如果执行失败, 返回 -1
该函数常用于执行已经设置了环境变量的可执行程序, 因此使用这个函数执行可执行程序不需要指定路径,只需要指定出名字即可。
// p == path
int execlp(const char *file, const char *arg, ...);
参数:
返回值:如果这个函数执行成功, 没有返回值,如果执行失败, 返回 -1
一般不会在进程中直接调用这个函数, 因为这样的话这个进程的代码区代码会被替换, 我们一般在调用这些函数的时候都会先创建一个子进程,在子进程中调用 exec 族函数,子进程的用户区数据被替换掉开始执行新的程序中的代码逻辑,但是父进程不受任何影响仍然可以继续正常工作。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main()
{
// 创建子进程
pid_t pid = fork();
// 在子进程中执行磁盘上的可执行程序
if(pid == 0)
{
// 磁盘上的可执行程序 /bin/ps
#if 1
execl("/bin/ps", "title", "aux", NULL);
// 也可以这么写
// execl("/bin/ps", "title", "a", "u", "x", NULL);
#else
execlp("ps", "title", "aux", NULL);
// 也可以这么写
// execl("ps", "title", "a", "u", "x", NULL);
#endif
// 如果成功当前子进程的代码区别 ps中的代码区代码替换
// 下面的所有代码都不会执行
// 如果函数调用失败了,才会继续执行下面的代码
perror("execl");
printf("++++++++++++++++++++++++\n");
printf("++++++++++++++++++++++++\n");
printf("++++++++++++++++++++++++\n");
printf("++++++++++++++++++++++++\n");
printf("++++++++++++++++++++++++\n");
printf("++++++++++++++++++++++++\n");
}
else if(pid > 0)
{
printf("我是父进程.....\n");
}
return 0;
}
进程控制主要是指进程的退出, 进程的回收和进程的特殊状态 孤儿进程和僵尸进程。
如果想要直接退出某个进程可以在程序的任何位置调用exit()或者_exit()函数。函数的参数相当于退出码
exit
函数是用于终止程序的标准库函数,它可在C和C++中使用。当程序执行到 exit
函数时,它会正常退出,并返回一个状态码给操作系统。
基本语法:
#include <stdlib.h>
void exit(int status);
status
:指定要返回给操作系统的状态码。通常,0 表示正常退出,非零值表示异常退出或错误状态。示例:
#include <stdio.h>
#include <stdlib.h>
int main() {
printf("Before exit function\n");
// 使用 exit 函数退出程序,返回状态码 0
exit(0);
// 以下代码不会执行
printf("After exit function\n");
return 0;
}
在这个例子中,当程序执行到 exit(0)
时,程序将立即终止,不会执行后面的代码。此时,操作系统将收到状态码 0,表示程序正常退出。
exit
函数的主要作用是确保程序在退出时执行一些清理工作,例如关闭文件、释放动态分配的内存等。如果不使用 exit
而直接让 main
函数返回,那么可能会导致一些清理工作无法完成。
在操作系统中,**孤儿进程是指其父进程先于它自己退出,导致它成为孤立的进程。**当一个进程创建子进程,而子进程的父进程在子进程退出之前就已经终止,这个子进程就会变成孤儿进程。
孤儿进程通常会被 init 进程(在现代系统中可能是 systemd 或其他类似的进程)接管。init 进程(PID= 1)会定期检查系统中是否存在孤儿进程,并负责清理这些进程的资源,防止它们变成僵尸进程。
以下是孤儿进程的主要特征和处理方式:
wait
或类似的机制来收集孤儿进程的退出状态,确保它们正常终止。下面是一个产生孤儿进程的简单示例,使用 fork
创建子进程,并在子进程中让它先于父进程退出:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return 1;
} else if (pid > 0) {
// 父进程
printf("Parent process (PID=%d) is sleeping...\n", getpid());
sleep(2);
printf("Parent process exits.\n");
} else {
// 子进程
printf("Child process (PID=%d) exits.\n", getpid());
exit(0);
}
return 0;
}
在这个例子中,子进程在创建后立即退出,而父进程会休眠一段时间后退出。因此,子进程成为了一个孤儿进程。
在一个启动的进程中创建子进程,这时候就有了父子两个进程,父进程正常运行, 子进程先与父进程结束, 子进程无法释放自己的PCB资源, 需要父进程来做这个件事儿, 但是如果父进程也不管, 这时候子进程就变成了僵尸进程。
僵尸进程不能将它看成是一个正常的进程,这个进程已经死亡了,用户区资源已经被释放了,只是还占用着一些内核资源(PCB)。
要处理僵尸进程,父进程通常需要调用 wait
或 waitpid
等系统调用,以获取子进程的终止状态。一旦父进程处理了子进程的终止状态,该子进程的资源就会被释放,它不再是僵尸进程。
以下是一个简单的示例,演示了一个父进程创建子进程,然后父进程休眠一段时间,导致子进程成为僵尸进程:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return 1;
} else if (pid > 0) {
// 父进程
printf("Parent process (PID=%d) is sleeping...\n", getpid());
sleep(5); // 父进程休眠一段时间,让子进程成为僵尸进程
printf("Parent process exits.\n");
} else {
// 子进程
printf("Child process (PID=%d) exits.\n", getpid());
exit(0);
}
return 0;
}
消灭僵尸进程的方法是,杀死这个僵尸进程的父进程,这样僵尸进程的资源就被系统回收了。通过kill -9 僵尸进程PID的方式是不能消灭僵尸进程的,这个命令只对活着的进程有效,僵尸进程已经死了
在父进程中进行子进程的资源回收,回收方式有两种,一种是阻塞方式wait(),一种是非阻塞方式waitpid()。
这是个阻塞函数,**如果没有子进程退出, 函数会一直阻塞等待, 当检测到子进程退出了, 该函数阻塞解除回收子进程资源。font>**这个函数被调用一次, 只能回收一个子进程的资源,如果有多个子进程需要资源回收, 函数需要被调用多次。
基本语法:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
status
:用于存储子进程终止状态的指针。可以为NULL,表示不关心子进程的终止状态。
返回值:如果成功,返回被等待子进程的进程ID(PID)。如果调用出错,返回-1,并设置 errno
表示错误类型。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return 1;
} else if (pid > 0) {
// 父进程
printf("Parent process (PID=%d) is waiting for the child process...\n", getpid());
int status;
pid_t terminated_child = wait(&status);
if (terminated_child == -1) {
perror("wait");
return 1;
}
if (WIFEXITED(status)) {
printf("Child process (PID=%d) exited with status %d.\n", terminated_child, WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("Child process (PID=%d) terminated by signal %d.\n", terminated_child, WTERMSIG(status));
}
printf("Parent process exits.\n");
} else {
// 子进程
printf("Child process (PID=%d) exits.\n", getpid());
exit(0); // 子进程退出后, 上面的wait函数解除阻塞
}
return 0;
}
waitpid
函数是用于等待指定子进程终止并获取其终止状态的系统调用。与 wait
不同,waitpid
允许指定等待的子进程,从而可以避免等待所有子进程的终止。
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
参数:
pid:
status: NULL, 和wait的参数是一样的
options: 控制函数是阻塞还是非阻塞
返回值:
阻塞回收的例子和wait基本一样, 举一个非阻塞回收的例子:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return 1;
} else if (pid > 0) {
// 父进程
printf("Parent process (PID=%d) is waiting for the child process...\n", getpid());
int status;
int options = WNOHANG; // 使用 WNOHANG 选项,非阻塞等待
pid_t terminated_child;
do {
terminated_child = waitpid(pid, &status, options);
if (terminated_child == -1) {
perror("waitpid");
return 1;
}
if (terminated_child == 0) {
// 子进程还没有终止
printf("No child process has terminated yet. Sleeping for a while...\n");
sleep(1);
}
} while (terminated_child == 0);
if (WIFEXITED(status)) {
printf("Child process (PID=%d) exited with status %d.\n", terminated_child, WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("Child process (PID=%d) terminated by signal %d.\n", terminated_child, WTERMSIG(status));
}
printf("Parent process exits.\n");
} else {
// 子进程
printf("Child process (PID=%d) exits.\n", getpid());
exit(0);
}
return 0;
}