一、Xmind整理:
父进程会拷贝文件描述符表给子进程:
二、课上练习:
练习1:①从终端获取一个文件的路径以及名字。②若该文件是目录文件,则将该文件下的所有文件的属性显示到终端,类似ls -l该文件夹③若该文件不是目录文件,则显示该文件的属性到终端上,类似ls -l这单个文件
include <stdio.h>
include <string.h>
include <stdlib.h>
include <head.h>
/获取文件类型
har get_fileType(mode_t m)
switch(m&S_IFMT)
{
case S_IFSOCK:return('s');break;
case S_IFLNK:return('l');break;
case S_IFREG:return('-');break;
case S_IFDIR:return('d');break;
case S_IFCHR:return('c');break;
case S_IFBLK:return('b');break;
case S_IFIFO:return('p');break;
}
/获取文件权限
oid get_filePermission(mode_t m)
for(int i=0;i<9;i++)
{
if((m&(0400>>i))==0)
{
putchar('-');
continue;
}
//能运行到当前位置,则代表对应位置有权限
//需要判断是r w x当中的哪一个
switch(i%3)
{
case 0:putchar('r');break;
case 1:putchar('w');break;
case 2:putchar('x');break;
}
}
return;
nt getstat(struct stat buf,char *str)
//文件的类型和权限
char type=get_fileType(buf.st_mode);
printf("%c",type);
get_fileType(buf.st_mode);
//文件的硬链接数
printf("%ld ", buf.st_nlink);
//文件的所属用户
//printf("uid: %d\n", buf.st_uid);
//将uid转换成名字
struct passwd* pwd = getpwuid(buf.st_uid);
if(NULL == pwd)
{
ERR_MSG("getpwuid");
return -1;
}
printf("%s ", pwd->pw_name);
//文件所属组用户
//printf("gid: %d\n", buf.st_gid);
//将gid转换成名字
struct group* grp = getgrgid(buf.st_gid);
if(NULL == grp)
{
ERR_MSG("getgrgid");
return -1;
}
printf("%s ", grp->gr_name);
//文件大小
printf("%ld ", buf.st_size);
//文件的修改时间
struct tm* info=NULL;
info=localtime(&buf.st_mtime);
//printf("%ld ",buf.st_ctime);
printf("%02d %02d %02d:%02d ",info->tm_mon+1,info->tm_mday,info->tm_hou
printf("%s\n",str);
nt main(int argc, const char *argv[])
char str[20]="";
char path[300]="";
char type=0;
struct stat buf;
DIR* dp=NULL;
struct dirent* rp=NULL;
//从终端获取一个文件的路径以及名字
printf("please enter a filename:");
scanf("%s",str);
getchar();
//判断文件是否是目录文件
if(stat(str,&buf) < 0)
{
ERR_MSG("stat");
return -1;
}
type=get_fileType(buf.st_mode);
if('d'==type)
{
//文件是一个目录文件,则需要打开目录
dp = opendir(str);
if(NULL == dp)
{
ERR_MSG("opendir");
return -1;
}
printf("open success \n");
while(1)
{
//循环读取目录
rp = readdir(dp);
if(NULL == rp)
{
if(0 == errno)
{
printf("读取完毕\n");
break;
}
else
{
ERR_MSG("readdir");
return -1;
}
}
//将读取到的文件名传入stat获取属性
sprintf(path,"%s%s",str,rp->d_name);
//printf("path:%s\n",path);
if(stat(path,&buf)<0)
{
ERR_MSG("stat");
return -1;
}
getstat(buf,rp->d_name);
// printf("[%d]%s\n",++i,rp->d_name);
}
closedir(dp);
}
else
getstat(buf,str);
return 0;
练习2:文件IO函数实现,拷贝文件。子进程先拷贝后半部分,父进程再拷贝前半部分。允许使用sleep函数
#include <stdio.h>
#include <head.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd_r = open("./1.png", O_RDONLY);
if(fd_r < 0)
{
ERR_MSG("open");
return -1;
}
int fd_w = open("copy.png", O_WRONLY|O_CREAT|O_TRUNC, 0644);
if(fd_w < 0)
{
ERR_MSG("open");
return -1;
}
//计算文件大小
off_t size = lseek(fd_r, 0, SEEK_END);
pid_t cpid = fork();
if(cpid > 0)
{
sleep(4);
//父进程拷贝前半部分
//将偏移量修改到0
lseek(fd_r, 0, SEEK_SET);
lseek(fd_w, 0, SEEK_SET);
char c = 0;
for(int i=0; i<size/2; i++)
{
read(fd_r, &c, 1);
write(fd_w, &c, 1);
}
printf("前半部分拷贝完毕\n");
}
else if(0 == cpid)
{
//子进程拷贝后半部分
//将偏移量修改到size/2
lseek(fd_r, size/2, SEEK_SET);
lseek(fd_w, size/2, SEEK_SET);
char c = 0;
for(int i=size/2; i<size; i++)
{
read(fd_r, &c, 1);
write(fd_w, &c, 1);
}
printf("后半部分拷贝完毕\n");
}
else
{
ERR_MSG("fork");
return -1;
}
close(fd_r);
close(fd_w);
return 0;
}
练习3:getpid / getppid
功能:获取进程号 、 获取父进程号
原型:
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
pid_t getppid(void);
返回值:
获取进程号 、 获取父进程号
练习4:_exit
功能:结束进程,销毁其在内存中的资源,且直接摧毁缓冲区,不会刷新缓冲区!!!
原型:
#include <unistd.h>
void _exit(int status);
参数:
int status:可以传递进程退出状态值给其父进程,父进程可以通过wait/ waitpid函数接收。可以传递任意整型;
练习5:exit
功能:结束进程,销毁其在内存中的资源,会刷新缓冲区!!!
原型:
#include <stdlib.h>
void exit(int status);
参数:
int status:可以传递进程退出状态值给其父进程,父进程可以通过wait/ waitpid函数接收。可以传递任意整型;
小练1:
小练2:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <head.h>
int main(int argc, const char *argv[])
{
pid_t cpid = fork();
if(cpid > 0) //父进程
{
printf("parent\n");
//阻塞函数,阻塞等待任意子进程退出
pid_t wpid = wait(NULL);
printf("wpid =%d\n",wpid);
while(1)
{
printf("this is parent: %d %d\n",getpid(),cpid);
sleep(1);
}
}
else if(0 == cpid)
{
int i = 0;
while(i < 3)
{
printf("this id child: %d %d\n",getppid(),getpid());
sleep(1);
i++;
}
printf("子进程准备退出\n");
//_exit(0); //退出进程,不会刷新缓冲区
exit(0); //退出进程,会刷新缓冲区
printf("子进程已经退出\n");
}
else
{
perror("fork");
return -1;
}
return 0;
}
练习6:wait
功能:1.阻塞函数,阻塞等待任意子进程退出
2.回收退出的子进程的资源,(回收僵尸进程)
3.接收子进程退出状态
原型:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
参数:
int *wstatus:接收子进程传递回来的退出状态值,若不想接收,则填NULL;
返回值:
>0, 成功返回退出的子进程的PID号;
=-1,函数运行失败,更新errno;
若没有子进程,则wait函数运行失败;
父进程只能回收子进程,无法回收孙子进程。
注:①若子进程退出,父进程没有回收子进程的资源,此时子进程会变成僵尸进程。
②没有子进程,则wait函数运行失败
练习7:子进程退出状态
wait(int* wstatus)中,int* wstatus指向的int类型参数,只有[8bit, 15bit]用于存储子进程传递的退出状态值。范围为[0, 255]。
所以子进程只能传递256种状态。
1)从wstatus中提取子进程退出状态值
int wstatus = -1;
pid_t wpid = wait(&wstatus);
printf("wpid = %d wstatus=%d\n", wpid, wstatus>>8);
WEXITSTATUS(wstatus): 提取wstauts中子进程传递的退出状态;--> ((status) & 0xff00) >> 8)
2)判断子进程是否正常退出
WIFEXITED(wstatus) 若正常退出,则返回真。
正常退出: exit _exit 主函数调用return退出。
小练:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <head.h>
int main(int argc, const char *argv[])
{
pid_t cpid = fork();
if(cpid > 0) //父进程
{
printf("parent\n");
//阻塞函数,阻塞等待任意子进程退出
int wstatus = -1;
pid_t wpid = wait(&wstatus);
printf("wpid = %d wstatus= %d\n",wpid,WEXITSTATUS(wstatus));
if(WIFEXITED(wstatus))
printf("子进程正常退出\n");
else
printf("子进程异常退出\n");
while(1)
{
printf("this is parent: %d %d\n",getpid(),cpid);
sleep(1);
}
}
else if(0 == cpid)
{
int i = 0;
while(i < 3)
{
printf("this is child: %d %d\n",getppid(),getpid());
sleep(1);
// i++;
}
printf("子进程准备退出\n");
//_exit(0); //退出进程,不会刷新缓冲区
exit(0); //退出进程,会刷新缓冲区
printf("子进程已经退出\n");
}
else
{
perror("fork");
return -1;
}
return 0;
}
练习8:waitpid
功能:阻塞等待指定子进程退出
原型:
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options);
参数:
pid_t pid:
< -1 阻塞等待指定进程组下的任意一个子进程退出;
-1 阻塞等待当前进程下的任意一个子进程退出; 与wait函数的功能基本一致;
0 阻塞等待当前进程组下的任意一个子进程退出;
> 0 阻塞等待指定的子进程退出;子进程的pid号 == pid参数;
int *wstatus:接收子进程传递回来的退出状态值,若不想接收,则填NULL;
int options:
0:阻塞方式运行,当指定的子进程没有退出的时候,该函数阻塞,直到指定子进程退出,解除阻塞;
WNOHANG:非阻塞方式运行,当指定的子进程没有退出,该函数不阻塞,立即返回;
返回值:
成功,>0, 成功回收到的子进程的pid号;
=0, 函数运行成功,但是此时子进程没有退出。
函数运行失败,返回-1,更新errno;
1. 若指定的子进程不存在(没有子进程)的时候,函数运行失败;
2. 子进程无法回收父进程的资源,
3. 同级之间无法相互回收资源。
小练:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <head.h>
int main(int argc, const char *argv[])
{
pid_t cpid = fork();
if(cpid > 0) //父进程
{
printf("parent\n");
//阻塞函数,阻塞等待任意子进程退出,并回收子进程的资源
//pid_t wpid = waitpid(-1,NULL,0);
//非阻塞方式运行,若运行到waitpid的时候,子进程没有退出,则返回0
//若运行到waitpid的时候,子进程已经退出了,则收回尸体,并返回子进程的pid号
sleep(4);
pid_t wpid = waitpid(-1,NULL,WNOHANG);
printf("wpid = %d\n",wpid);
while(1)
{
printf("this is parent: %d %d\n",getpid(),cpid);
sleep(1);
}
}
else if(0 == cpid)
{
int i = 0;
while(i < 3)
{
printf("this is child: %d %d\n",getppid(),getpid());
sleep(1);
i++;
}
printf("子进程准备退出\n");
//_exit(0); //退出进程,不会刷新缓冲区
exit(0); //退出进程,会刷新缓冲区
printf("子进程已经退出\n");
}
else
{
perror("fork");
return -1;
}
return 0;
}
练习9:孤儿进程
父进程退出,子进程不退出,此时子进程被1号(init)进程收养,变成孤儿进程。
孤儿进程会脱离终端控制,且运行在后端,不能用ctrl+c杀死后端进程,但是可以被kill -9杀死。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <head.h>
int main(int argc, const char *argv[])
{
//父进程退出,子进程不退出
pid_t cpid = fork();
if(cpid > 0) //父进程
{
}
else if(0 == cpid)
{
while(1)
{
printf("this is child: %d %d\n",getppid(),getpid());
sleep(1);
}
}
else
{
perror("fork");
return -1;
}
return 0;
}
练习10:僵尸进程
子进程退出,父进程不退出去,且父进程没有给子进程收尸,此时子进程就变成僵尸进程。
注意:
- 僵尸进程只能被回收,不能被杀死。
- 僵尸进程有危害:占用进程号,占用部分内存空间,占用物理空间,占用进程调度块(PCB)等等...
- 回收僵尸进程的方式:
- 结合信号的方式回收僵尸进程:当子进程退出后,通知父进程收尸。
- wait / waitpid函数回收。缺点:阻塞函数,父进程无法做自己的事情。非阻塞形式,有可能收不到。
- 退出父进程后,子进程的资源由内核自动回收。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <head.h>
int main(int argc, const char *argv[])
{
//子进程退出,父进程不退出
pid_t cpid = fork();
if(cpid > 0) //父进程
{
while(1)
{
printf("this is parent: %d %d\n",getpid(),cpid);
sleep(1);
}
}
else if(0 == cpid)
{
}
else
{
perror("fork");
return -1;
}
return 0;
}
练习11:守护进程(幽灵进程)
1.守护进程脱离于终端,且运行在后端。
2.守护进程在执行过程中不会将信息显示在任何终端上,避免影响前端任务执行。且不会被任何终端产生的终端信息所打断。
3.守护进程目的:需要周期性执行某个任务或者周期性等待处理某些事情的时候,为了避免影响前端执行或者被前端信息打断的时候,可以使用守护进程。
守护进程的创建:
1.创建孤儿进程:所有工作都在子进程中执行,从形式上脱离终端控制。
fork(), 退出父进程
2.创建新的会话组:使子进程完全独立出来,防止兄弟进程对其有影响
setsid() 函数 功能:创建一个新的进程组和会话组,成为该进程组和会话组组长 原型: #include <sys/types.h> #include <unistd.h> pid_t setsid(void);
3.修改当前孤儿进程的运行目录为不可卸载的文件系统:例如根目录,/tmp
防止运行目录被删除后,导致进程崩溃
chdir函数 功能:修改运行目录; 原型: #include <unistd.h> int chdir(const char *path); chdir("/");
注意:从当前位置往后,运行在指定的目录下
4.重设文件权限掩码:umask(0), 一般清零;
5.关闭所有文件描述符,从父进程继承过来的文件描述符不会用到,浪费资源。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <head.h>
int main(int argc, const char *argv[])
{
//创建孤儿进程
pid_t cpid = fork();
if(0 == cpid)
{
//创建新的会话
pid_t sid = setsid();
printf("sid = %d\n", sid);
//修改运行目录为不可卸载的文件目录下
chdir("/");
//清空文件权限掩码
umask(0);
//关闭所有文件描述符
for(int i = 0; i<getdtablesize(); i++)
close(i);
while(1)
{
//守护进程运行的周期性代码
sleep(1);
}
}
return 0;
}
三、课后作业:
1.打印时钟在终端上,若终端输入quit,结束时钟
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <head.h>
int main(int argc, const char *argv[])
{
pid_t cpid = fork();
if(cpid > 0)
{
time_t t;
struct tm *info=NULL;
while(1)
{
if(waitpid(-1,NULL,WNOHANG) > 0)
break;
t = time(NULL);
info = localtime(&t);
printf("%d-%02d-%02d %02d:%02d:%02d\r",\
info->tm_year+1900,info->tm_mon+1,\
info->tm_mday,info->tm_hour,info->tm_min,info->tm_sec);
fflush(stdout);
sleep(1);
}
}
else if(0 == cpid)
{
char str[10]="";
while(1)
{
scanf("%s",str);
if(0 ==strcmp(str,"quit"))
exit(0);
sleep(1);
}
}
return 0;
}