Ⅰ. 进程控制块PCB
task struct结构体:
- 进程id。系统中每个进程有唯一的id,在C语言中用pid_t类型表示,其实就是一个非负整数。
- 进程的状态,有运行、挂起、停止、僵尸等状态。
- 进程切换时需要保存和恢复的一些CPU寄存器。
- 描述虚拟地址空间的信息。
- 描述控制终端的信息。
- 当前工作目录(Current Working Directory)
- umask掩码。
- 文件描述符表,包含很多指向file结构体的针。
- 和信号相关的信息。
- 用户id和组id
- 控制终端、Session和进程组。
- 进程可以使用的资源上限(Resource Limit)
Ⅱ. 进程控制fork
fork的作用是根据一个现有的进程复制出一个新进程,原来的进程称为父进程(Parent Process),新进程称为子进程(Child Process)。系统中同时运行着很多进程,这些进程都是从最初只有一个进程(PID为1)开始一个一个复制出来的。可以用命令pstree
查看进程的家族图谱。
在Shell下输入命令可以运行一个程序,是因为Shell进程在读取用户输入的命令之后会调用fork复制出一个新的Shell进程。
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
- 返回值:调用失败返回-1并设置errno,调用成功的话:
- 在父进程中返回子进程的PID
- 在子进程中返回0
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
pid_t pid;
char* message;
int n;
pid = fork();
if (pid < 0) {
perror("fork failed");
exit(-1);
}
if (pid == 0) {
// 子进程执行代码块
message = "This is the child process\n";
n = 6;
} else {
// 父进程执行代码块
message = "This is the parent process\n";
n = 3;
}
// 下面的是父、子进程都会执行的代码块
while (n > 0) {
printf("%s", message);
n--;
sleep(1);
}
return 0;
}
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
pid_t getppid(void);
fork在子进程中返回0,子进程可以调用getpid函数得到自己的进程id,也可以调用getppid函数得到父进程的id。在父进程中用getpid可以得到自己的进程id,然而要想得到子进程的id,只有将fork的返回值记录下来,别无它法。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
pid_t pid;
int n;
pid = fork();
if (pid < 0) {
perror("fork failed");
exit(-1);
}
if (pid == 0) {
// 子进程执行代码块
n = 6;
while (n) {
// 当父进程结束之后,子进程会成为孤儿进程,被其它进程接管,此时ppid==1
printf("This is the child process. pid=%d, ppid=%d\n", getpid(), getppid());
n--;
sleep(1);
}
} else {
// 父进程执行代码块
n = 3;
while (n) {
printf("This is the parent process. pid=%d, ppid=%d\n", getpid(), getppid());
n--;
sleep(1);
}
}
// 下面的是父、子进程都会执行的代码块
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
/**
* @brief 创建10个子进程,并打印它们的pid和ppid
*
* @return int
*/
int main() {
pid_t pid;
int n = 10;
while (n) {
pid = fork();
if (pid < 0) {
perror("fork failed");
exit(-1);
}
if (pid == 0) {
// 子进程
printf("This is the child process. pid=%d, ppid=%d\n", getpid(), getppid());
break;
}
// 父进程会执行下面的代码,子进程因为break了所以不会执行
n--;
}
sleep(1); // 等一下,免得父进程死得比子进程早,让子进程成为孤儿进程
return 0;
}
Ⅲ. exec函数族
当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...
/* (char *) NULL */);
int execlp(const char *file, const char *arg, ...
/* (char *) NULL */);
int execle(const char *path, const char *arg, ...
/*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
char *const envp[]);
#include <unistd.h>
int execve(const char *filename, char *const argv[], char *const envp[]);
这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回,如果调用出错则返回-1,所以exec函数只有出错的返回值而没有成功的返回值。
1.环境变量表environ
extern char **environ;
exec系统调用执行新程序时会把命令行参数和环境变量表传递给main函数。和命令行参数argv类似,环境变量表也是一个字符串数组
name=value
可以用getenv函数,查询对应name的value
#include <stdlib.h>
char *getenv(const char *name);
可以用setenv函数,将环境变量name的值设置为value
#include <stdlib.h>
int setenv(const char *name, const char *value, int rewrite);
void unsetenv(const char *name);
#include <stdio.h>
#include <unistd.h>
/**
* @brief 打印环境变量
*
* @return int
*/
int main() {
extern char **environ;
// 读取environ直到遇到NULL(哨兵)
// for (int i = 0; environ[i] != NULL; i++) {
// printf("%s\n", environ[i]);
// }
while (*environ) {
printf("%s\n", *environ);
environ++;
}
return 0;
}
/**
* @brief 获取和设置PATH环境变量
*
* @return int
*/
int main() {
printf("PATH = [%s]\n", getenv("PATH"));
setenv("PATH", "hello", 1); // 改的只是当前进程的环境变量,因为这个进程是由bash fork出来的子进程,修改环境变量不影响父进程
printf("PATH = [%s]\n", getenv("PATH"));
return 0;
}
2.exec函数族
- 带有字母l (表示list)的exec函数要求将新程序的每个命令行参数都当作一个参数传给它,命令行参数的个数是可变的,最后一个可变参数应该是
NULL
,起sentinel的作用。 - 对于带有字母v (表示vector)的函数,则应该先构造一个指向各参数的指针数组,然后将该数组的首地址当作参数传给它,数组中的最后一个指针也应该是
NULL
,像main函数的argv参数或者环境变量表environ
一样。 - 不带字母p (表示path)的exec函数第一个参数必须是程序的相对路径或绝对路径,例如
"/bin/ls"
或"./a. out"
对于带字母p的函数:如果参数中包含/
,则将其视为路径名。否则视为不带路径的程序名,在PATH
环境变量的日录列表中搜索这个程序。 - 对于以e (表示environment)结尾的exec函数,可以把一份新的环境变量表其他exec函数仍使用当前的环境变量表执行新程序。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
/**
* @brief 实现exec族函数调用命令
*
* @return int
*/
int main() {
// execlp("ls", "ls", "-a", "-l", NULL); // arg[0]其实写什么都一样,因为ls解析参数是从arg[1]开始,NULL起到哨兵作用
char *arg[] = {"ls", "-a", "-l", NULL};
execvp("ls", arg);
perror("exec");
exit(-1);
}
#include <ctype.h>
#include <stdio.h>
/**
* @brief 将输入字符转为大写形式
*
* @return int
*/
int main() {
int ch;
while (~(ch = getchar())) {
putchar(toupper(ch));
}
return 0;
}
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
/**
* @brief 实现流的重定向
*
* @param argc
* @param argv
* @return int
*/
int main(int argc, char* argv[]) {
if (argc < 3) {
printf("usage:cmd + input_filename + output_filename\n");
exit(-1);
}
int fd = open(argv[1], O_RDONLY);
if (fd < 0) {
perror("read");
exit(-2);
}
dup2(fd, 0); // 标准输入重定向
close(fd);
fd = open(argv[2], O_WRONLY | O_CREAT, 0644);
if (fd < 0) {
perror("write");
exit(-3);
}
dup2(fd, 1); // 标准输出重定向
close(fd);
execl("./upper", "./upper", NULL); // 调用./upper命令
perror("exec");
exit(-4);
}
评论 (0)