进程控制编程——创建进程1

本文深入解析进程的概念,探讨进程与程序的区别,详细阐述进程的生命周期、状态转换、地址空间及进程ID。此外,还介绍了进程间通信、互斥、同步机制,以及进程调度算法,如先来先服务、短进程优先等。通过实例代码,演示如何在Linux环境下使用fork、vfork、exec等系统调用,实现进程创建、替换和等待。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  1. 进程: 进程是一个具有一定独立功能的程序的一次运行活动,同时也是资源分配的最小单元。
    (ctrl +z——》切到后台)
    (fg ——》切到前台)
    (ps - elf ——>查看运行的所有进程)

程序是放到磁盘的可执行文件
进程是指程序执行的实例

  1. 程序与进程的关系
    (1)进程是动态的,程序是静态的:程序是有序代码的集合;进程是程序的执行。通常进程不可在计算机之间迁移;而程序通常对应着文件、静态和可以复制
    (2)进程是暂时的,程序使长久的:进程是一个状态变化的过程,程序可长久保存
    (3)进程与程序组成不同:进程的组成包括程序、数据和进程控制块(即进程状态信息)
    (4)进程与程序的对应关系:通过多次执行,一个程序可对应多个进程;通过调用关系,一个进程可包括多个程序。

  2. 进程的生命周期
    (1)创建: 每个进程都是由其父进程创建,进程可以创建子进程,子进程又可以创建子进程的子进程
    (2)运行: 多个进程可以同时存在,进程间可以通信
    (3)撤销: 进程可以被撤销,从而结束一个进程的运行

针对运行——》运行分为三个状态:
(1)执行状态:进程正在占用CPU——》例如正在运行的5ms
(2)就绪状态:进程已具备一切条件,正在等待分配CPU的处理时间片——》例如正在执行qq, 那么qq音乐就在就绪状态。
(3)等待状态:进程不能使用CPU,若等待事件发生则可将其唤醒——》scanf

执行状态——就绪状态 时间片
相互切换

  1. Linux下的进程地址空间
    Linux中的进程包含3个段,分别为“数据段”、“代码段”和“堆栈段”。
    (1)“数据段”存放的是全局变量、常数以及动态数据分配的数据空间;
    (2)“代码段”存放的是程序代码的数据。
    (3)“堆栈段”存放的是子程序的返回地址、子程序的参数以及程序的局部变量等。

  2. 进程ID
    进程ID(PID):标识进程的唯一数字
    父进程的ID(PPID)
    启动进程的用户ID(UID)

  3. 进程互斥
    进程互斥是指当有若干进程都要使用某一共享资源时,任何时刻最多允许一个进程使用,其他要使用该资源的进程必须等待,直到占用该资源者释放了该资源为止

8.临界资源
操作系统中将一次只允许一个进程访问的资源称为临界资源

9.临界区
进程中访问临界资源的那段程序代码称为临界区,为实现对临界资源的互斥访问,应保证诸进程互斥地进入各自的临界区

10.进程同步
一组并发进程按一定的顺序执行的过程称为进程间的同步
具有同步关系一组并发进程称为合作进程,
合作进程间互相发送的信号称为消息或事件

  1. 进程调度
    概念:
    按一定算法,从一组待运行的进程中选出一个来占有CPU运行。
    调度方式:
    • 抢占式
    • 非抢占式

  2. 调度的算法
    (1)先来先服务调度算法
    (2)短进程优先调度算法
    (3)高优先级优先调度算法
    (4)时间片轮转法

第一:getpid(获取id号)(LINUX1——》lesson2——》getpid.c)
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
printf("%d\n", getpid());
printf("%d\n", getppid());

while (1);

return 0;

}

第二:fork(创建子进程)(LINUX1——》lesson2——》fork.c)
注意点: fork的奇妙之处在于它被调用一次,却返回两次,它可能有三种不同的返回值:
返回值:
0: 子进程
子进程ID(大于0):父进程
-1: 出错

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
pid_t pid;

pid = fork();
if (-1 == pid)   //错误处理
{
	perror("fork");
	exit(1);
}
else if (0 == pid)   //子进程
{
	printf("This is Child, pid is %d, Parent id is %d!\n", getpid(), getppid());
	printf("Child : pid = %d\n", pid);
}
else     //子进程和父进程运行顺序随机,子进程进程号比父进程进程号大一
{
	printf("This is Parent, pid is %d!\n", getpid());
	printf("Parent : pid : %d\n", pid);
}
printf("helloworld!\n");

return 0;

}

第三:count(验证父子进程)(LINUX1——》lesson2——》count.c)
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
pid_t pid;
int count = 0;

pid = fork();
if (-1 == pid)
{
	perror("fork");
}
else if (0 == pid)
{
	count++;
	printf("%d %p\n", count, &count);
}
else
{
	count++;
	printf("%d %p\n", count, &count);
}
return 0;

}

(1)运行完后, 父子进程中的count都是1

原因:子进程的数据空间、堆栈空间都会从父进程得到一个拷贝,而不是共享。(子进程复制父进程的地址空间)

v在子进程中对count进行加1的操作,并没有影响到父进程中的count值,父进程中的count值仍然为0

(2)写实拷贝: 只有在子进程写的时候才真正拷贝父进程的地址空间

(3)fork 之前打开的文件都是父进程的
fork ——》父进程打开一个文件, 子进程继承其文件描述符。

第四:vfork(LINUX1——》lesson2——》vfork.c)
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
pid_t pid; //pid_t 代表int 型, 所有的_t,差不都都是。
int count = 0;

pid = vfork();        //子进程共享父进程的数据(堆栈、数据段), 运行完后count的值分别为1,2  这个与fork有区别
if (-1 == pid)
{
	perror("vfork");
	exit(1);
}
else if (0 == pid)   //一定是子进程先运行,一定是子进程运行完毕,父进程再运行
{
	count++;
	printf("This is Child %d %d!\n", count, getpid());
	//sleep(3);
	exit(1);   //vfork子进程要指定退出方式
}
else
{
	count++;
	printf("This is Parent %d %d!\n", count, getpid());
}

return 0;

}

第五:exec.c(函数族)(LINUX1——》lesson2——》exec.c)

注意:exec用被执行的程序替换调用它的程序。
区别:
fork创建一个新的进程,产生一个新的PID。
exec启动一个新程序,替换原有的进程,因此进程的PID不会改变

代码一:(test.c)

#include <stdio.h>

int main(int argc, char *argv[])
{
int i;

printf("test pid %d\n", getpid());
for (i = 1; i < argc; i++)
{
	printf("%s\n", argv[i]);
}

return 0;

}

代码二:(exec.c)
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
pid_t pid;
pid = vfork();
if (-1 == pid)
{
perror(“vfork”);
exit(1);
}
else if (0 == pid) //子进程
{
printf(“Child pid %d\n”, getpid());
execl("/home/168/linux/process/test", “./test”, “aaaaa”, “bbbbb”, “cccc”, NULL); //完整路径,以空指针NULL结束
}
else
{
printf(“this is parent!\n”);
}

return 0
}

注意点:
(1)execl后,进程号相同,但是地址空间变了。
(2)调用execl 后, 子程序不用exit(1)退出了, execl 后面写的代码都没有用了,不执行了, 例如sleep(5)——》没有用。

第六:execv(数组形式)(LINUX1——》lesson2——》exec.c)

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
pid_t pid;
char *string[] = {"./test", “aaaaa”, “bbbbb”, “cccc”, NULL};

pid = vfork();
if (-1 == pid)
{
	perror("vfork");
	exit(1);
}
else if (0 == pid)   //子进程
{
	printf("Child pid %d\n", getpid());
	//execl("/home/168/linux/process/test", "./test", "aaaaa", "bbbbb", "cccc", NULL);
	execv("/home/168/linux/process/test", string);
}
else
{
	printf("this is parent!\n");
}

return 0;

}

与上一个代码执行的效果一样

第七:wait(进程等待)(LINUX1——》lesson2——》wait.c)

作用:阻塞该进程,直到其某个子进程退出。
(1)父进程不仅需要创建子进程,还要回收子进程
孤儿进程:(父进程死了, 子进程没有死)
僵尸进程 :(子进程运行完死了, 但是父进程没有回收他)

wait(参数)——》&status作用
(1)等待子程序结束 :父进程一定在子进程结束后才能死
(2)回收子程序
(3)用到fork 的时候, 一定要用wait

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
pid_t pid;

pid = fork();
if (-1 == pid)
{
	perror("fork");
	exit(1);
}
else if (0 == pid)
{
	sleep(3);
	printf("This is child!\n");
	exit(200);    //退出进程,正常退出
}
else
{
	int status;
	printf("This is parent!\n");
	//wait(&status);  //1、等待子进程结束   2、回收子进程
	waitpid(pid, &status, 0);    // 作用和上一行一样
	if (WIFEXITED(status))   //判断子进程是否正常退出 
	{
		printf("子进程正常退出 %d!\n", WEXITSTATUS(status));   //如果正常退出就获取子进程退出状态         不正常退出:比如sleep时间长一点,然后用kill杀死子程序, 就没有下面的printf了。
	}
}

return 0;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值