进程概念
进程是程序在计算机上执行一次的过程,也就是一个执行中的程序,进程是一个独立的逻辑控制流,独占处理器,相当于工人和机器,进程具有私有的地址空间,独占存储器系统,相当于工厂;进程的本质是程序在地址空间中按照代码逻辑控制流执行的过程,同时进程是资源分配的最小单位;
进程和程序的区别
进程是动态的,并且具有生命周期,并且一个进程只能对应于一个程序;相反,程序是动态的指令集合,一个程序可以对应于多个进程;
进程的状态
就绪:Ready,当进程获得出CPU以外的所有的必要的资源时,进程进入就绪状态,也就是说当进程得到CPU就可以立即运行;
运行:Runing,进程正在占据CPU资源;
阻塞(Blocked):进程犹豫等待某个事件发生而无法执行时,进程被迫放弃CPU执行资源;
进程的创建
进程的查看
Windows可以使用tasklist 例如:tasklist / F1 "PID eq 进程PID";
在Linux底下可以使用ps命令进行查看,ps命令的相关选项:
user:表示进程所属的用户;
PID:表示进程ID;
ppid:表示父进程ID;
%CPU:表示CPU占有率;
%mem:表示内存占有率;
VSZ:表示进程虚拟大小;
RSS:表示常驻内存,也就是内存中页面的数量;
tty:表示进程使用的终端;
stat:表示进程的状态;
start:表示启动进程的时间;
TIME :表示进程消耗CPU的时间;
command:表示打开进程的命令和参数;
进程状态标识符:
D:表示不可中断进程,Uninterruptible;
R:表示正在运行,或者在队列中的进程;
S:处于休眠状态;
T:进程处于停止或者被追踪状态;
Z:僵尸进程;
W:表示进入内存交换,从内核2.6以后开始失效;
X:表示死掉的进程;
<:表示高优先级;
n:表示低优先级;
s:表示包含子进程;
+:表示位于后台的进程组;
查看某个具体的进程:ps -p pid;ps -C在命令行进行查看;同样的还可以使用pstree进行查看,但是可能需要安装yum install psmisc -y;同样的的还可以使用top命令动态的查看系统进程;
进程的杀死
windows:task kill /F /PID 或者task kill /F /IM 程序名
linux:kill 进程标识PID
与进程相关的文件一般在/proc/底下,通常是进程id作为目录名的文件;
进程的操作
进程的标识通常使用pid来完成,获取当前进程的id使用函数pid_t getpid();获取当前进程父进程的ID使用函数pid_t getppid();
#include <stdio.h>
#include <unistd.h>
int main(){
printf("PID:%dPPID:%d\n",getpid(),getppid());//分别用于获得当前进程ID和父进程ID;
for(;;);
}
关于进程的创建
fork()函数
函数失败时返回值是-1,返回值为0表示子进程逻辑控制流,如果返回值为其他,表示父进程控制流;
函数的特点:
1、调用一次,返回两次;
2、相同但是具有独立的地址空间;
3、可以并发执行;
4、这个函数的本质是函数堆栈数据,text文本的复制;
函数的本质是父进程函数相关数据的复制,然后分叉出另一个函数;
#include <stdio.h>
#include <unistd.h>
int main(){
printf("PID:%d,PPID:%d\n",getpid(),getppid());
pid_t pid = fork();
fork();
if(pid == 0){// child
printf("this is child\n");
printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
}else{
printf("this is father\n");
printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
}
for(;;);
}
子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。注意,子进程持有的是上述存储空间的“副本”,这意味着父子进程间不共享这些存储空间。
fork02.c:这个函数是用来构建子进程,用来输出一些值;
#include <stdio.h>
#include <unistd.h>
int i = 100;
int main(){
int j=100;
pid_t pid = fork();
if(pid == 0){// child
int k;
for(k=0;k<10000;k++)
printf("this is childi%d\t j%d\n",++i,++j);
}else{
int k;
for(k=0;k<10000;k++)
printf("this is fatheri%d\t j%d\n",--i,--j);
}
}
fork03.c:这个函数父进程在增加变量的值,子进程在减小函数的值,用来模拟两个进程的运行情况;
#include <stdio.h>
#include <unistd.h>
int i = 100;
int main(){
int j=100;
FILE* fd = fopen("./test","w+");
pid_t pid = fork();
if(pid == 0){// child
int k;
for(k=0;k<10000;k++)
fprintf(fd,"this is childi%d\t j%d\n",++i,++j);
}else{
int k;
for(k=0;k<10000;k++)
fprintf(fd,"this is fatheri%d\t j%d\n",--i,--j);
}
}
exec函数簇
char **environ;
int execl (const char *path, const char *arg0, ..., (char*)0);
int execlp(const char *file, const char *arg0, ..., (char*)0);
int execle(const char *path, const char *arg0, ..., (char*)0, char *const envp[]);
int execv (const char *path, char *const argv[]);
int execvp(cosnt char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
execl.c
#include<stdio.h>
#include <unistd.h>
extern char ** environ;
int main(int argc,char **argv){
execl("/bin/ps","ps","-a","-o","pid,ppid,cmd,stat",0);
return 0;
}
execle.c
#include<stdio.h>
#include<unistd.h>
extern char **environ;
int main(int argc,char **argv){
execle("/bin/ps","ps","-a","-o","pid,ppid,cmd,stat",0,environ);
return 0;
}
execlp.c
#include <stdio.h>
#include <unistd.h>
int main(int argc,char **argv){
execlp("ps","ps","-a","-o","pid,ppid,cmd,stat",0);
return 0;
}
execv.c
#include <stdio.h>
#include <unistd.h>
int main(){
char *args[]={"ps","-a","-o","pid,ppid,cmd,stat",0};
execv("/bin/ps",args);
return 0;
}
execve.c
#include <stdio.h>
#include <unistd.h>
extern char **environ;
int main(){
char *args[]={"ps","-a","-o","pid,ppid,cmd,stat",0};
execve("/bin/ps",args,environ);
return 0;
}
execvp.c
#include<stdio.h>
#include<unistd.h>
int main(){
char *argv[] = {"ps","-a","-o","pid,ppid,cmd,stat",0};
execvp("ps",argv);
return 0;
}
函数命名的规律组总结
v:表示第二个参数是数组;
l:第二个参数之后是变参;
p:第一个参数是文件名;
e:最后一个参数是环境变量;
常见的字符串数组函数
execv(),execvp(),execve()
可变参数函数
execle(),execlp(),execl();
exec.c函数:
exec函数簇函数的返回值,-1表示失败,执行成功时,是不返回任何值的,这些函数具有以下特点:一次调用,失败返回;改朝换代,取而代之;但是函数的PID是不会变化的,变化的是程序地址空间的内容,exec函数簇函数的本质是覆盖原有函数,执行新的函数;
system(shell字符串)
这个函数是系统函数int system(shell 字符串或者命令);函数的返回值为-1时,表示失败;函数的返回值是127时,表示无法启动shell;其它返回值用于表示函数的退出码;系统调用函数一次调用,一次返回;这个函数本质上是在执行shell命令;
system.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc,char** argv){
printf("PID:%d\n",getpid());
system("sleep 3&");
printf("PID:%d\n",getpid());
}
进程的结束
对于return和exit来说,函数的执行属于寿终正寝,并且自己能够了结后事。_exit()函数属于自杀的情况,但是没有料理后事。abort()函数属于意外的死亡,也没有料理后事;信号终止属于他杀;料理后事表示的意思是程序结束以后,相关堆栈资源的回收工作;
main()函数return 0 是语言级别的退出,使用这种情况的退出,函数仍然需要调用exit(0)函数,exit(0)是函数级别的退出;
exit(0),表示释放所有的静态的全局对象,缓存,关掉所有的IO通道,然后终止程序;abort()会直接终止程序,但是不会释放任何资源;
exit(0)函数在调用_exit()系统调用之前要检查文件的打开情况,吧文件缓冲区中的内容写回文件,清理IO缓冲,exit(0)是标准的C函数;
_exit()不做清理IO缓冲函数,_exit()是Linux系统调用;
main函数退出:
main.c
#include <stdio.h>
#include <unistd.h>
int main(){
printf("PID:%d,PPID:%d\n",getpid(),getppid());
return 100;
}
exit函数退出
exit函数一般用在main函数以外的函数退出
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
printf("PID:%d,PPID:%d\n",getpid(),getppid());
exit(EXIT_FAILURE);
abort();
}
_exit函数一般用来结束子进程
这个函数暂时没有说明
abort()函数一般用来异常退出
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
printf("PID:%d,PPID:%d\n",getpid(),getppid());
abort();
}
还有一种方式--信号终止
pause.c
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void test(int sig){
printf("revc a signal%d",sig);
}
int main(){
signal(SIGINT,test);
printf("before pause\n");
pause();
printf("after pause\n");
}
进程的停止
int sleep(unsigned int secs)函数 ,sece用于表示休眠的秒数,如果指定为-1表示永久休眠,函数的返回值是未休眠的秒数;如果没有信号中断,休眠指定的秒数返回0,否则马上返回未休眠的秒数;
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <strings.h>
int main(){
int i = 0;
for(;;){
time_t t;
time(&t);
struct tm* ptm = gmtime(&t);
char buf[BUFSIZ];
bzero(buf,BUFSIZ);
strftime(buf,BUFSIZ,"%P %T",ptm);
printf("\r%s",buf);
fflush(stdout);
sleep(1);
}
}
int pause()函数返回值一定是-1,如果函数没有处理信号,直接中断,执行默认的信号处理,程序后续代码不再执行。如果程序存在信号处理,执行处理信号后,执行后续代码。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void test(int sig){
printf("revc a signal%d",sig);
}
int main(){
signal(SIGINT,test);
printf("before pause\n");
pause();
printf("after pause\n");
}
pid_t wait(int * status);函数等价于pid_t waitpid(-1,status,0);这个函数的参数包括:pid:小于-1时,表示的是等待进程组为pid的所有进程,-1:表示等待任何子进程;0:表示等待同组的进程;大于0:表示等待进程为pid的子进程;
status:参数的含义:
正常结束,WIFEXITED(status),参数为0时,表示的是正常结束任何子进程,0表示的是非正常结束子进程;WEXITSTATUS()表示取得子进程exit()返回的结束代码,一般会先用WIFEXITED来判断是否正常结束才是用这个宏定义;
异常结束:WIFSIGNALED(ststus)非0值表示异常结束子进程,0表示非异常结束子进程;WTERMSIG(status),取得子进程因信号而终止的信号代码,一般先会用WIFSIGNALED来判断后才能使用宏;
暂停:WIFSTOPPED(status),非0值表示暂停结束子进程,0表示暂停结束子进程;WSTOPSIG(status),取得引发子进程暂停的信号代码,一般先用
WIFSTOPPED来判断后才能使用此宏;
option,选项WNOHANG,若子进程没有结束,返回0,不予等待。若子进程结束,返回该子进程的ID;WUNTRACED,若子进程进入暂停状态,则马上返回,但子进程的结束状态不予理会。
返回值,-1表示失败;其他值表示等待的PID;
wait.c:
#include <stdio.h>
#include <unistd.h>
#include <wait.h>
void handler(int sig){
pid_t cpid = wait(NULL);
printf("child %d exit",cpid);
}
int main(){
signal(SIGCHLD,handler);
printf("PID:%d,PPID:%d\n",getpid(),getppid());
pid_t pid = fork();
if(pid == 0){// child
sleep(2);
printf("this is child\n");
printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
}else{
printf("this is father\n");
printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
}
}
wait02.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <wait.h>
void handler(int sig){
int status;
pid_t cpid = wait(&status);
if(WIFEXITED(status)){
printf("child exit by %d\n",WEXITSTATUS(status));
}
if(WIFSIGNALED(status)){
printf("child exit by signal %d\n",WTERMSIG(status));
}
printf("child %d exit\n",cpid);
}
int main(){
signal(SIGCHLD,handler);
printf("PID:%d,PPID:%d\n",getpid(),getppid());
pid_t pid = fork();
if(pid == 0){// child
sleep(2);
printf("this is child\n");
printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
//exit(0);
}else{
printf("this is father\n");
printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
printf("leave:%d\n",sleep(5));
//exit(200);
}
for(;;);
}
孤儿进程与僵尸进程
孤儿进程:父进程先于子进程退出,init进程作为新的父进程,但是对于系统无害,init进程会回收这些系统资源;
僵尸进程:子进程退出,父进程没有获得子进程的状态信息,可以通过调用wait(status)或者调用waitpid函数来实现;
waitpid.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <wait.h>
int main(){
printf("PID:%d,PPID:%d\n",getpid(),getppid());
pid_t pid = fork();
if(pid == 0){// child
sleep(2);
printf("this is child\n");
printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
//exit(0);
}else{
printf("this is father\n");
printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
printf("pid:%d exit\n",waitpid(pid,NULL,0));
}
}
zomble.c:
#include <stdio.h>
#include <unistd.h>
#include <wait.h>
void handle(int sig){
//wait(NULL);
printf("this is child exit %d",sig);
}
int main(){
signal(SIGCHLD,handle);
printf("PID:%d,PPID:%d\n",getpid(),getppid());
pid_t pid = fork();
if(pid == 0){// child
printf("this is child\n");
printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
}else{
printf("this is father\n");
printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
for(;;);
}
}
一个模拟简单shell的程序:
shell.c
//这个程序是一个简单的shell程序,可以用于执行shell命令中不带有参数选项的命令;
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
void handle(int sig){
wait(NULL);
}
int main(){
printf("welcome to sim shell\n");
//
signal(SIGCHLD,handle);
for(;;){
printf(">");
fflush(stdout);
char buf[BUFSIZ];
bzero(buf,BUFSIZ);
fgets(buf,BUFSIZ,stdin);
size_t n = strlen(buf);
if(buf[n-1] == '\n'){
buf[n-1] = '\0';
}
if(strcmp(buf,"quit")==0)
break;
//system(buf);
char* argv[128];
bzero(argv,128);
char* p = buf;
int index = 0;
char* temp = NULL;
do{
temp =strtok(p," ");
p = NULL;
argv[index++] = temp;
}while(temp != NULL);
if(fork() == 0){
execvp(argv[0],argv);
}
//sleep(-1);
pause();
}
printf("sim shell exit\n");
return 0;
}