切板中的内容输出到文件### 进程相关概念
程序:编译好的二进制文件,在磁盘上,不占用系统资源(不包括磁盘)。(剧本)
进程:占用系统资源,是程序的一次运行。(戏剧)
一个程序可以产生多个进程,一个进程可以调用多个程序
并发:并行执行
单道程序设计:DOS系统
多道程序设计:时钟中断
中央处理器CPU
存储介质:按照容量从大到小:
硬盘->内存->cache(高速缓存)->寄存器
预取器:从cache中取出指令
译码器:解析指令
算数逻辑单元(ALU):只会+
和<<
寄存器堆:ALU操作的寄存器扎堆的地方
然后再将寄存器中的值返回给cache
MMU
MMU位于CPU内部:负责虚拟内存和物理内存之间的映射,设置修改内存访问的级别(CPU中设置了0-3四个等级,Linux系统中用到0级内核区和3级用户区)
每产生一个进程产生一个虚拟内存:可用的地址空间
每次最小分配物理内存4K(一个page)
同一个程序的不同进程的kernel区内存映射到同一个空间,但是使用的是不同的PCB
PCB
处于kernel区,进程描述符\进程控制块,实际上是一个task_struct
结构体,里面有很多的成员
- 进程的id,无符号整数
- 进程状态:初状态、就绪状态、运行状态、挂起状态、终止状态
- 进程切换时候需要保存和恢复的一些寄存器
- 描述虚拟地址空间的信息
- 描述控制终端的信息
- 当前进程的工作目录
umask
掩码- 文件描述符表
- 和信号相关的信息
- 用户id和组id
- 会话和进程组
- 进程可以使用的资源上限
ulimit -a
环境变量
Linux系统是多用户多任务的开源操作系统
用户操作计算机的时候运行的一些信息通过环境变量进行设置
- 字符串
char * environ[]
,存储在用户区,高于stack
的起始位置 - 统一的存储格式:
名字=值[:值]
- 值用来描述进程环境信息
以shell
为例,所使用的环境变量为PATH
,在解析命令的时候按照PATH
中的内容从前往后逐个目录进行查找,因此如果希望使用新版本软件应该把新版本软件的环境变量向前移动
SHELL 所使用的命令解析器在哪里
HOME 家目录在哪里
LANG 使用的是什么语言
TERM 所使用的终端类型,图形界面所使用的一般是xterm,可以显示汉字,字符界面一般不可以
通过程序打印所有的环境变量:
#include<stdio.h>extern char ** environ;//引入环境变量表int main(void)
{int i;for(int i=0;environ[i]!=NULL;++i){printf("%s\n",environ[i]);}return 0;
}
相关函数
getenv
手册第三章setenv
unsetenv
删除环境变量:即使没有那个环境变量也会返回成功,只有当参数为已经有的环境变量的非法格式例如:name=
才会报错,例如:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>int main()
{const char* name="123ABC";char *val;setenv(name,"123",1);printf("%d\n",unsetenv("123ABC=1")); //-1printf("%d\n",unsetenv("123ABC")); //0return 0;
}
进程控制
创建进程的方法:
- 运行可执行程序
- 通过
fork
函数创建子进程
fork函数创建子进程
#include<unistd.h>
pid_t fork(void)
fork
有两个成功返回值
如果子进程创建失败则返回-1,并且将错误信息保存在erron中,我们可以使用perror输出错误信息。
如果子进程创建成功则会在父进程中返回子进程的ID,在子进程中返回0
父进程的fork
返回子进程ID,子进程的fork
返回值为0,通过对返回值的判断处于哪个进程
可执行文件的父进程是bash
创建单个进程
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>int main()
{printf("begin:\n");pid_t pid = fork();if(-1 == pid){perror("process creat:");exit(1);}else if(0 == pid){printf("This is son process,pid = %d \n",(int)getpid());printf("My father process pid = %d \n",(int)getppid());}else{printf("This is father process,pid = %d \n",(int)getpid());printf("My father process pid = %d \n",(int)getppid());sleep(1);}printf("end\n");return 0;
}
循环创建N个子进程
如果直接使用循环进行创建,则n层循环会创建2n-1个子进程,这显然不是我们需要的。
因此我们需要在子进程中直接跳出循环,这样就不会产生过多的子进程
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>int main(int argc,char* argv[])
{if(argc<2){printf("too few arguments\n");exit(1);}if(argc>2){printf("too few arguments\n");exit(1);}int i,limit=argv[1][0]-'0';printf("begin:\n");for(i=0;i<limit;++i){pid_t pid = fork();if(-1 == pid){perror("process creat:");exit(1);}else if(0 == pid){printf("This is %dth son process,pid = %d \n",i+1,(int)getpid());printf("My father process pid = %d \n",(int)getppid());break;}
}printf("end\n");return 0;
}
在《APUE》中说到如果不加控制的话父进程有98%的可能性获得CPU的控制权(不过我的电脑上并不是这样),由内核的调度算法决定
getuid
获取当前进程实际用户IDuid_t getuid(void);
获取当前进程有效用户IDuid_t geteuid(void);
getgid
获取当前进程实际用户组IDgid_t getgid(void);
获取当前进程有效用户组IDgid_t getegid(void);
进程共享
父子进程相同的:全局变量、.data,.text,栈、共享库、堆、环境变量、用户ID、宿主目录、进程工作目录、信号处理方式都是相同的
父子进程对于前面的变量的处理:读时共享写时复制
如果子进程只对前面的数据进行读取,则和父进程共享同一个变量,如果对前面的数据进行修改(写,改变),则复制一份新的,指针的话会制定一个新的地址。
父子进程不同的:进程ID,fork返回值,父进程ID,进程运行时间,闹钟(定时器),未决信号集
进程运行时间:子进程的运行时间为父进程fork()调用时间
父子进程共享:
- 文件描述符
mmap
建立的映射区(进程间的通信)
我自己写了一个测试程序
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>int main(int argc,char* argv[])
{int fd=open("test",O_CREAT | O_RDWR | O_TRUNC,0644);pid_t fid=getppid();if(argc<2){printf("too few arguments\n");exit(1);}if(argc>2){printf("too few arguments\n");exit(1);}int i,limit=argv[1][0]-'0';printf("begin:\n");for(i=0;i<limit;++i){pid_t pid = fork();if(-1 == pid){perror("process creat:");exit(1);}else if(0 == pid){break;}}char buffer[100];memset(buffer,0,sizeof(buffer));int sz=sprintf(buffer,"This is %dth son process,pid = %d \n",i+1,(int)getpid());write(fd,buffer,sz);if(getppid()==fid){close(fd);printf("end\n");}return 0;
}
gdb调试
使用gdb调试的时候,gdb只能跟踪一个进程,可以在fork函数调用之前,通过执行设置gdb调试工具跟踪父进程或者是跟踪子进程,默认跟踪父进程。
set follow-fork-mode child
命令设置gdb在fork之后跟踪子进程set follow-fork-mod parent
设置跟踪父进程- 注意一定在
fork
函数调用前设置才有效