进程的基础操作
获得当前进程的PID
#include <unistd.h>
#include <stdio.h>
int main(int argc, char** argv){
pid_t pid = getpid();
printf("进程的PID是:%d\n", pid);
return 0;
}
进程的PID是:4830
创建进程
- 函数
#include<unistd.h>
pid_t fork();
该函数用于创建子进程,子进程被创建后复制父进程的程序内容和变量内容(但不共享变量内存),包括该fork函数。如果进程创建成功,在父进程中,fork函数的返回值为子进程的PID;在子进程中fork函数的返回值为0。看起来就像是fork有两个返回值一样。如果进程创建失败,子进程没有产生,父进程中的fork函数返回-1。
- 示例代码
#include <unistd.h>
#include <iostream>
using namespace std;
int main(int argc, char** argv){
pid_t pid; // 用以记录进程的PID
int counter = 0; // 用以证明子进程复制了父进程的程序,但并不是内存共享
pid = fork(); // 创建子进程
if(pid<0){ // 进程创建失败
cerr<< "进程创建失败!"<< endl;
return -1;
}
else if(pid==0){ // 该进程为子进程
cout<< "我是子进程,PID:"<< getpid()<< endl;
counter++;
}
else{ // 该进程为父进程
cout<< "我是父进程,PID:"<< getpid()<< " 我获得的子进程PID为:"<< pid<< endl;
counter++;
}
if(pid==0){
cout<< "子进程中的counter:"<< counter<< endl;
}
else{
cout<< "父进程中的counter:"<< counter<< endl;
}
return 0;
}
我是父进程,PID:7825 我获得的子进程PID为:7826
父进程中的counter:1
我是子进程,PID:7826
子进程中的counter:1
- 分析
可以看到,在父进程中我们所获得的子进程的PID和在子进程中所获得的PID是一致的(7826)。同时,子进程虽然在创建时拷贝了父进程的变量,但是,由于子进程与父进程之间不存在内存共享,所以在子进程和父进程中分别修改counter并不能互相影响。另外,子进程和父进程的执行并没有先后顺序,一切都取决于操作系统的调度策略。
替换进程
- 函数
#include <unistd.h>
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 arg[]);
int execvp(const char* file, char* const arg[]);
int execvpe(const char* file, char* const arg[], char* const envp[]);
exec函数族同样可以创建进程,与fork不同的是,exec创建的进程会取代父进程,甚至会占有父进程的PID,实打实的进程界的“鬼上身”。exec函数族中常用的两个函数分别是execl和execlp,下面将通过实例详细介绍。
- execl
execl的第一个参数是执行文件的路径,第二个参数是该执行文件所需的输入(执行文件传参),和我们main(int argc, char** argv)中的argv部分一样,执行文件传参的第一个参数argv[0]一般是文件名(可以是任何字符,一般设置为文件名或命令名)。从第二个参数argv[1]起才是真正能够在执行文件中发挥作用的参数。当argv中出现NULL时则认为传参结束。一般情况下,执行文件都需要一个argv[0]文件名参数,也就是说argv至少需要一个非null参数(就是我们说的文件名),否则不能正确执行。
函数执行成功时无返回值,执行失败时返回-1.
$ 执行一个不需要传参的执行文件
#include <unistd.h>
using namespace std;
int main(int argc, char** argv){
execl("/bin/pwd", "pwd", NULL);
cout<< "=========================="<< endl;
return 0;
}
/home/torch/code/linux-c++/clion/cmake-build-debug
在这个例子中,我们执行了pwd命令,他是一个不需要参数的命令,因此我们的argv[0]为命令名,并没有实际作用,但必须要有;argv[1]为NULL,表示传参结束。另外,程序并没有输出一串"="号,说明程序被替换为了pwd命令,原来的程序已经不存在了。
$ 执行一个需要传参的执行文件
#include <unistd.h>
using namespace std;
int main(int argc, char** argv){
execl("/bin/ls", "ls -al", "-al", "/etc/passwd", NULL);
return 0;
}
/home/torch/code/linux-c++/clion/cmake-build-debug
在这个例子中,我们执行了ls -al /etc/passwd命令,所以我们的argv[0]为"ls -al"并没有实际作用,但必须要有;argv[1]为"-al",argv[2]为"/etc/passwd",argv[3]为NULL,表示传参结束。
$ 如果argv没有任何参数会发生什么?
我们首先写一个被执行程序,并把他编译为test.out,然后执行
/**
* file name: test.cpp
*/
#include <unistd.h>
#include <iostream>
using namespace std;
int main(int argc, char** argv){
cout<< "argc: "<< argc<< endl;
for(int i=0;i<argc;i++){
cout<< argv[i]<< endl;
}
return 0;
}
argc: 1
./test.out
系统自动传递了一个文件名参数给我们的程序,因此检测到一个参数,参数为文件名。
我们再写一个程序通过execl函数执行我们的test.out
#include <unistd.h>
#include <iostream>
using namespace std;
int main(int argc, char** argv){
execl("./test.out", NULL);
return 0;
}
argc:1
可以看到,当我们不传递任何参数给执行文件时,编译时会给我们警告,并且函数会自动传递一个空字符参数给我们的执行文件。我们对程序进行修改:
#include <unistd.h>
#include <iostream>
using namespace std;
int main(int argc, char** argv){
execl("./test.out", "test.out", NULL);
return 0;
}
argc:1
test.out
此时编译器不再发出警告,test.out获得了我们传递给他的"test.out"字符串。
- execlp
execlp的第一个参数是环境变量中的文件名,第二个参数和execl一致。相比于execl,execlp专门用来执行已经加入到环境变量中的程序。
$ 示例
#include <unistd.h>
#include <iostream>
using namespace std;
int main(int argc, char** argv){
execlp("pwd", "pwd", NULL);
return 0;
}
/home/torch/code/linux-c++/clion/cmake-build-debug