1Linux进程概述
进程是一个程序执行一次的过程,他和程序有着本质的区别。程序是静态的,他是一些保存在磁盘上的指令有序的集合。
进程是动态的概念,他是运行者的程序,包含进程的动态创建,调度和消亡,是Linux的基本调度单位。
进程控制块(PCB)是进程的静态描述,包括进程的描述信息,进程的控制信息,以及资源信息
时间片:他轮流在每个进程的得到的时间片用完后从进程那里千回控制权
1.1进程标识
os会为每一个进程分配一个唯一的盛行ID,作为进程的标识号(pid),还有父进程ID(ppid)
所有的进程的祖先都是同一个进程init进程,ID为1
通过getpid(),getppid() 得到进程的pid,ppid
实例:printf("pid:%d ppid:%d\n",getpid(),getppid());
1.2进程的用户ID与组ID
进程运行过程中必须有类似于用户的身份,哪个用户就是该用户的身份,就是那个用户的组 可用getuid(),getgid();获得 进程还有有效用户ID和有效组ID,缺少的情况下,与真实ID相同,可以用geteuid(),getegid(); 文件权限有S的时候,有效ID是进程的所有者(创建者),否则有效ID就是程序的运行者,与真实ID相同
ps -aux查看所有用户进程的权限,cpu,和内存的使用情况
ps -ef 查看用户操作的PID与PPID 与CMD(命令行)
1.3进程的状态
执行态,就绪态,等待态
1.4LInux下的进程结构
Linux中的进程包含三个段:数据段,代码段,堆栈段
数据段:(普通数据段)全局变量,常熟(bbs数据段)为初始化的全局变量以及(堆)动态数据分配的数据空间
代码段:存放程序代码的数据
堆栈段:子程序的返回地址,子程序的参数,程序的局部变量
1.5LInux下的进程管理
进程process:是os的最小单位
1)ps查看活动进程
2)ps -aux查看所有进程%cpu,%mem stat状态(S睡眠T暂停R运行Z僵尸)
3)ps -aux|grep'aa' 查找指定的(aa)进程
4)ps -ef可以现实父子关系和cmd
5)top现实前20条的进程,动态改变
6)./my_add 可能要运行很长时间,按ctrl+z可以把京城暂停,在执行 bg作业ID 可以将该进程带入后台运行例如:
[1]+ ./my_add 1 34 & ,&表示该进程在后台正在运行。
利用jods可以查看后台任务
fg作业ID 把后台人物带到前台
7)kill -9 进程号(PID) 杀掉该进程 ,pkill a 进程名
2 进程的创建
Linux下有四类创建子进程的函数:system(),fork(),exec*(),popen();
2.1system函数
system函数system括号里面放的是命令,也就是cmd
例子如下:
#include<iostream>
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
using namespace std;
int main(int argc,char *argv[]){
char cmd[1024]="";
for(int i=1;i<argc;i++){
strcat(cmd,argv[i]);
strcat(cmd," ");
}
puts(cmd);
int ans;
ans=system(cmd);
printf("%x\n",ans);
}
用system调用其他可执行程序,并输入参数 输入例如: ./main ./my_add 其中my_add计算两个数之和
其中计算结果在system的返回值高字节上:ret00,所以直接出256
int main(int argc,char *argv[]){
if(argc!=2){
printf("failed\n");
return 0;
}
int left,right;
char cmd[1024]="";
char line[1024]="";
strcat(cmd,argv[1]);
strcat(cmd," ");
while(printf(">>"),scanf("%d %d",&left,&right)){
memset(line,0,sizeof(line));
sprintf(line,"%s %d %d",argv[1],left,right);
strcpy(cmd,line);
int ret;
ret=system(cmd);
printf("result:%d\n",ret/256);
}
}
2.2fork函数
fork函数在以存在的进程中创建一个新的进程,新的进程作为子进程,原进程为父进程
父进程的返回值是子进程,子进程的返回值是0.
fork创建子进程是完全复制父进程,而且缓冲区也复制
元进程和子京城都从函数fork返回,在各自继续圆形下去,但是元函数的fork返回值
是子进程的pid,而在子进程中fork返回0,返回-1表示创建失败
#include<iostream>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
using namespace std;
int main(int argc,char *argv[]){
pid_t pid;
int i=3;
printf("hello\n");
pid=fork();
fflush(stdout);//儿子复制的时候将缓冲区也复制了,如果没有这句话,上面的hello将出现两便
if(pid>0){
printf("parents:pid:%u,ret:%u,pp:%u\n",getpid(),pid,getppid());
}
else if(pid==0){
// sleep(5);
printf("child:pid:%u,ret:%u,pp:%u\n",getpid(),pid,getppid());
}
printf("bye!\n");
}
2.3exec函数
exec函数是用exec的第一个参数制定的程序覆盖现有的进程空间,也就是说执行exec族函数之后,他后面的所有代码不再执行
详细使用方法 在终端输入man exec
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
其中path是包含执行文件名的全路径名,多个参数时,注意后面最后一个参数必须为NULL,arg是可执行文件的命令行参数
int execl(const char *path, const char *arg, ...);例如:
if(execl("/home/yang/0820/my_exec/","my_add","3","2",NULL)==-1){
perror("execl error");
}
int execlp(const char *file, const char *arg, ...);例如:
if(execlp("./my_add","my_add","fwef","fwe","fewgeraf",NULL)==-1){
perror("execlp error");
}
int execv(const char *path, char *const argv[]);例如:
char *args[10];
args[0]="my_add";
args[1]="12";
args[2]="23";
args[3]=NULL;
if(execv("/home/yang/0820/my_exec/my_add",args)==-1){
perror("execl:");
}
2.4popen函数(以后补充)
popen函数类似system函数,与system不同之处在于它使用管道操作,
原型为
#include<stdio.h>
FILE*popen(const char *command, const char *type);
int pclose(FILE *stream);
command为可执行文件的全路径和执行参数, t ype可选参数为“r” 或“w”
“w”popen返回的文件流作为新的进程(例如my_add.exe)标准输入流,即stdin
“r”popen返回的文件流作为新进程的标准输出流 stdout,
也就是说popen只改变新进程的标准输入流或者标准输出流
popen的详细流程:type为“r”,(即command命令的执行结果作为当前进程的输入结果),调用程序利用popen函数返回FILE*文件流指针,就可以通过常用的stdio库如fgets,来读取被调用函数的输出。如果type是“w”(即当前的程序的输出结果作为commend命令的输入),调用程序可以用fwrite想调用程序发送数据,而被调用的程序可以在自己的标准输入上读取这些数据
例子:
主程序屏幕输出:
FILE *fp;
char a[1000];
char b[1000];
char cmd[1024]="";
printf("plseas write\n");
fgets(a,1000,stdin);
sprintf(cmd,"%s %s",argv[1],a);
fp=popen(cmd,"r");//这个指令cmd为可执行文件的全路经
fgets(b,1000,fp);
printf("%s\n",b);
新进程屏幕输出:
FILE *fp;
char a[1000];
gets(a);
fp=popen(argv[1],"w");
fputs(a,fp);
pclose(fp);
3.进程控制与终止
用fork函数启动一个子进程时,子进程就有了自己的生命独立运行了
孤儿进程:如果父进程先于子进程退出,子进程就变成了孤儿进程(通常是父进程负责释放子进程的内存空间),此时将自动被PID为1的进程(即init)接管。孤儿进程退出后,它的清理工作由祖先进程init自动处理
僵尸进程:子进程退出,系统不会自动清理掉子进程的工作环境,必须有父进程调佣wait或waitpid函数来完成清理工作,如果父进程不做清理工作,一经推出的子进程就会成为僵尸进程,系统中如果僵尸进程过多就会影响系统性能
函数原型:
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid,int *status,int options);
wait函数随机等待一个已经退出的子进程,并返回该子进程的pid
waitpid等待制定pid的子进程,如火为-1表示等待所有子进程
status参数是传出参数,存放子进程的退出状态
options用于改变waitpid的行为,其中最重要的是WNOHANG,它表示无论子进程是否有退出都立即返回
产生僵尸进程例子:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
main()
{
pid_t pid = fork();
if( pid == 0 )
{
exit(10);
}
else
{
sleep(10);
}
}
避免僵尸进程例子:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
main()
{
pid_t pid = fork();
if( pid == 0 )
{
exit(10);
}
else
{
wait(NULL); //NULL表示等待所有进程
sleep(10); //通常要将sleep放在wait的后面,要不然也会出现僵尸进程
}
}
3.2进程的终止
5种方式终止:
1) main函数自然返回
2)调用exit函数
3)调用_exit函数
调用abort函数
接受能导致进程终止的信号ctrl + c
exit与_exit区别:exit会处理缓冲区的内容,_exit不会处理缓冲区的内容
exit和_exit函数的原型:
#include<stdlib.h> //exit的头文件
#include<unistd.h> //_exit的头文件
void exit(int status);
void_exit(int status);
status是一个整型的参数,可以利用这个参数传递进程结束是的状态,0表示正常退出,其他数表示出现错误,进程非正常结束