目录
预备知识
fork
进程等待
wait
waitpid
环境变量
概念
分类
常见的环境变量及其用途
环境变量的查看与设置
exec系列
函数解释
命名理解
简单shell
预备知识
fork
fork
是 Linux 和许多其他类 Unix 系统中的一个重要系统调用,它用于创建一个新的进程,这个新进程是当前进程的复制品,称为子进程。子进程会获得父进程当前状态的副本,包括父进程的内存布局、环境变量、打开的文件描述符等。但是,子进程和父进程是两个独立的执行线程,它们可以独立地运行程序的不同部分。
以下是关于 fork
的一些关键点:
- 复制:子进程是父进程的一个副本。但是,这种复制是“写时复制”(Copy-On-Write, COW)的。这意味着,如果子进程和父进程都不修改它们共享的内存区域,那么这些区域实际上不会被复制,从而节省内存和 CPU 时间。
- 返回值:
fork
系统调用在父进程中返回新创建的子进程的进程 ID(PID),而在子进程中返回 0。这是区分父进程和子进程的一个常用方法。 - 资源分配:子进程会获得其父进程所拥有资源的副本,如文件描述符、环境变量等。但是,这些资源并不一定是物理上独立的;它们通常是通过引用计数或其他机制来共享的。
- 并发:由于子进程和父进程是两个独立的执行线程,因此它们可以并发地执行不同的任务。这使得
fork
成为实现多进程并发和并行处理的重要工具。 - 进程间通信:由于子进程和父进程是独立的,因此它们之间需要进行通信以共享数据或协调行为。这可以通过各种进程间通信(IPC)机制来实现,如管道、消息队列、信号量、共享内存等。
- 终止和等待:当子进程终止时,它会留下一个僵尸进程(Zombie Process),直到其父进程调用
wait
或waitpid
系统调用来获取其终止状态并释放其占用的资源。
需要注意的是,虽然 fork
是一个强大的工具,但它也有一些限制和开销。例如,由于需要复制父进程的状态,因此 fork
的开销通常比创建线程要大。此外,由于子进程和父进程是独立的,因此它们之间的通信和同步也需要额外的开销和复杂性。因此,在选择使用 fork
还是其他并发机制(如线程或协程)时,需要根据具体的应用场景和需求进行权衡。
进程等待
wait
- 功能:
wait
函数用于使父进程等待其任意一个子进程结束,并回收该子进程的资源。当子进程结束时,内核会向父进程发送SIGCHLD信号,父进程可以通过wait
函数来响应这个信号并获取子进程的状态。 - 函数原型:
pid_t wait(int *wstatus);
- 参数
wstatus
是一个指向整数的指针,用于存储子进程的退出状态。如果父进程不关心子进程的退出状态,可以将此参数设为NULL。 - 返回值是已结束子进程的进程ID(PID),如果出现错误则返回-1。
- 参数
- 特点:
wait
函数会阻塞父进程的执行,直到有一个子进程结束。如果调用wait
时子进程已经结束,则wait
会立即返回子进程的结束状态值。
waitpid
- 函数原型:#include #include pid_t wait(int*status); 返回值: 成功返回被等待进程pid,失败返回-1
- 返回值:
- 当正常返回的时候waitpid返回收集到的子进程的进程ID;
- 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
- 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
- 参数:
- pid:
- Pid=-1,等待任一个子进程。与wait等效。
- Pid>0.等待其进程ID与pid相等的子进程。
- status:
- WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
- WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
- options:
- WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进 程的ID。
- pid:
环境变量
概念
- 定义:环境变量是系统预定义的参数,相当于全局变量,存在于所有的shell中,具有继承性。它们可存储有关shell会话和工作环境的信息。
- 作用:环境变量允许在内存中存储数据,以便运行在shell上的程序和脚本访问。这些数据可以用来识别用户、系统、Shell的特性以及任何其它需要存储的数据。
分类
- 按作用范围分:
- 系统环境变量:公共的,对全部的用户都生效。
- 用户环境变量:用户私有的、自定义的个性化设置,只对该用户生效。
- 按生存周期分:
- 永久环境变量:在环境变量脚本文件中配置,用户每次登录时会自动执行这些脚本,相当于永久生效。
- 临时环境变量:使用时在Shell中临时定义,退出Shell后失效。
常见的环境变量及其用途
- PATH:指定命令的搜索路径,由冒号分隔的目录列表,用于告诉系统在哪里可以找到可执行文件。
- HOME:指定用户的主工作目录,即用户登录到Linux系统时,默认的目录。
- SHELL:当前Shell的路径,通常指向用户当前使用的shell解析器,如
/bin/bash
。 - LD_LIBRARY_PATH:C/C++语言动态链接库文件搜索的目录,对C/C++程序员来说非常重要。
- CLASSPATH:JAVA语言库文件搜索的目录,对JAVA程序员来说非常重要。
环境变量的查看与设置
- 查看:
- 使用
env
命令可以查看当前用户全部的环境变量。 - 使用
echo $环境变量名
可以查看特定环境变量的值。
- 使用
- 设置:
- 使用
export
命令可以设置新的环境变量或修改现有环境变量的值。例如,export PATH=$PATH:/new/directory
。 - 如果希望环境变量永久生效,需要在登录脚本文件中(如
/etc/profile
、/etc/profile.d/
、~/.bash_profile
、~/.bashrc
等)进行配置
- 使用
exec系列
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
nt execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]); i
nt execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
这些函数的主要目的是在当前进程的上下文中执行一个新程序,从而替换当前进程的映像。它们允许程序在运行时加载新的可执行文件,实现动态性和灵活性。
函数解释
- 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
- 如果调用出错则返回-1
- 所以exec函数只有出错的返回值而没有成功的返回值
命名理解
- l(list) : 表示参数采用列表
- v(vector) : 参数用数组
- p(path) : 有p自动搜索环境变量PATH
- e(env) : 表示自己维护环境变量
简单shell
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<ctype.h>
#include<unistd.h>
#include<fcntl.h>
#include<errno.h>
#include<sys/wait.h>
#define SIZE 512
#define ZERO '\0'
#define SEP " "
#define NUM 32
#define SkipPath(p) do{p+=strlen(p)-1;while(*p!='/')p--;}while(0)
#define SkipSpace(cmd,pos) do{ \while(1){\if(isspace(cmd[pos]))\pos++;\else break;\}}while(0)
#define none_type 0
#define in_type 1
#define out_type 2
#define app_type 3
int redir_type=none_type;
char* filename=NULL;char* gArgv[NUM];
int lastcode=0;
void Die()
{exit(1);
}
const char* GetHostname()
{const char* hostname=getenv("HOSTNAME");if(hostname==NULL)return "/";return hostname;
}
const char* GetUsername()
{const char* user=getenv("USER");if(user==NULL)return "none";return user;
}
const char* Getcwd()
{const char* cwd=getenv("PWD");if(cwd==NULL) return "none";return cwd;
}
void MakeCommmandLine()
{char line[SIZE];const char* hostname=GetHostname();const char* username=GetUsername();const char* cwd=Getcwd();SkipPath(cwd);snprintf(line,sizeof(line),"[%s@%s %s]> ",username,hostname,strlen(cwd)==1?"/":cwd+1);printf("%s",line);fflush(stdout);
}
int GetUserCommad(char commad[],size_t n)
{char* s=fgets(commad,n,stdin);if(s==NULL)return -1;commad[strlen(commad)-1]=ZERO;return strlen(commad);}
void CheckRedir(char cmd[])
{int pos=0;int end=strlen(cmd);while(pos<end){if(cmd[pos]=='>'){if(cmd[pos+1]=='>'){cmd[pos++]=0;pos++;redir_type=app_type;SkipSpace(cmd,pos);filename=cmd+pos;}else {cmd[pos++]=0;redir_type=out_type;SkipSpace(cmd,pos);filename=cmd+pos; }}else if(cmd[pos]=='<'){cmd[pos++]=0;redir_type=in_type;SkipSpace(cmd,pos);filename=cmd+pos;}else{pos++;}}
}
void SplitCommad(char command[])
{gArgv[0]=strtok(command,SEP);int index=1;while((gArgv[index++]=strtok(NULL,SEP)));
}
void ExecuteCommand()
{pid_t id=fork();if(id<0)Die();else if (id==0){//子进程if(filename!=NULL){if(redir_type==in_type){int fd=open(filename,O_RDONLY);dup2(fd,0);}else if (redir_type==out_type){int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);dup2(fd, 1); }else if (redir_type==app_type){int fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);dup2(fd, 1);}}execvp(gArgv[0],gArgv);exit(errno);}else{//父进程int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid > 0){lastcode = WEXITSTATUS(status);if(lastcode != 0) printf("%s:%s:%d\n", gArgv[0], strerror(lastcode), lastcode);} }
}
int main()
{int quit=0;while(!quit){redir_type=none_type;filename=NULL;//创建一个自己的命令行MakeCommmandLine();//获取用户命令char usercommad[SIZE];int n=GetUserCommad(usercommad,sizeof(usercommad));if(n<=0)return 1;CheckRedir(usercommad);//测试一下// printf("cmd: %s\n", usercommad);// printf("redir: %d\n", redir_type);// printf("filename: %s\n", filename);//分割字符串SplitCommad(usercommad);//执行命令ExecuteCommand();}return 0;
}