简单的进程控制
进程创建
fork函数和vfork函数
fork():子进程返回值是0,父进程返回值是子进程的pid;写时拷贝;子进程具有独立的地址空间
vfork():父子进程共享地址空间,必然是子进程先运行
进程终止
正常终止以及异常退出
对于_exit()函数以及exit()的辨析:执行exit()函数时先执行用户定义的清理函数,关闭所有打开的流,写入缓存数据,调用
_exit()函数。
return()退出等同于exit()
进程等待
wait和waitpid
当子进程正在运行的过程中,执行wait活着waitpid,可能导致的进程阻塞
要实现一个自主shell ,我们需要循环执行以下过程:
- 读取命令
- 解析命令
- 创建子进程
- 替换子进程
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <ctype.h>
int argc = 0;
char* argv[8] = {};
void do_parse(char* buf){ // 字符串解析
argc = 0;
int i = 0;
int status = 0;
for(; buf[i]!=0; i++){
if(!isspace(buf[i])&&status == 0){ //如果是字符并且是从空格到字符
argv[argc++] = buf + i;
status = 1;
}else if(isspace(buf[i])){ // 如果是字符到空格
buf[i] = 0;
status = 0;
}
}
}
void do_shell(){
pid_t pid = fork(); // 创建子进程
if(pid == -1){
perror("fork");
return;
}
else if(pid > 0)
wait(NULL); // 父进程等待子进程结束
else{ // 子进程进行进程替换
if(execvp(argv[0],argv) == -1)
perror("execvp"),exit(1);
}
}
int main(){
char buf[1024] = {};
while(1){
memset(buf, 0x00, sizeof(buf));
printf("shell >");
scanf("%[^\n]", buf); // 命令行读取
scanf("%*c");
if(strcmp(buf, "exit") == 0)
break;
do_parse(buf);
do_shell();
}
}
利用回调函数封装fork wait execvp 等函数, 编写一个process_create函数:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int process_create(int(*func)(), const char *file, char *argv[])
{
int ret = 0;
pid_t pid = fork();
if(pid == 0)
{
//子进程
ret = func(file, argv);
if(ret == -1)
{
printf("调用execvp失败\n");
perror("func");
_exit(-1);
}
}
else
{
int st;
pid_t ret = wait(&st);
if(ret == -1)
{
perror("wait");
exit(-1);
}
}
return 0;
}
int main()
{
char *argv1[] = {"ls"};
char *argv2[] = {"ls", "-al", "/etc/passwd", 0};
process_create(execvp, *argv1, argv2);
return 0;
}
popen/system
popen() 创建一个管道,通过fork或者invoke一个子进程,然后执行command。返回值在标准IO流中,由于是在管道之中,因此数据流是单向的,command只能产生stdout或者读取stdin,因此type只有两个值:‘w’或‘r’。r表示command从管道中读取数据流,而w表示command的stdout输出到管道中。command无法同时读取和输出。popen返回该FIFO数据流的指针。
popen可以控制程序的输入或者输出,而system的功能明显要弱一点,比如无法将ls的结果用到程序中。如果不需要使用到程序的I/O数据流,那么system是最方便的。