第二节课的内容主要涉及到与进程相关的知识,是重点也是难点,当然学懂了还是会觉得很有趣的,比如以前我听说过的僵尸进程攻击,其形成的原理也是基于进程方面的知识。当然在学习的过程中还是有很多的迷惑之处,所以我的总结如果有什么问题希望能指出,还请见谅。
上一节课的收尾
由于第一节课时间有限,有些文件操作的函数并没有讲完,所以我简单复习一下文件操作留下的尾巴。
0x01 stat
stat()存在于<sys/stat.h>中,其功能是用于获取文件的属性,也就是能获取到文件的文件名、文件大小、文件类型等等。形式如下,
int stat(const char *path, struct stat *buf);
参数path为文件的路径(同目录下为文件名);参数buf即接受文件所拥有的属性。
0x02 access()
access()存在于<unistd.h>中,其功能是用于测试文件的某些权限是否存在。形式如下,
int access(const char *pathname, int mode);//成功返回0,失败返回-1
参数pathname为文件名;
参数mode取值有4个分别为:R_OK, W_OK, X_OK, F_OK;前3个是测试文件是否具有读、写、执行权限,最后一个测试文件是否存在。
0x03 chmod()
chmod()函数存在于<sys/stat.h>中,其功能是修改文件的访问权限与Linux中chmod指令功能大同小异。形式如下,
int chmod(const char *path, mode_t mode);
参数path为路径名;参数mode为需要修改的权限,权限值有以下几种,
S_ISUID | 04000 | 文件的 (set user-id on execution)位 |
S_ISGID | 02000 | 文件的 (set group-id on execution)位 |
S_ISVTX | 01000 | 文件的sticky 位 |
S_IRUSR (S_IREAD) | 00400 | 文件所有者具可读取权限 |
S_IWUSR (S_IWRITE) | 00200 | 文件所有者具可写入权限 |
S_IXUSR (S_IEXEC) | 00100 | 文件所有者具可执行权限 |
S_IRGRP | 00040 | 用户组具可读取权限 |
S_IWGRP | 00020 | 用户组具可写入权限 |
S_IXGRP | 00010 | 用户组具可执行权限 |
S_IROTH | 00004 | 其他用户具可读取权限 |
S_IWOTH | 00002 | 其他用户具可写入权限 |
S_IXOTH | 00001 | 其他用户具可执行权限 |
这些mode参数可以组合。另外提一点,想要使用这个函数,用户得是文件持有者或具有一定的权限。
0x04 truncate()
truncate()存在于<sys/stat.h>中,其功能是用于修改文件的大小,如果修改后文件大小比起始小了,文件的部分数据会被删除(也不知道这个函数在什么地方能用得上)。形式如下,
int truncate(const char *path, off_t length);
path为路径名;length为需要修改文件的大小值,length大小的单位根据我的测试应该是以字节为单位的。
进程的创建
这堂课主要讲的是父子进程的关系以及如何创建一个子进程?如何实现一个父进程创建多个子进程?父进程创建多个子进程时会遇到什么问题,该怎么解决?什么是孤儿进程,孤儿进程不处理会出现什么情况?大概就是这些问题,我们顺着这几个问题讲下去。
首先我们讲讲如何创建一个子进程,在这里有个创建子进程的函数fork(),
fork()
功能:创建进程;函数执行后,系统会创建一个与原进程几乎相同的进程,之后父子进程都继续执行。形式如下,
pid_t fork(void);
这个fork()直接使用,如果创建成功子进程,则返回子进程的pid,而创建的子进程中的fork()返回为0,如果创建失败则返回-1。可能有点绕,但理解了就不会有问题了。
单个子进程的创建
看一个子进程创建的案例:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(){
pid_t pid;
pid = fork();
if(pid == -1){//创建失败
perror("fork error");
exit(1);
}
else if(pid > 0){//parent
printf("parent process,pid=%d,ppid=%d\n",getpid(),getppid());
}
else if(pid == 0){//child
printf("child process,pid=%d,ppid=%d\n",getpid(),getppid());
}//of if
printf(">>>>>>>>>>>finish<<<<<<<<<<<<<<\n\n");
return 0;
}
pid是目前进程的进程ID,ppid是父进程的进程ID,所以child的ppid的值与parent的pid值相同。
但是我们如果多运行几次这玩意儿会发现一个问题,
为什么这个ppid等于1,前面不是说等于父进程的pid吗?为什么这里不等于133254??
这里的原因我就直接说了,是因为创建的子进程与父进程其实优先级是相同的,他们会竞争CPU的优先使用权,CPU会随机挑选他们当中的一个进入使用,当父进程被优先挑选到后就会优先执行父进程但也会优先终止。而进程终止后的回收机制是由其进程的父进程回收的,所以这里父进程终止后其实就是被它的父进程回收了,但是这个子进程却没有父进程了,那也就是会出现子进程结束了但是却没有父进程来回收,这也就是所谓的孤儿进程。
那么如果没有回收机制回收孤儿进程,而放任它一直存在不仅没什么用还会占用进程ID号,如果只是一两个到也没有什么影响,但是如果这些孤儿进程多了把进程ID号都占满了就会出现我们想要运行一个程序但是没有进程号分配,则程序无法执行,导致电脑瘫痪,而且这种情况储存和内存都不会出现异常,这也就是僵尸进程的原理以及危害。
而解决孤儿进程回收的问题引入了一个优先级更高的进程 init (pid=1),如果程序的父进程结束被回收了,就会使该孤儿进程指向父进程 init 因此才会出现上面测试的结果,想解决父进程与子进程抢占CPU的情况,我个人认为是可以在父进程那边加一个sleep()让父进程阻塞几秒,后面抢占优先于子进程的概率就会大大减小。
多个子进程的创建
上面提到的只是单个子进程的创建,那么如果我们想要创建多个子进程又该怎么做呢?一般我们写程序多次输入数据、多次打印数据都是利用循环,所以这里我们也是利用循环来做,案例如下,
int i;
for(i = 0;i < 3 ; i++){
pid=fork();
}
以上为反面教材,切勿以此创建多个子进程,因为用以上代码会出现一个问题,fork()创建了一个子进程以后在子程序中还会进行循环,那么在第二次循环中子进程也会创建进程,那么就乱套了(有兴趣可以自己测试测试),我们的本意是想要父进程创建多个子进程,仅此而已。而现在的情况是父进程创建多个子进程,这些子进程也在创建属于他们自己的子进程。
那么如何解决这种窘境呢?我们前面提到过,子进程的fork()值为0,我们利用这一点在循环中作一个条件判断,如果是子进程则终止循环即不会出现子进程再创建子进程了。优化后的代码如下,
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
pid_t tempPid;
int i;
for(i = 0; i < 5; i ++){
if((tempPid = fork()) == 0){
break;
}//of if
}//of for i
if(tempPid == -1){
perror("fork error");
}else if(tempPid > 0){//parent
printf("parent process, pid = %d, ppid = %d\n", getpid(), getppid());
}else{//child
printf("I am child process = %d, pid = %d, ppid = %d\n", i + 1, getpid(), getppid());
}//of if
printf(">>>>>>>>>>>>>>> finish <<<<<<<<<<<<<<<<\n");
return 0;
}//of main
用了条件判断可以保证我们在创建子进程的时候不会出现子进程再创建子进程的情况。
可以看到子进程不会根据我们正常的想法来按照1、2、3、4、5的顺序来递增创建,它随机创建,甚至还会发现有些子进程由于与父进程竞争CPU使用权失败后变成了孤儿进程,所以我们这里需要解决两个问题,一个是如何让子进程有顺序的创建,还有一个问题是如何避免父进程优先结束留下几个孤儿进程。上面提到过我们可以利用sleep()函数阻塞,按顺序创建即越后创建的阻塞时间越久。
解决案例如下,
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
pid_t tempPid;
int i;
for(i = 0; i < 5; i ++){
if((tempPid = fork()) == 0){
break;
}//of if
}//of for i
if(tempPid == -1){
perror("fork error");
}else if(tempPid > 0){//parent
sleep(5);
printf("parent process, pid = %d, ppid = %d\n", getpid(), getppid());
}else{//child
sleep(i);
printf("I am child process = %d, pid = %d, ppid = %d\n", i + 1, getpid(), getppid());
}//of if
printf(">>>>>>>>>>> finish <<<<<<<<<<<<");
return 0;
}//of main
这里还需要注意一点,就是父进程的阻塞时间设置的要比最后一个子进程创建的阻塞时间还要长,只能长不能短,否则后面阻塞的子进程创建后就为孤儿进程了。
总结&问题
不知道总结什么了,我自己的总结都在上面的内容里了。
要强行说在总结中说的话,就是在本次课程中我对子进程和父进程的关系有了更深的理解,也明白了孤儿进程是什么,孤儿进程的处理,以及父进程创建子进程遇到的问题的处理方法,如何防止创建的子进程变成孤儿进程。
这里给自己留了一个问题,希望以后能解决,在上课的机房中我测试过创建多个子进程会出先我的老师上课提到过的一个问题,如下
我们会发现,bash命令抢在子进程创建的时候弹出来了,后续才把几个子进程创建,这个问题上课还提到了,原因是bash命令进程也与我们的子进程和父进程优先级相同,也是去竞争CPU的优先使用权,所以如果bash优先抢占成功就会出现以上情况,但是我在我自己的kali机中测试发现bash命令优先级好像是要低于父进程的说(不清楚,不知道是不是测试的次数少了),所以留下这个问题,以后如果能搞懂再把这片空白填充。