一,进程创建
创建进程有两种方式:1)操作系统创建 2)⽗进程创建。
进程的创建可以通过system函数、exec系列函数(execl、execlp等)、fork函数实现。
1)system函数
函数原型为:int system(const char*filename)
使⽤时需要包含头⽂件: stdlib.h
主要用于执行shell命令。
2)复制进程fork函数
函数返回值:函数返回类型 pid_t 实质是 int 类型。
在⽗进程中返回⼦进程的 pid,在⼦进程中返回 0,失败返回-1。

如图所示:pid = fork();子进程被创建,和它的父进程一样,继续执行剩下的程序。
1.父进程的数据空间,堆栈空间都会复制给子进程,而不是共享这些内存
2.子进程会继承父进程的一些属性,比如共享的存储段,当前工作目录,上下文环境等;子进程也有不同父进程的一些属性:没有继承比如进程pid;fork的返回值不同。
3)孤儿进程
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
pid_t pid=fork();
if(pid==0)
{
while(1){
printf("⼦进程的id=%d⽗进程id=%d\n",getpid(),getppid());
sleep(3);} //延时⽬的是为了产⽣孤⼉进程
}
else if(pid>0)
{
printf("⽗进程=id=%d\n",getpid());
exit(0);
}
else
{
printf("创建进程错误!");
}
printf("end=%d\n",getpid());
return 0;
}
4)进程等待
等待进程结束的函数主要有:
1,pid_t wait(int *ststus);
功能:wait函数会在⽗进程中阻塞,等待⼦进程结束,如果⼦进程结束,则返回子进程的PID。如果没有子进程则⽴刻返回-1。
注意:如果要获取wait的状态码,需要⽤该宏包裹WEXITSTATUS(status)才可以拿到。
2,pid_t waitpid(pid_t pid, int *status, int options);
功能:waitpid函数等待⼦进程结束(options设置为WNOHANG时为⾮阻塞,设置为0 表示阻塞),如果⼦进程结束,则返回⼦进程的PID。如果没有⼦进程则⽴刻返 回-1,如果是⾮阻塞的并且⼦进程还没有结束,则返回0。
两者的区别:
wait函数作⽤就是⽗进程调⽤,将⽗进程挂起,直到所等待的进程结束,然后在执 ⾏⽗进程。⽽如果有多个⼦进程的话,只要第⼀个⼦进程结束,wait就会接受到信 号,并结束等待。⽽waitpid就会指定⼀个进程号,⽗进程将会等待pid这个⼦进程 结束才会执⾏。
5)僵尸进程及处理办法
(1)僵死进程概念:
⼦进程先于⽗进程结束,⽗进程没有调⽤ wait 获取⼦进程退出码。
(2) 如何处理僵死进程:⽗进程通过调⽤ wait()完成。
虽然⼦进程的主体结束,但是进程的PCB依旧存在 ,如果进程不调⽤wait / waitpid, 那么保留的那段信息就不会释放,其进程号就会⼀直被占⽤,但是系统 所能使⽤的进程号是有限的,如果⼤量的产⽣僵死进程,将因为没有可⽤的进程号 ⽽导致系统不能产⽣新的进程. 此即为僵⼫进程的危害,应当避免。
6)进程退出
(1)正常结束进程
(2)进程的异常结束
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
pid_t pid = 0;
pid = fork();
int status;
if(pid == 0 )
{
absort();
}
if(pid > 0 )
{
wait(&status);
printf("CODE= %d \n",WEXITSTATUS(status) );
}
return 0;
}
7)vfork函数
创建⼀个新进程的⽅法只能由某个已存在的进程调⽤fork()或vfork()进⾏创建,两者的主要区别是:
1)fork: ⼦进程拷⻉⽗进程的数据段和堆栈段,⽽vfork:⽗⼦进程共享数据段
2)vfork:保证⼦进程先运⾏,在它调⽤exec或exit之后⽗进程才能调度运⾏。
vfork函数⽣成的子进程与父进程共享数据。并优先执行。
8)多进程下对⽂件描述符的影响
9)exec函数(夺舍函数)


使用演示:
创建被调用的程序 (add.c):
#include <stdio.h>
#include <stdlib.h> // 为了使用 atoi 函数
#include <unistd.h>
int main(int argc, const char *argv[]) {
// argc 是命令行参数的数量
// argv 是一个指向参数字符串的指针数组
// 检查传入的参数数量是否正确(程序名 + 两个数字)
if (argc < 3) {
printf("用法: %s <数字1> <数字2>\n", argv[0]);
return 1; // 参数不足,返回错误
}
// 使用 atoi 将字符串参数转换为整数
int num1 = atoi(argv[1]);
int num2 = atoi(argv[2]);
int sum = num1 + num2;
// 打印传递进来的参数和计算结果
printf("我是 'add' 程序,我收到的参数是 %s 和 %s\n", argv[1], argv[2]);
printf("计算结果: %d + %d = %d\n", num1, num2, sum);
return 0;
}
创建调用程序 (main.c):
#include <stdio.h>
#include <unistd.h> // 为了使用 fork 和 execl
#include <sys/wait.h> // 为了使用 wait
#include <stdlib.h> // 为了使用 perror
int main() {
pid_t pid;
printf("主程序(main)开始执行...\n");
// 创建一个子进程
pid = fork();
// fork 调用失败
if (pid < 0) {
perror("fork 失败");
return 1;
}
// 这是子进程执行的代码块
else if (pid == 0) {
printf("这里是子进程 (PID: %d),我将要变身去执行 'add' 程序...\n", getpid());
// 使用 execl 来执行新的程序
// 参数说明:
// 1. "/path/to/add": 要执行的程序路径 (这里假设在同一目录,用 "./add")
// 2. "add_program": 传给新程序的 argv[0] (通常是程序名)
// 3. "25": 传给新程序的 argv[1]
// 4. "8": 传给新程序的 argv[2]
// 5. NULL: 参数列表结束的标志
execl("./add", "add_program", "25", "8", NULL);
// 如果 execl 调用成功,它永远不会返回。
// 所以,如果下面的代码被执行了,就说明 execl 调用出错了。
perror("execl 失败");
exit(1); // 必须退出,否则会继续执行父进程的代码
}
// 这是父进程执行的代码块
else {
printf("这里是父进程 (PID: %d),我正在等待子进程执行完毕...\n", getpid());
// 等待子进程结束
wait(NULL);
printf("父进程:子进程已经执行完毕,主程序(main)结束。\n");
}
// 注意:子进程成功执行 execl 后,它自己的代码(包括下面的 return)就被替换掉了,
// 所以子进程永远不会执行到这里。
return 0;
}
编译并运行这两个文件,先编译add.c:gcc add.c -o add 编译main.c:gcc main.c -o main
运行主程序:./main
会看到类似输出效果:

进程创建与管理详解
1396

被折叠的 条评论
为什么被折叠?



