进程
如果程序是菜谱,进程就是厨师烹饪;
如果程序是乐谱,进程就是乐师演奏;
如果程序是剑谱,进程就是剑客舞剑;
如果程序是棋谱,进程就是棋士复盘;
程序是静的,进程是动的。
进程与程序区别
进程 | 程序 |
---|---|
动态 | 静态 |
有生命周期 | 指令集和 |
只能对应一个程序 | 可以对应多个进程 |
概念
从代码到程序
从程序到进程
·内核将程序读入内存,为程序镜像分配内存空间。
·内核为该进程分配进程标志符PID。
·内核为该进程保存PID及相应的进程状态信息。
程序格式ELF
ELF(Executable and Linkable Format)文件格式,一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件。
查看程序(ELF文件):
readelf -S 文件名
查看进程空间大小:
size 文件名
虚拟存储器/虚拟地址空间
gdb查看内存映射信息:
info proc mapping
进程状态
状态 | 含义 |
---|---|
就绪(Ready) | 进程已获得到除CPU以外的所有必要的资源,获得CPU立即执行 |
运行(Running) | 程序正在CPU上执行 |
阻塞(Blocked) | 等待某个事件发生而无法执行时,放弃CPU |
如何查看进程
ps命令
进程操作接口
获取PID
进程标识pid:进程身份证号
函数 | 接口 |
---|---|
pid_t getpid() | 获取当前进程ID |
pid_t getppid() | 获取当前进程父进程ID |
#include <stdio.h>
#include <unistd.h>
int main(){
printf("PID:%dPPID:%d\n",getpid(),getppid());
}
如何查看进程的PID和PPID:
ps -o pid,ppid,cmd,s
如何创建进程
1.分叉函数pid_t fork()
返回值 | 含义 |
---|---|
-1 | 失败 |
0 | 子进程逻辑控制流 |
其他(子进程PID) | 父进程逻辑控制流 |
特点:
1.调用一次,返回两次
2.相同但是独立的地址空间
3.并发执行
4.共享文件
示例
调用一次,返回两次:
#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(;;);
}
相同但是独立的地址空间&并发执行:
#include <stdio.h>
#include <unistd.h>
int i = 100;
int main(){
int j=100;
pid_t pid = fork(); //fork之后,i变量虽然地址相同,但是其实一分为二被父子两个
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);
}
}
共享文件:
#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);
}
}
2.执行函数exec()
分类:
分类 | 函数 |
---|---|
字符串数组参数 | execv()、execvp()、execve() |
可变参数 | execle()、execlp()、execl() |
exec函数组名字规律:
字符 | 含义 |
---|---|
v | 第二个参数是数组 |
l | 第二个参数之后是变参 |
p | 第一个参数是文件名 |
e | 最后一个参数是环境变量 |
返回值
返回值 | 含义 |
---|---|
-1 | 失败 |
不返回 | 成功 |
本质:覆盖程序
示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
extern char ** environ;
int main(int argc,char** argv){
printf("%s,PID %d\n",argv[0],getpid());
//execl("/bin/ps","ps","-a","-o","pid,ppid,cmd",0); //取命令的位置
//execlp("ps","ps","-a","-o","pid,ppid,cmd",0); //取命令文件名即可
//execle("/bin/ps","ps","-a","-o","pid,ppid,cmd",0,environ);
char* args[] = {"ps","-a","-o","pid,ppid,cmd",0};
//execv("/bin/ps",args);
execve("/bin/ps",args,environ);
//execvp("ps",args);
printf("%s,PID %d\n",argv[0],getpid());
}
注意:可以通过which查看命令的位置。
3.系统函数int system(Shell字符串)
返回值说明:
返回值 | 含义 |
---|---|
-1 | 失败 |
127 | 无法启动shell |
其他 | 命令退出码 |
特点: 一次调用,一次返回
本质: shell执行命令/程序
示例:
#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());
}
Linux系统可以创建多少个进程?使用ulimit -a可以查看到。可以通过ulimit -u 进程数修改。
更详细的设置可以在/etc/security/limits.conf修改。
如何结束进程
方式 | 说明 |
---|---|
main函数退出 | 只能用在main函数内 |
调用exit()函数 | 一般用在main函数以外的函数 |
调用_exit()函数 | 一般用来结束子进程 |
调用abort()函数 | 一般用来异常退出 |
信号终止 | 终止其他进程 |
示例:
return退出:
#include <stdio.h>
#include <unistd.h>
int main(){
printf("PID:%d,PPID:%d\n",getpid(),getppid());
return 100;
}
exit()/abort()退出:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
printf("PID:%d,PPID:%d\n",getpid(),getppid());
//exit(EXIT_FAILURE);
abort();
}
如何停止进程
1.休眠
int sleep(unsigned int secs)
参数
secs指定休眠的秒数,-1表示永久休眠
返回值
未休眠的秒数
特性
如果没有信号中断,休眠指定秒数返回0,否则马上返回未休眠的秒数。
实现时钟:
#include<iostream>
#include<unistd.h>
#include<ctime>
using namespace std;
int main(){
while(true){
time_t t = time(NULL);
tm* ptm = localtime(&t);
char str[20]=[0];
strftime(str,sizeof(str),"%Y/%m/%d %T",ptm);
cout << '\r' << str << flush; //每次刷新后光标回到行首
sleep(1); //睡眠一秒后刷新
}
}
实现进度条:
#include<iostream>
#include<unistd.h>
#include<iomanip>
using namespace std;
int main(){
for(int i=0;i<=100;++i){
cout << '\r' << setw(2) << i << '%'; //刷新数字为两位
for(int j=0;j<=i;++j){
cout << "=";
}
cout << flush;
usleep(100000); //usleep进程的睡眠单位为微秒
}
cout << endl;
}
2.暂停
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");
}
3.等待
pid_t wait(int* status); //等价于pid_t waitpid(-1,stauts,0)
pid_t waitpid(pid_t pid,int * status,int options);
参数说明:
参数 | 说明 |
---|---|
pid | <-1:等待进程组为pid的所有进程;-1: 等待任何子进程;0:等待同组的进程;>0:进程为pid 的子进程 |
status | 判断正常结束:使用WIFEXITED(status);判断异常结束使用WIFSIGNALED(status);判断暂停使用WIFSTOPPED(status) |
options | WNOHANG若子进程没有结束,返回0,不予以等待;若子进程结束,返回该子进程的ID。WUNTRACED若子进程进入暂停状态,则马上返回,但子进程的结束状态不予以理会 |
正常结束:WIFEXITED(status)
参数 | 说明 |
---|---|
非0 | 正常结束子进程 |
0 | 非正常结束子进程 |
WEXITSTATUS(status)取得子进程exit()返回的结束代码,一般会先用WIFEXITED来判断是否正常结束才能使用此宏
异常结束:WIFSIGNALED(status)
参数 | 说明 |
---|---|
非0 | 异常结束子进程 |
0 | 非异常结束子进程 |
WTERMSIG(status)取得子进程因信号而中止的信号代码,一般会先用 WIFSIGNALED 来判断后才使用此宏
暂停:WIFSTOPPED(status)
参数 | 说明 |
---|---|
非0 | 暂停结束子进程 |
0 | 非暂停结束子进程 |
WSTOPSIG(status)取得引发子进程暂停的信号代码,一般会先用 WIFSTOPPED 来判断后才使用此宏。
返回值说明:
返回值 | 说明 |
---|---|
-1 | 失败 |
其他 | 等到的pid |
示例
父进程等待子进程退出:
#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));
}
}
更加安全的方式:
#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(;;);
}
特殊进程
示例:
#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:
#include<iostream>
#include<sstream>
#include<vector>
#include<unistd.h>
using namespace std;
int main(){
for(::){
//从终端读取一行
string line;
getline(cin,line);
//把命令行按照空白符分割
istringstream iss(line);
vector<string> cmd;
string option;
while(iss >> option){
cmd.push_back(option);
}
const char* opts[cmd.size()+1] = {NULL};
int i = 0;
for(auto& c:cmd)
opts[i++] = c.c_str();
if(0 == fork()){
if(-1==execvp(opts[0].const_cast<char* const>(opts))){
perror("execute cmd error");
}
}
}
}