Linux系统编程与网络编程——进程控制,fork创建进程以及获取PID(七)

进程控制

在Linux系统中,用户创建一个进程的唯一方法就是使用系统调用fork()。内核为完成系统调用fork()要进行几步操作。

第一步:为新进程在进程表中分配一个表项task_struct。系统对一个用户可以同时运行的进程数是有限制的,对超级 用户没有该限制,但也不能超过进程表的最大表项的数目。

第二步:给子进程一个唯一的进程标识号(PID)。该进程标识号其实就是该表项在进程表中的索引号。

第三步:复制一个父进程的进程表项的副本给子进程。内核初始化子进程的进程表项时,是从父进程处拷贝的。所以子进程拥有父进程一样的uid、euid、gid,用于计算优先权的nice值、当前目录、当前根、用户文件描述符表等。

第四步:把与父进程相连的文件表和索引节点表的引用数加1,这些文件自动地与该子进程相连。


进程调度

CPU资源是有限的,那么在调度进程时,每个进程只允许运行很短的时间,当这个时间用完之后,系统将选择另一个进程来运行,原来的进程必须等待一段时间以继续运行,这段时间称为时间片

Linux使用基于优先级的简单调度算法来选择下一个运行进程。当选定新进程后,系统必须将当前进程的状态,处理器中的寄存器以及上下文状态保存到task_struct结构中。同时它将重新设置新进程的状态并将系统控制权交给此进程。为了将CPU时间合理的分配给系统中每个可执行进程,调度管理器必须将这些时间信息也保存在task_struct
中。


fork函数创建进程

一个现有进程可以调用fork函数创建一个新进程。包括代码、数据和分配给进程的资源全部都复制一份,除了进程标识pid不同,其他基本都跟父进程一样,由fork创建的新进程被称为子进程(child process)。fork函数被调用一次但返回两次。

fork函数原型:
在这里插入图片描述
1)在父进程中,fork返回新创建子进程的进程ID; 2)在子进程中,fork返回0; 3)创建子进程失败,fork返回-1;

源代码: fork_test.c

#include <stdio.h>
#include <unistd.h>

int main(int argc, char** argv)
{
	pid_t pid;
	printf("Current process PID = [%d]\n", getpid());
	pid = fork();
	if(pid == 0)
	{
		printf("child process PID = [%d], parent PID = [%d]\n", getpid(), getppid());
	}
	else if(pid != -1)
	{
		printf("parent process, PID = [%d]\n", getpid());
	}
	else
	{
		perror("fork");
	}
	return 0;
}

程序非常简单,调用了fork()函数并把返回值赋值给pid变量。分别在pid等于0、pid不等于-1的时候输出当前进程的PID。

编译运行这个程序,结果如下:
在这里插入图片描述
可见,当pid等于0的时候,表明是当前进程派生出来的子进程。如果返回的pid不等于-1,表明派生操作成功并且返回值就是新的子进程PID。如果返回值等于-1,那么操作失败。

在代码pid=fork()之前,只有一个进程在执行这段代码,但在这条语句之后,就变成两个进程在执行了,这两个进程的代码部分完全相同

源码:fork_test2.c

#include <stdio.h>
#include <unistd.h>

int main(int argc, char** argv)
{
	pid_t pid;
	int i;
	printf("Current process PID = [%d]\n", getpid());
	pid = fork();
	if(pid == 0)
	{
		for(i = 0; i < 10; i++)
		{
			printf("child process, PID=[%d], my parent PID=[%d]\n", getpid(), getppid());
			sleep(1);
		}
	}
	else if(pid != -1)
	{
		for(i = 0; i < 10; i++)
		{
			printf("parent process, PID=[%d], child PID=[%d]\n", getppid(), pid);
			sleep(1);
		}
	}
	else
	{
		printf("fork error\n");
	}
	return 0;
}

我们的父进程和子进程函数中都加入了一个for循环,循环输出10次,并在每次输出之后睡眠1秒。下面是程序的执行结果:
在这里插入图片描述
从输出的结果,可以看到,子进程的输出和父进程的输出是交替输出的,而不是由某个一个进程执行完10次循环之后,才能执行另外一个进程,当进程进入睡眠模式的时候,内核会调度新的进程进入运行模式,这样使得两个进程看上去像是同步执行。

调用fork的时候直接从父进程 复制一份PCB到子进程,除了PID不同以外,其他的都一样,包括文件描述符表中的文件指针也指向同一个地址。就相当于是调用了==dup==复制了文件描述符。于是父子进程其实是共享同一个文件读写偏移量。

测试例子如下:

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
	int fd,nr;
	char buf[20];
	pid_t pid;
	fd = open("log",O_RDONLY);
	pid = fork();
	if(pid == 0)
	{
		nr = read(fd,buf,10);
		buf[nr]='\0';
		printf("pid %d buf %s\n",getpid(),buf);
		exit(0);
	}
	
	nr = read(fd,buf,10);
	buf[nr]='\0';
	printf("pid %d buf %s\n",getpid(),buf);
	return 0;
}

在log文件中写入helloworld1234567890,运行程序后输出:
在这里插入图片描述
足够说明fork的时候,复制出来的文件描述符是共享文件偏移量的。


获得进程有关的ID

用户标识号UID(user ID):用于标识正在运行进程的用户。
用户组标识号GID(group ID): 用于标识正在运行进程的用户所属的组ID。
进程标识号PID(process ID):用于标识进程。

在这里插入图片描述

#include <stdio.h>
#include <unistd.h>

int main(int argc, char** argv)
{
	printf("Current process's UID = [%d]\n", getuid());
	printf("Current process's GID = [%d]\n", getgid());
	printf("Current process's PID = [%d]\n", getpid());
	printf("Current process's PPID = [%d]\n", getppid());
	return 0;
}

注意:
1、实际用户ID和实际用户组ID:标识我是谁。也就是登录用户的uid和gid,比如我的Linux以where登录,在Linux运行的所有的命令的实际用户ID都是where的uid,实际用户组ID都是where的gid(可以用id命令查看)。

2、有效用户ID和有效用户组ID:进程用来决定我们对资源的访问权限。一般情况下,有效用户ID等于实际用户ID,有效用户组ID等于实际用户组ID。当设置SUID位设置,则有效用户ID等于文件的所有者的uid,而不是实际用户ID;同样,如果设置了SGID位,则有效用户组ID等于文件所有者的gid,而不是实际用户组ID。

猜你喜欢

转载自blog.csdn.net/Gsunshine24/article/details/88959394