一、进程控制理论基础
1.进程的定义:进程是一个具有一定独立功能的程序的一次运行活动,同时也是资源分配的最小单元。
2.进程与程序
进程(程序执行的实例) | 程序(放到磁盘的可执行文件) |
动态 | 静态 |
暂时(状态变化的过程) | 长久(长久保存) |
组成:包括程序、数据和进程控制块(即进程状态信息) | |
通过多次执行,一个程序可对应多个进程; 通过调用关系,一个进程可包括多个程序。 |
3. 进程的生命周期
创建: 每个进程都是由其父进程创建,进程可以创建子进程,子进程又可以创建子进程的子进程
运行: 多个进程可以同时存在,进程间可以通信
撤销: 进程可以被撤销,从而结束一个进程的运行
(每个操作系统起来后都有1号进程 init)
4. 进程的状态
执行状态:进程正在占用CPU
就绪状态:进程已具备一切条件,正在等待分配CPU的处理时间片
等待状态:进程不能使用CPU,若等待事件发生则可将其唤醒
5.Linux进程:健壮性,并行性,互不干扰
6.Linux下进程地址空间:
“数据段”存放的是全局变量、常数以及动态数据分配的数据空间;
“代码段”存放的是程序代码的数据。
“堆栈段”存放的是子程序的返回地址、子程序的参数以及程序的局部变量等。
7.进程ID(PID):标识进程的唯一数字
父进程ID(PPID)
8.进程互斥:当有若干进程都要使用某一共享资源时,任何时刻最多允许一个进程使用
临界资源:一次只允许一个进程访问的资源
临界区:访问临界资源的那段代码
进程同步:一组并发进程按一定顺序执行的过程
进程调度:按一定算法,从一组待运行的进程中选出一个来占有CPU运行
9. 死锁:多个进程因资源竞争而形成一种僵局,若无外力作用,这些进程将永远不能向前推进
二、进程的控制编程
1.获取ID:
pid_t getpid(void) 获取本进程ID
pid_t getppid(void) 获取父进程ID
2. 进程的创建:
(1)pid_t fork(void) 创建子进程
父进程返回子进程ID,子进程返回0
子进程拷贝父进程的地址空间
(2)pid_t vfork(void) 创建子进程(与父进程共享地址空间)
子进程先运行,子进程运行完,父进程才运行
子进程指定退出方式 exit(1);
例:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid; //此时仅有一个进程
int a = 0;
pid = fork(); //创建子进程,此时有两个进程
if(-1 == pid) //失败返回-1
{
perror("fork");
exit(1);
}
else if(0 == pid) //子进程
{
a++;
printf("This is ChildProcess pid %d ppid %d!\n", getpid(), getppid());
printf("pid = %d\n", pid);
printf("a = %d\n", a);
printf("%p\n", a);
}
else //父进程
{
a++;
printf("This is ParentProcess %d!\n", getpid());
printf("pid = %d\n", pid);
printf("a = %d\n", a);
printf("%p\n", a);
}
printf("Hello!\n");
return 0;
}
运行结果:(1)父进程子进程的a均为1,a的地址相同,因为子进程复制父进程的地址空间,拷贝数据
(2)打印两次Hello!,因为父进程和子进程执行时都执行进程括号外面的代码
----------------------------------------------------
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
void ReadFile(int fd)
{
char buf[32] = {0};
lseek(fd, 0, SEEK_SET);
int ret = read(fd, buf, sizeof(buf));
if(-1 == ret)
{
perror("read");
exit(1);
}
printf("Child read from txt:%s\n", buf);
}
void WriteFile(int fd)
{
char buf[32] = "HelloWorld";
int ret = write(fd, buf, strlen(buf));
if(-1 == ret)
{
perror("write");
exit(1);
}
}
int main()
{
pid_t pid;
int fd = open("tmp.txt", O_RDWR | O_CREAT | O_EXCL, S_IRWXU);
if(-1 == pid) //失败返回-1
{
perror("fork");
exit(1);
}
pid = fork(); //创建子进程
if(0 == pid) //子进程
{
sleep(1);
ReadFile(fd);
}
else //父进程
{
WriteFile(fd);
sleep(2);
}
return 0;
}
子进程继承父进程打开的文件描述符
-------------------------------------------------------------
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int a = 0;
pid = vfork(); //创建子进程
if(-1 == pid) //失败返回-1
{
perror("vfork");
exit(1);
}
else if(0 == pid) //子进程
{
a++;
printf("This is ChildProcess pid %d ppid %d!\n", getpid(), getppid());
printf("pid = %d\n", pid);
printf("a = %d\n", a);
printf("%p\n", &a);
exit(1); //子进程指定退出方式
}
else //父进程
{
a++;
printf("This is ParentProcess %d!\n", getpid());
printf("pid = %d\n", pid);
printf("a = %d\n", a);
printf("%p\n", &a);
}
printf("Hello!\n");
return 0;
}
运行结果 :(1)父进程a = 1, 子进程a = 2,a的地址相同,因为子进程共享父进程的地址空间
(2)打印两次Hello!,因为父进程和子进程执行时都执行进程括号外面的代码
-----------------------------------------------------
fork 与 vfork 的区别:
fork | vfork |
子进程拷贝父进程的数据 | 子进程与父进程共享数据 |
父、子进程的执行次序不确定 | 子进程先运行,父进程后运行 |
3. exec函数族
exec启动一个新程序,替换原有的进程,因此进程的PID不会改变
(1)int execl(const char * path,const char * arg1, ...)
execl("进程的绝对路径","进程名", 命令行参数,NULL)
参数:path:被执行程序名(含完整路径)。
arg1 – argn: 被执行程序所需的命令行参数,含程序名。以空指针(NULL)结束。
(2)int execv (const char * path, char * const argv[ ])
参数:path:被执行程序名(含完整路径)。
argv[ ]: 被执行程序所需的命令行参数数组。
例:
被执行的程序:
#include <stdio.h>
int main(int argc, char *argv[])
{
int i;
printf("%d\n", getpid());
for(i = 1; i < argc; i++)
{
printf("%s\n", argv[i]);
}
return 0;
}
用 execl 执行 :
#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)
{
execl("/mnt/hgfs/share/class/Linux/process/5test", "./5test", "Hello",
"World", NULL);
}
else
{
printf("This is Parent Process %d\n", getpid());
}
return 0;
}
用 execv 执行:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
char *argv[] = {"./test", "1234","Hello!", NULL};
pid = vfork();
if(-1 == pid)
{
perror("vfork");
exit(1);
}
else if(0 == pid)
{
execv("/mnt/hgfs/share/class/Linux/process/5test", argv);
}
else
{
printf("This is Parent Process %d\n", getpid());
}
return 0;
}
4. 进程等待
(1)pid_t wait (int * status)
例:wait(&status);
功能:等待任意一个子进程结束,回收子进程资源
宏函数:WIFEXITED :判断子进程是否正常退出,正常退出返回非0
WEXITSTATUS :获取子进程的退出状态
(2)pid_t waitpid (pid_t pid, int * status, int options)
例:waitpid(pid, &status, 0);
功能:父进程指定等待某一子进程结束
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
pid = fork(); //创建子进程
if(-1 == pid)
{
perror("fork");
exit(1);
}
else if(0 == pid) //子进程
{
sleep(1);
printf("This is ChildProcess!\n");
exit(100);
}
else //父进程
{
int status;
printf("This is ParentProcess!\n");
wait(&status);//1.等待子进程结束(阻塞)2.回收子进程资源
if(WIFEXITED(status)) //判断子进程是否正常退出
{
printf("Child Exit Normally! %d\n", WEXITSTATUS(status));//获取子进程退出状态
}
}
return 0;
}
5. 进程退出 exit(1);
6.孤儿进程:父进程结束,此时子进程为孤儿进程,默认父进程为init
僵尸进程: 子进程结束,父进程没有回收其资源