获取进程相关的ID
头文件
#include <sys/types.h>
#include <unistd.h>
函数原型:
pid_t getpid(void);
pid_t getppid(void);
uid_t getuid(void);
gid_t getgid(void);
- 函数的返回值都是就是上面所获得到的id
功能
getpid函数:获取调用该函数的进程ID
getppid函数:获取调用该函数进程的父进程ID
getuid函数:获取调用该函数的用户ID
getgid函数:获取调用该函数的组ID
- 函数不会调用失败,永远都是成功的不会调用失败。
getpid函数
获取进程PID
代码:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t pid = 0;
pid = getpid();
printf("%d\n",pid);
while(1);
return 0;
}
运行结果为:
我们打开新终端进行进程查看:
命令:ps -aux
- 我们可以查看到a.out程序运行起来之后生成的pid为4179的进程。
- 进程关闭之后,刚才PID为4179的进程将不存在。
getppid函数
代码:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t pid = 0;
pid_t ppid = 0;
pid = getpid();
ppid = getppid();
printf("%d\n",pid);
printf("%d\n",ppid);
while(1);
return 0;
}
运行结果为:
我们可以看到当前进程的id是4683
当前进程父进程的ID是3702
我们在进程l列表里面进行查看:
我们可以看到当前进程的PID和当前进程父进程的PID
因为我们在bash终端下面运行的a.out程序
所以a.out进程的父进程就是bash终端
- 在那个窗口下面运行程序,进程的父进程就是运行所在的窗口。
getuid和getgid
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t pid = 0;
pid_t ppid = 0;
uid_t uid = 0;
gid_t gid = 0;
pid = getpid();
ppid = getppid();
uid = getuid();
gid = getgid();
printf("%d\n",pid);
printf("%d\n",ppid);
printf("%d\n",uid);
printf("%d\n",gid);
while(1);
return 0;
}
运行结果为:
用户id和组id都是1000表示当前进程在ID为1000的用户下面运行,
并且组内只有一个人,所以gid也是1000.
fork函数
程序是如何运行起来的:
- 1.在内存中划出一片内存空间。
- 2.将硬盘上的可执行文件中的代码(机器指令)拷贝到划出来的内存空间
- 3.pc指向第一条指令,CPU取指令运行
有OS以上过程是通过API来实现
Linux平台提供两个非常关键的API
-
exec :
将程序代码(机器指令)拷贝到开辟的内存空间
让pc指向第一条指令,进程就开始运行
运行的进程和其他进程切换并发运行。 -
fork :
开辟出一块内存空间
函数原型
#include <unistd.h>
pid_t fork(void);
函数功能:
从调用函数的进程中复制出子进程
被复制的进程则成为父进程
复制出来的进程成为子进程
复制之后的结果:
1.依照父进程的内存空间样子,原样复制地开辟出子进程的内存空间
2.子进程原样复制父进程的空间,因此子进程内存空间中的代码和数据和父进程完全相同
函数无参数
返回值
1,父进程的fork 成功返回子进程的PID 失败返回-1 errno被设置
2,子进程的fork 成功返回0 失败返回-1 errno被设置
代码演示:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t ret = 0;
ret = fork();
printf("%d,ret = %d\n",getpid(),ret);
return 0;
}
运行结果为:
上面的结果有点奇怪,是因为父进程先结束了。
上面的结果是由于父进程在调用fork之后产生产生子进程也会调用fork
上面结果中:
5122是父进程进程的PID
5123是父进程调用fork产生的子进程返回的子进程PID
5123是在自己子进程里面运行的getpid得到的PID
0是子进程调用fork函数返回值。
我们给代码加上循环不让其退出来查看两个进程:
上面打印的结果都比较正常,
因为父进程和子进程都进入到了while循环没有退出。
我们可以看到两个PID都是a.out他们都是从a.out演变而来的。
如何让父子进程做不同的事情?
我们就可以根据fork函数的返回值来进行区分:
父进程fork返回子进程PID 子进程fork返回值为0
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t ret = 0;
ret = fork();
if(ret > 0)
{
printf("this is parent PID = %d\n",getpid());
printf("parent ret = %d\n",ret);
}
else if(ret == 0)
{
printf("this is child PID = %d\n",getpid());
printf("child ret = %d\n",ret);
}
printf("hello world\n");
while(1);
return 0;
}
运行结果为:
进程复制的原理
Linux有虚拟内存机制,父进程运行在虚拟内存上,虚拟内存是OS通过数据结构基于物理内存模拟出来的,因此底层对应的还是物理内存。复制进程的时候,会复制父进程的虚拟内存数据结构,就得到了子进程的虚拟内存,相应的底层就会对应的一段新的物理内存空间,里面放了与父进程一模一样的代码和数据。我们的目的是得到子进程的物理内存空间,然后调用exec去加载新程序的代码,去执行新程序的代码。在没有使用exec加载之前父子进程拥有相同的代码。
父子进程各自会执行那些代码?
图示:
注意:子进程调用fork函数返回的0并不是PID
验证子进程复制了父进程的代码和数据
我们对于代码进行修改:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t ret = 0;
printf("before fork\n");
ret = fork();
if(ret > 0)
{
printf("this is parent PID = %d\n",getpid());
printf("parent ret = %d\n",ret);
}
else if(ret == 0)
{
printf("this is child PID = %d\n",getpid());
printf("child ret = %d\n",ret);
}
printf("after fork\n");
while(1);
return 0;
}
执行结果为:
子进程并没有输出before fork 但是输出了after fork 说明子进程复制了父进程的代码但是子进程是从fork位置开始执行,fork前面的代码不执行。
我们在对于代码进行修改:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t ret = 0;
printf("before fork\n");
ret = fork();
if(ret > 0)
{
printf("this is parent PID = %d\n",getpid());
printf("parent ret = %d\n",ret);
}
else if(ret == 0)
{
printf("this is child PID = %d\n",getpid());
printf("child ret = %d\n",ret);
}
printf("after fork\n");
while(1);
return 0;
}
上面代码我们只是去掉before fork后面的\n
运行结果为:
我们可以看到父进程和子进程都打印了before fork ,
我们前面说过,子进程从fork开始运行。
解释说明:
当父进程执行到before fork 的时候后面没有\n就会被保存到printf缓冲区,然后父进程执行fork,就会复制父进程的代码和数据,就会把父进程printf缓冲区的beforefork进行复制。
过程如下:
子进程在执行
printf("this is child PID = %d\n",getpid());
的时候刷新刷新缓冲区,
那么子进程就打印出来了before fork
加\n
在fork运行之前,printf(“before fork\n”); 里面有\n 会刷新printf函数缓冲区,缓冲区不会有数据,所以在父进程执行fork函数之后,子进程也就不会从从父进程的printf函数缓冲区复制到数据。