Unix高级编程:环境变量(续)、管道、文件重定向、信号基础
2017-01-15 22:53
639 查看
一、环境变量(续)
"setenv"(3)
#include <stdlib.h>
int setenv(const char *name, const char *value, int overwrite);
功能:改变或增加一个环境变量
参数:
"name" 要添加或改变的环境变量名
"value" 环境变量的值
"overwrite" 环境变量存在,值取 0 环境变量值不变;值取非 0 环境变量值被改为value
返回值:
成功 - 返回 0
失败 - 返回 -1,errno被设置
"unsetenv"(3)
int unsetenv(const char *name);
功能:删除环境变量(环境变量不存在,函数执行成功,环境变量不变)
参数:"name" 环境变量名
返回值:
成功 - 返回 0
失败 - 返回 -1,errno被设置
/*举例验证,为进程添加/获取/打印/改变/再打印/删除环境变量,setenv.c*/
#include <stdio.h>
#include <stdlib.h>
int main(void/*int argc, char *argv[], char *envp[]*/) {
setenv("name", "laowang", 0);//设置环境变量
printf("name = %s\n", getenv("name"));
setenv("name", "gebi", 0);//第三个参数为0环境变量值不变
printf("name = %s\n", getenv("name"));
setenv("name", "gebi", 1);//修改环境变量的值
printf("name = %s\n", getenv("name"));
unsetenv("name");//删除环境变量
if(getenv("name"))
printf("name = %s\n", getenv("name"));
return 0;
}
/*综合execle和环境变量使用演示,exec_env.c*/
sudo mv ../1208/myenv /bin 移动文件
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
const char *ps_envp[] = {"name=laowang", NULL};
execle("/bin/myenv", "myenv", NULL, ps_envp);//添加环境变量
return 0;
}
二、管道 //鸟哥私房菜
管道分为"有名管道"和"无名管道"两种。
"无名管道",单向的(双向需创建两个管道)
"pipe"(2) //无名管道,应用于有亲缘关系的两个进程间的通讯
#include <unistd.h>
int pipe(int pipefd[2]);
功能:创建管道
参数:"pipefd[2]" 含两个整形数元素的数组
pipefd[0] 负责写数据的进程关闭管道的"读端文件描述符"
pipefd[1] 负责读数据的进程关闭管道的"写端文件描述符"
返回值:
成功 - 返回 0
失败 - 返回 -1,errno被设置
/*举例验证,创建管道实现进程间的通信,pipe.c*/
父进程: 子进程
1)pipe创建管道 --
2)fork创建子进程 子进程被创建
3)close(pipefd[0]); close(pipefd[1]);
4)向管道写入内容 从管道里读取内容/输出读取到的内容
5)wait(NULL); 退出
6)退出
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void) {
int fd[2];
char buf[20] = {0};
int r = pipe(fd);//创建管道
if(r == -1) {
perror("pipe");
return 1;
}
pid_t pid = fork();//创建子进程
if(pid == -1) {
perror("fork");
return 2;
}
if(pid == 0) { //子进程执行的代码
close(fd[1]);//关闭子进程写端,留出读端
int size = read(fd[0], buf, 12);
write(1, buf, size);//标准输出到显示器
write(1, "\n", 2);
exit(0);
} else { //父进程执行的代码
close(fd[0]);//关闭父进程读端,留出写端
write(fd[1], "hello,world!", 12);
wait(NULL); //不需要知道子进程死亡状态,NULL
}
return 0;
}
"有名管道" //用的少,了解即可
有名管道通过文件实现进程间的通讯,两个进程不必具有亲缘关系。
"mkfifo"(3)
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
功能:创建一个FIFO文件,用于进程间的通信
参数:
"pathname" 文件的路径/文件名
"mode" 文件的权限
返回值:
成功 - 返回 0
失败 - 返回 -1,errno被设置
/*举例验证,实现进程间的通信,通过有名管道实现 fifoA.c fifoB.c*/
/*fifoA.c*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(void) {
int r = mkfifo("hello", 0664);//创建有名管道
if(r == -1) {
perror("mkfifo");
return 1;
}
int fd = open("hello", O_WRONLY);//打开管道文件
if(fd == -1) {
perror("open");
return 2;
}
write(fd, "hello,world!", 12);
sleep(5);
close(fd);
return 0;
}
/*fifoB.c*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(void) {
char buf[20] = {0};
int fd = open("hello", O_RDONLY);//打开管道文件
if(fd == -1) {
perror("open");
return 2;
}
int size = read(fd, buf, 20);
write(1, buf, size);
write(1, "\n", 2);
close(fd);
return 0;
}
三、文件重定向的实现 //鸟哥私房菜
运用进程管理、文件管理完成重定向的实现
"EOF" 使用库函数操作文件的时候,文件的结束标志
"toupper"(3)
#include <ctype.h>
int toupper(int c);
int tolower(int c);
功能:转换字母大小写格式 upper大写,lower小写
参数:"c" 字符
返回值:
成功 - 返回字符的值(ASCII码)
从键盘输入"ctrl + D" 代表了 "EOF" 文件结束。
/*实现代码参见 upper.c*/
#include <stdio.h>
#include <ctype.h>
int main(void) {
int ch;
while((ch = getchar()) != EOF) { //EOF:文件结束标识
putchar(toupper(ch));//全部输入转换大写
}
return 0;
}
"标准输入重定向"
/*文件输入重定向,代码参见 wrapper.c*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int fd = open(argv[1], O_RDONLY);//打开文件
if(fd == -1) {
perror("open");
return 1;
}
dup2(fd, STDIN_FILENO);//将fd文件描述符拷贝给标准输入
close(fd);//关闭文件
execl("./upper", "upper", NULL);//在当前进程里加载upper的映像
perror("execl");
return 0;
}
补充:
"bash"
bash下有两种命令:
内部命令:命令的实现代码写在bash程序中(不许要fork创建子进程)
外部命令:命令的实现代码独立于bash程序代码(fork-execl-子进程)
查看命令的类型:type 命令 //如type ls 如type touch 如type cd
四、vfork的使用
vfork创建子进程以后,子进程和父进程共享PCB
vfork一般用于创建子进程后,在子进程中加载进程映像,执行新的程序。开辟了一条新的执行路线,并没有分配资源。
/*举例验证,子进程有父进程的进程映射的一份拷贝, fork_pcb.c */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(void) {
int var_i = 30;
pid_t pid = fork();
if(pid < 0) {
perror("fork");
return 1;
}
if(pid == 0) {
printf("child var_i_1 = %d\n", var_i);//30
var_i = 10;
printf("child var_i_2 = %d\n", var_i);//10
exit(1);
} else {
wait(NULL);
printf("parent var_i_1 = %d\n", var_i);//30
var_i = 80;
printf("parent var_i_2 = %d\n", var_i);//80
}
return 0;
}
"vfork"(2)
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);
功能:创建一个子进程,并且阻塞父进程
参数:void 不需要参数
返回值:
成功 - 返回 0 是子进程,非 0 是父进程
失败 - 返回 -1,errno被设置
/* 代码参见 vfork.c */
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(void) {
pid_t pid = vfork();//使用vfork创建子进程
if(pid < 0) {//vfork出错
perror("vfork");
return 1;
}
if(pid == 0) { //子进程执行的代码
//子进程退出的时候不要使用exit和return
printf("child pid %d\n", getpid());
sleep(3);
_exit(0);
} else { //父进程执行的代码
printf("parent pid %d\n", getpid());
}
return 0;
}
/* 举例验证父进程局部变量的问题,vfork1.c*/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(void) {
int cnt = 10;
pid_t pid = vfork();
if(pid < 0) {
perror("vfork");
return 1;
}
if(pid == 0) { //子进程执行代码
printf("cnt = %d\n", cnt);//10
cnt++;
printf("cnt++ = %d\n", cnt);//11
_exit(0);
} else { //父进程执行代码
printf("cnt_p = %d\n", cnt);//11
cnt++;
printf("cnt_p++ = %d\n", cnt);//12
}
return 0;
}
进程小结:
1)进程和程序的区别
2)每个进程都有自己的pid,还有自己的PCB
3)使用fork和vfork创建子进程
4)进程的同步(收尸:wait等待所有子进程/waitpid等待指定子进程)
5)加载新的程序映像
6)管道(无名管道,亲缘关系的进程通信-缓冲/有名管道,所有进程通信-文件)
7)环境变量(都是从父进程继承下来的)
8)使用文件和进程实现重定向的功能
五、信号的基础
"信号",就是软中断
"软中断",软件模拟中断的实现
"终端",中断处理程序
系统为我们提供的信号:
kill -l //查看系统提供的信号列表,总共提供了64个信号(实际62,无32,33)
1~31 不可靠信号(非实时信号)
34~64 可靠信号(实时信号)
信号有信号的名字和信号的编号。如 2) SIGINT
2) SIGINT 用户产生中断符(Ctrl + C)
3) SIGQUIT 用户产生退出符(Ctrl + \)
10) SIGUSR1 进程自定义的信号
12) SIGUSR2 进程自定义的信号
11) SIGSEGV 当程序运行产生段错误"segment fault"的时候,无效内存访问
9) SIGKILL 杀死进程
信号的处理过程:
"信号的产生" "信号阻塞" "信号的捕获" "信号的处理程序"
"信号的捕获"
当信号到达进程的时候,进程就捕获到了信号。进程需要对信号进行处理。
信号处理分为三种:
1)缺省处理("SIG_DFL"):大部分信号的缺省处理是使进程终止
2)信号忽略处理("SIG_IGN"):不用对到达的信号做任何的处理
3)用户自定义处理("自定"):信号的处理程序由用户自己定义
进程"默认从父进程继承信号处理"程序。
改变进程对信号的默认处理为自定处理。这时候需要用户向系统注册信号的处理函数。
当前进程怎么向系统注册信号的处理函数:
使用"signal"函数向系统注册。
"signal"(2)
#include <signal.h>
typedef void (*sighandler_t)(int); //函数指针(回调)
sighandler_t signal(int signum, sighandler_t handler);
功能:设置handler为信号signum的处理函数
参数:
"signum" 信号的编号或者信号名字
"handler" 信号的处理函数
返回值:
成功 - 返回以前的这个信号的处理函数 //以前的没用,可不用返回
失败 - 返回SIG_ERR,函数执行错误
/* 举例验证,用户自定义信号处理函数的注册,代码参见 signal.c*/
#include <stdio.h>
#include <signal.h>
void handler(int signum) { //用户定义的信号处理函数
printf("hhhhhhh\n");
return;
}
int main(void) {
signal(2, handler); //向进程注册用户自定义的信号处理函数
while(1);
return 0;
}
"setenv"(3)
#include <stdlib.h>
int setenv(const char *name, const char *value, int overwrite);
功能:改变或增加一个环境变量
参数:
"name" 要添加或改变的环境变量名
"value" 环境变量的值
"overwrite" 环境变量存在,值取 0 环境变量值不变;值取非 0 环境变量值被改为value
返回值:
成功 - 返回 0
失败 - 返回 -1,errno被设置
"unsetenv"(3)
int unsetenv(const char *name);
功能:删除环境变量(环境变量不存在,函数执行成功,环境变量不变)
参数:"name" 环境变量名
返回值:
成功 - 返回 0
失败 - 返回 -1,errno被设置
/*举例验证,为进程添加/获取/打印/改变/再打印/删除环境变量,setenv.c*/
#include <stdio.h>
#include <stdlib.h>
int main(void/*int argc, char *argv[], char *envp[]*/) {
setenv("name", "laowang", 0);//设置环境变量
printf("name = %s\n", getenv("name"));
setenv("name", "gebi", 0);//第三个参数为0环境变量值不变
printf("name = %s\n", getenv("name"));
setenv("name", "gebi", 1);//修改环境变量的值
printf("name = %s\n", getenv("name"));
unsetenv("name");//删除环境变量
if(getenv("name"))
printf("name = %s\n", getenv("name"));
return 0;
}
/*综合execle和环境变量使用演示,exec_env.c*/
sudo mv ../1208/myenv /bin 移动文件
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
const char *ps_envp[] = {"name=laowang", NULL};
execle("/bin/myenv", "myenv", NULL, ps_envp);//添加环境变量
return 0;
}
二、管道 //鸟哥私房菜
管道分为"有名管道"和"无名管道"两种。
"无名管道",单向的(双向需创建两个管道)
"pipe"(2) //无名管道,应用于有亲缘关系的两个进程间的通讯
#include <unistd.h>
int pipe(int pipefd[2]);
功能:创建管道
参数:"pipefd[2]" 含两个整形数元素的数组
pipefd[0] 负责写数据的进程关闭管道的"读端文件描述符"
pipefd[1] 负责读数据的进程关闭管道的"写端文件描述符"
返回值:
成功 - 返回 0
失败 - 返回 -1,errno被设置
/*举例验证,创建管道实现进程间的通信,pipe.c*/
父进程: 子进程
1)pipe创建管道 --
2)fork创建子进程 子进程被创建
3)close(pipefd[0]); close(pipefd[1]);
4)向管道写入内容 从管道里读取内容/输出读取到的内容
5)wait(NULL); 退出
6)退出
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void) {
int fd[2];
char buf[20] = {0};
int r = pipe(fd);//创建管道
if(r == -1) {
perror("pipe");
return 1;
}
pid_t pid = fork();//创建子进程
if(pid == -1) {
perror("fork");
return 2;
}
if(pid == 0) { //子进程执行的代码
close(fd[1]);//关闭子进程写端,留出读端
int size = read(fd[0], buf, 12);
write(1, buf, size);//标准输出到显示器
write(1, "\n", 2);
exit(0);
} else { //父进程执行的代码
close(fd[0]);//关闭父进程读端,留出写端
write(fd[1], "hello,world!", 12);
wait(NULL); //不需要知道子进程死亡状态,NULL
}
return 0;
}
"有名管道" //用的少,了解即可
有名管道通过文件实现进程间的通讯,两个进程不必具有亲缘关系。
"mkfifo"(3)
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
功能:创建一个FIFO文件,用于进程间的通信
参数:
"pathname" 文件的路径/文件名
"mode" 文件的权限
返回值:
成功 - 返回 0
失败 - 返回 -1,errno被设置
/*举例验证,实现进程间的通信,通过有名管道实现 fifoA.c fifoB.c*/
/*fifoA.c*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(void) {
int r = mkfifo("hello", 0664);//创建有名管道
if(r == -1) {
perror("mkfifo");
return 1;
}
int fd = open("hello", O_WRONLY);//打开管道文件
if(fd == -1) {
perror("open");
return 2;
}
write(fd, "hello,world!", 12);
sleep(5);
close(fd);
return 0;
}
/*fifoB.c*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(void) {
char buf[20] = {0};
int fd = open("hello", O_RDONLY);//打开管道文件
if(fd == -1) {
perror("open");
return 2;
}
int size = read(fd, buf, 20);
write(1, buf, size);
write(1, "\n", 2);
close(fd);
return 0;
}
三、文件重定向的实现 //鸟哥私房菜
运用进程管理、文件管理完成重定向的实现
"EOF" 使用库函数操作文件的时候,文件的结束标志
"toupper"(3)
#include <ctype.h>
int toupper(int c);
int tolower(int c);
功能:转换字母大小写格式 upper大写,lower小写
参数:"c" 字符
返回值:
成功 - 返回字符的值(ASCII码)
从键盘输入"ctrl + D" 代表了 "EOF" 文件结束。
/*实现代码参见 upper.c*/
#include <stdio.h>
#include <ctype.h>
int main(void) {
int ch;
while((ch = getchar()) != EOF) { //EOF:文件结束标识
putchar(toupper(ch));//全部输入转换大写
}
return 0;
}
"标准输入重定向"
/*文件输入重定向,代码参见 wrapper.c*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int fd = open(argv[1], O_RDONLY);//打开文件
if(fd == -1) {
perror("open");
return 1;
}
dup2(fd, STDIN_FILENO);//将fd文件描述符拷贝给标准输入
close(fd);//关闭文件
execl("./upper", "upper", NULL);//在当前进程里加载upper的映像
perror("execl");
return 0;
}
补充:
"bash"
bash下有两种命令:
内部命令:命令的实现代码写在bash程序中(不许要fork创建子进程)
外部命令:命令的实现代码独立于bash程序代码(fork-execl-子进程)
查看命令的类型:type 命令 //如type ls 如type touch 如type cd
四、vfork的使用
vfork创建子进程以后,子进程和父进程共享PCB
vfork一般用于创建子进程后,在子进程中加载进程映像,执行新的程序。开辟了一条新的执行路线,并没有分配资源。
/*举例验证,子进程有父进程的进程映射的一份拷贝, fork_pcb.c */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(void) {
int var_i = 30;
pid_t pid = fork();
if(pid < 0) {
perror("fork");
return 1;
}
if(pid == 0) {
printf("child var_i_1 = %d\n", var_i);//30
var_i = 10;
printf("child var_i_2 = %d\n", var_i);//10
exit(1);
} else {
wait(NULL);
printf("parent var_i_1 = %d\n", var_i);//30
var_i = 80;
printf("parent var_i_2 = %d\n", var_i);//80
}
return 0;
}
"vfork"(2)
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);
功能:创建一个子进程,并且阻塞父进程
参数:void 不需要参数
返回值:
成功 - 返回 0 是子进程,非 0 是父进程
失败 - 返回 -1,errno被设置
/* 代码参见 vfork.c */
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(void) {
pid_t pid = vfork();//使用vfork创建子进程
if(pid < 0) {//vfork出错
perror("vfork");
return 1;
}
if(pid == 0) { //子进程执行的代码
//子进程退出的时候不要使用exit和return
printf("child pid %d\n", getpid());
sleep(3);
_exit(0);
} else { //父进程执行的代码
printf("parent pid %d\n", getpid());
}
return 0;
}
/* 举例验证父进程局部变量的问题,vfork1.c*/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(void) {
int cnt = 10;
pid_t pid = vfork();
if(pid < 0) {
perror("vfork");
return 1;
}
if(pid == 0) { //子进程执行代码
printf("cnt = %d\n", cnt);//10
cnt++;
printf("cnt++ = %d\n", cnt);//11
_exit(0);
} else { //父进程执行代码
printf("cnt_p = %d\n", cnt);//11
cnt++;
printf("cnt_p++ = %d\n", cnt);//12
}
return 0;
}
进程小结:
1)进程和程序的区别
2)每个进程都有自己的pid,还有自己的PCB
3)使用fork和vfork创建子进程
4)进程的同步(收尸:wait等待所有子进程/waitpid等待指定子进程)
5)加载新的程序映像
6)管道(无名管道,亲缘关系的进程通信-缓冲/有名管道,所有进程通信-文件)
7)环境变量(都是从父进程继承下来的)
8)使用文件和进程实现重定向的功能
五、信号的基础
"信号",就是软中断
"软中断",软件模拟中断的实现
"终端",中断处理程序
系统为我们提供的信号:
kill -l //查看系统提供的信号列表,总共提供了64个信号(实际62,无32,33)
1~31 不可靠信号(非实时信号)
34~64 可靠信号(实时信号)
信号有信号的名字和信号的编号。如 2) SIGINT
2) SIGINT 用户产生中断符(Ctrl + C)
3) SIGQUIT 用户产生退出符(Ctrl + \)
10) SIGUSR1 进程自定义的信号
12) SIGUSR2 进程自定义的信号
11) SIGSEGV 当程序运行产生段错误"segment fault"的时候,无效内存访问
9) SIGKILL 杀死进程
信号的处理过程:
"信号的产生" "信号阻塞" "信号的捕获" "信号的处理程序"
"信号的捕获"
当信号到达进程的时候,进程就捕获到了信号。进程需要对信号进行处理。
信号处理分为三种:
1)缺省处理("SIG_DFL"):大部分信号的缺省处理是使进程终止
2)信号忽略处理("SIG_IGN"):不用对到达的信号做任何的处理
3)用户自定义处理("自定"):信号的处理程序由用户自己定义
进程"默认从父进程继承信号处理"程序。
改变进程对信号的默认处理为自定处理。这时候需要用户向系统注册信号的处理函数。
当前进程怎么向系统注册信号的处理函数:
使用"signal"函数向系统注册。
"signal"(2)
#include <signal.h>
typedef void (*sighandler_t)(int); //函数指针(回调)
sighandler_t signal(int signum, sighandler_t handler);
功能:设置handler为信号signum的处理函数
参数:
"signum" 信号的编号或者信号名字
"handler" 信号的处理函数
返回值:
成功 - 返回以前的这个信号的处理函数 //以前的没用,可不用返回
失败 - 返回SIG_ERR,函数执行错误
/* 举例验证,用户自定义信号处理函数的注册,代码参见 signal.c*/
#include <stdio.h>
#include <signal.h>
void handler(int signum) { //用户定义的信号处理函数
printf("hhhhhhh\n");
return;
}
int main(void) {
signal(2, handler); //向进程注册用户自定义的信号处理函数
while(1);
return 0;
}
相关文章推荐
- Understanding Unix/Linux Programming 笔记:chapter 9:可编程的shell、shell变量和环境; chapter 10:I/O重定向和管道
- UNIX环境高级编程学习之第六章系统数据文件和信息-取所有用户名和UID, GID
- UNIX环境高级编程学习之第六章系统数据文件和信息-GID To GroupName
- UNIX环境高级编程学习之第十一章线程-使用条件变量
- UNIX环境高级编程学习之第十五章进程间通信 - 两个进程通过映射普通文件实现共享内存通信
- Unix环境高级程序设计入门--文件系统的相关编程(上)
- UNIX环境高级编程学习之第十章信号-信号集的操作,让进程阻塞SIGQUIT信号
- UNIX环境高级编程学习之第三章文件IO-文件写操作
- Unix环境高级程序设计入门----文件系统的相关编程(下)
- Unix环境高级程序设计入门----文件系统的相关编程(下)
- Unix环境高级编程——第四章 目录和文件
- UNIX环境高级编程学习之第四章文件和目录-用C实现Shell中的"ls -l"功能
- Unix环境高级程序设计入门----文件系统的相关编程(上)
- unix环境高级编程(信号)
- UNIX环境高级编程学习之第十五章进程间通信 - 通过匿名管道实现父子进程同步
- Unix环境高级程序设计入门----文件系统的相关编程(上)
- Unix环境高级编程第三章文件I/O摘记
- UNIX环境高级编程学习之第六章系统数据文件和信息-修改第四章实现的Shell的“ls -l”功能
- unix环境高级编程-10(信号)
- UNIX环境高级编程学习之第十五章进程间通信 - 通过有名管道(命名管道)实现进程间通信