1. fork()函数 当程序调用fork()函数并返回成功之后,程序就将变成两个进程,调用fork()者为父进程,后来生成者为子进程。这两个进程将执行相同的程序文本,但却各自拥有不同的栈段、数据段以及堆栈拷贝。子进程的栈、数据以及栈段开始时是父进程内存相应各部分的完全拷贝,因此它们互不影响。
fork()函数在Linux中有两次返回,在父进程中返回子进程的pid,在子进程中返回0。
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <unistd.h> int main (void ) { pid_t pid; pid = fork(); if (pid>0 ){ printf ("I'm a parent\n" ); }else if (pid==0 ){ printf ("I'm a child\n" ); }else { perror ("fork" ); } }
输出
1 2 I'm a parent I'm a child
2. 进程等待之wait() & waitpid()
如果子进程已经退出,调用wait/waitpid会立即返回,并且释放资源,获得子进程退出信息
如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则父进程可能阻塞
如果不存在该子进程,则立即出错返回
子进程的退出是个异步事件(子进程可以在父进程运行的任何时刻终止)
2.1 wait() 1 2 3 4 5 6 7 8 头文件:#include <sys/wait.h> #include <sys/type.h> 原型 pid_t wait (int *status) 返回值: 成功:返回被等待进程(子进程)pid 失败:返回-1 参数:输出型参数,获取子进程退出状态,不关心则可以设置为NULL
2.2 waitpid() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 头文件: #include <sys/type.h> #include <sys/wait.h> 返回值: (1 )当正常返回的时候waitpid返回收集到的子进程的进程ID (2 )如果设置了选项WNOHANG,而调用中waitpid发现已经没 有已经可以退出的子进程可收集,则返回0 (3 )如果调用中出错,则返回-1 ,这时errno会被设置成相应的 值以指示错误所在 原型: pid_t waitpid (pid_t pid,int *status,int options) 1.pid pid =-1 ,等待任一个子进程,与wait等效 pid>0 ,等待其进程ID与pid相等的子进程 2. status WIFEXITED (status) : 若为正常终止子进程返回的状态,则为 真(此参数是查看进程是否是正常退出) WEXITSTATUS(status): 若WEXITSTATUS非零,提取子进程退 出码(查看进程的退出码) 3. options WNOHANG:若pid指定的子进程没有结束,则waitpid()函数 返回0 ,不予以等待,若正常结束,则返回该子进程的ID
3. 进程替换:exec 函数族 所谓exec函数族,其实有六种以exec开头的函数,统称exec函数:execl、execlp、execle、execv、execvp、execve。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。将当前进程的.text、.data替换为所要加载的程序的.text、.data,然后让进程从新的.text第一条指令开始执行,但进程ID不变。
3.1 exec函数族一般规律: exec函数一旦调用成功即执行新的程序,不返回。只有失败才返回,错误值-1。所以通常我们直接在exec函数调用后直接调用perror()和exit(),无需if判断。
exec函数族名字很相近,使用起来也很相近,它们的一般规律如下:
l (list) 命令行参数列表
p (path) 搜素file时使用path变量
v (vector) 使用命令行参数数组
e (environment) 使用环境变量数组,不使用进程原有的环境变量,设置新加载程序运行的环境变量
3.2 带p的exec函数 这类函数有:execlp,execvp
具体说明:表示第一个参数无需给出具体的路径,只需给出函数名即可,系统会在PATH环境变量中寻找所对应的程序,如果没找到的话返回-1。
1 int execvp (const char *file, char *const argv[]) ;
execvp()会从PATH 环境变量所指的目录中查找符合参数file 的文件名,找到后便执行该文件,然后将第二个参数argv传给该欲执行的文件。
4. 代码实现和结果 github链接:GitHub - Kakaluoto/MyShell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 #include <iostream> #include <vector> #include <cstring> #include <unistd.h> #include <sys/wait.h> #define cd_failed 0 #define cd_success 1 using namespace std;vector<string> argv; vector<string> history_cmds (100 ) ;string cmd; char * current_path = nullptr ;void argparse () ; void change_directory () ;void execute_cmd () ;int main () { current_path = getcwd (NULL , 0 ); while (1 ) { printf ("myshell:%s$ " , current_path); getline (cin, cmd); if (strcmp (cmd.data (), "exit" ) == 0 ) { delete current_path; return 0 ; } argparse (); execute_cmd (); argv.clear (); } } void argparse () { string param; for (char i:cmd + " " ) { if (i != ' ' ) { param += i; } else { argv.push_back (param); param = "" ; continue ; } } } int change_directory (int argc) { if (argc == 2 ) { if (chdir (argv[1 ].data ()) == 0 ) { current_path = getcwd (NULL , 0 ); if (current_path != nullptr ) { return cd_success; } else { cout << "No such file or directory!\n" ; return cd_failed; } } else { cout << "No such file or directory!\n" ; return cd_failed; } } else { cout << "too many arguments!\n" ; return cd_failed; } } void execute_cmd () { pid_t pid; int argc = argv.size (); char ** arguments = new char * [argc]; for (int i = 0 ; i < argc; i++) { arguments[i] = (char *) argv[i].data (); } if (strcmp (arguments[0 ], "cd" ) == 0 ) { change_directory (argc); } else { switch (pid = fork()) { case -1 : cout << "Failed to create subprocess!\n" ; return ; case 0 : execvp (arguments[0 ], arguments); cout << "invalid input command : \"" << arguments[0 ] << "\"" << endl; exit (1 ); default : { int status; waitpid (pid, &status, 0 ); int err = WEXITSTATUS (status); if (err)cout << "Error: " << strerror (err) << endl; } } } }
进入MyShell可执行文件所在目录执行如下命令即可
得到输出如下
1 myshell:/home/hy/myCppProject/cmake_demo/myshell$
执行ls
1 2 myshell:/home/hy/myCppProject/cmake_demo/myshell$ ls MyShell MyShell.cpp readme.md
执行ls -l:
1 2 3 4 5 6 myshell:/home/hy/myCppProject/cmake_demo/myshell$ ls -l total 176 -rwxrwxr-x 1 hy hy 159632 12月 18 23:51 MyShell -rw-rw-r-- 1 hy hy 3020 12月 19 16:08 MyShell.cpp -rw-rw-r-- 1 hy hy 13420 12月 9 22:43 readme.md
执行ps
1 2 3 4 5 myshell:/home/hy/myCppProject/cmake_demo/myshell$ ps PID TTY TIME CMD 135724 pts/0 00:00:00 bash 135766 pts/0 00:00:00 MyShell 135847 pts/0 00:00:00 ps
执行cd:
1 2 myshell:/home/hy/myCppProject/cmake_demo/myshell$ cd / myshell:/$
执行pwd和ls命令
1 2 3 4 5 myshell:/$ pwd / myshell:/$ ls bin cdrom etc lib lib64 media opt root sbin srv sys usr boot dev home lib32 libx32 mnt proc run snap swapfile tmp var