Linux系统编程入门
1、软件安装
-
vm
VMware Workstation16 密钥: ZF3R0-FHED2-M80TY-8QYGC-NPKYF YF390-0HF8P-M81RQ-2DXQE-M2UT6 ZF71R-DMX85-08DQY-8YMNC-PPHV8
-
boa_ubuntu.zip 配置好环境的Ubuntu虚拟机文件
-
sublime 编辑器
2、Linux基础
2.1 设置
-
终端字体调节
-
放大:Ctrl Shift +
-
缩小:Ctrl -
-
-
输入法切换
- 英->中:Ctrl 空格
- 中->英:Shift、Ctrl Shift、Ctrl 空格
-
[~]
- :当前用户目录 为
/home/用户名
- 根目录(/) 家目录(/home) 用户目录(~)
- :当前用户目录 为
2.2 基础命令
pwd
:查看当前目录的绝对路径ls
:查看当前路径下的内容cd
:切换目录mkdir
:创建目录touch
:创建文件cp
:复制mv
:剪切rm
:删除clear
:清屏
2.3 终端使用技巧
- tab
- 对齐
- 上下
- 查看历史命令
- 左右
- 移动光标更改命令
3、C语言编译运行
# 通过gedit编辑器创建C语言源文件
gedit a.c
- 源文件
#include <stdio.h>
int main()
{
printf("hello world!");
return 0;
}
- 编译
- 使用编译器将编写正确的代码生成机器能够识别的机器码(二进制)
# 编译器为gcc,默认生成a.out,此处通过-o选项指定文件名为a
# 一步完成
gcc a.c -o a
# 分步完成
预处理:gcc -E a.c -o a.i
编译: gcc -S a.i -o a.s
汇编: gcc -c a.s -o a.o
链接: gcc a.o -o a
-
运行
# 运行
./a
Example:打字游戏
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h> //tcgetattr函数所需头文件
#include <unistd.h>
/*
select_mode() 选择游戏难度
*/
int select_mode()
{
int n = 0;
printf("请选择游戏难度:\n");
printf("1:简单,2:中等,3:困难, 4:自定义\n");
scanf("%d", &n);
if( n < 1 || n > 4 )
printf("难度选择错误!\n");
return n;
}
/*
show_rule() 输出游戏规则
*/
void show_rule()
{
printf("游戏规则如下:\n");
printf("1、按下任意键开始游戏\n");
printf("2、按下首字母开始计时\n");
printf("3、正确输入则输出本身,错误输入则输出?\n");
printf("\n");
}
/*
get_str() 获取键盘按下的按键
*/
char get_str()
{
struct termios oldt,newt;
char ch;
tcgetattr(STDIN_FILENO,&oldt);
newt=oldt;
newt.c_lflag &=~( ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
ch = getchar();
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
return ch;
}
/*
game_start() 获取,判断,回显,结束输出
参数: str 随机字符串
n 字符串长度
*/
void game_start(char* str, int n)
{
char ch;
int count = 0;
int start_time, end_time;
int i;
for(i =0; i < n; i++)
{
ch = get_str();
if(ch == str[i])
{
count++;
printf("%c", ch);
}
else
printf("?");
if(0 == i)
start_time = time(NULL);
}
end_time = time(NULL);
printf("\n");
printf("共用时%d\n", end_time - start_time);
printf("正确率为%.2f%%\n", count * 1.0 / n * 100);
}
/* 函数声明 */
void play_again();
/*
create_str() 创建用来输入的随机字符串
参数: n 选择的难度等级
*/
void create_str(int n)
{
int num = 0;
/* 根据选择的不同难度设置随机字符串长度 */
switch(n)
{
case 1:{ num = 10; break; }
case 2:{ num = 20; break; }
case 3:{ num = 30; break; }
case 4:{ printf("请输入一个数:"); scanf("%d", &num); break; }
}
char* word = malloc(num + 1); /* 还需要给结束标志位('\0')分配内存 */
memset(word, 0, num); /* 清空数组内容 */
int i;
srand(time(NULL));
for(i = 0; i < num; i++)
{
word[i] = rand() % 26 + 'a';
}
get_str(); /* 此函数会阻塞进程 */
system("clear"); /* 终端清屏 */
puts(word);
game_start(word, num);
free(word);
play_again();
}
/*
play_again() 可重新选择难度开始游戏,也可以结束游戏
*/
void play_again()
{
char ch;
printf("\n按下任意键结束游戏,按下Esc重新开始游戏\n");
ch = get_str();
if(ch == 0x1B)
{
system("clear");
int n = select_mode();
create_str(n);
}
else
printf("欢迎下次游玩!\n");
}
int main()
{
show_rule();
int n = select_mode();
create_str(n);
return 0;
}
4、Linux系统编程
实现多任务:
- 程序:静态的、文件
- 进程:动态的、程序的执行实例
- 每个进程都由一个进程号来标识,其类型为
pid_t
,进程号的范围:0~32767。 - 进程号总是唯一的,但进程号可以重用。当一个进程终止后,其进程号就可以再次使用了。
- 每个进程都由一个进程号来标识,其类型为
#查看当前进程
ps -e
4.1 获取进程号
#include <stdio.h>
#include <unistd.h>
int main()
{
/* 定义进程号 */
pid_t pid, ppid, pgid;
/* 获取当前进程进程号 */
pid = getpid();
printf("当前进程号:%d\n", pid);
ppid = getppid();
printf("当前进程父进程号:%d\n", ppid);
pgid = getpgid(pid);
printf("当前进程进程组号:%d\n", pgid);
while(1)
{
}
}
4.2 创建进程
#include <stdio.h>
#include <unistd.h>
int main()
{
/* 定义进程号 */
pid_t pid;
/* 创建进程 */
pid = fork();
if(pid < 0) /* 返回-1:创建失败 */
{
perror("fork"); /* 输出错误原因 */
return 0; /* 结束程序 */
}
else if(0 ==pid) /* 返回0:创建成功,子进程 */
{
while(1)
{
printf("子进程\n");
sleep(1);
}
}
else /* 值为子进程进程号:main,父进程 */
{
while(1)
{
printf("父进程\n");
sleep(1);
}
}
}
4.3 创建多个进程
#include <stdio.h>
#include <unistd.h>
int main()
{
int i;
for(i = 0; i < 2; i++)
{
pid_t pid = fork();
if(pid < 0) /* 返回-1:创建失败 */
{
perror("fork"); /* 输出错误原因 */
return 0; /* 结束程序 */
}
else if(0 ==pid) /* 返回0:创建成功,子进程 */
{
break;
}
}
if(0 == i) /* 第一个子进程 */
{
printf("第一个子进程\n");
}
else if(1 == i) /* 第二个子进程 */
{
printf("第二个子进程\n");
}
else
{
printf("父进程\n");
}
}
4.4 fork函数特点:
- 父子进程是交替轮循进行的
- fork创建的进程,谁先开始不一定,cpu先给谁分配工作时间,谁就先开始
- 进程创建子进程的时候,会将原本具有的资源,复制一份给子进程父子进程具有独立的地址空间
- 子进程要比他的父进程先结束,父进程要负责回收子进程的资源如果父进程比他的子进程先结束,那么这个子进程就会变成孤儿进程他会被1 (init)收养,会回收他的资源
三种孤儿进程
- 孤儿进程
- 僵尸进程
- 精灵进程(守护进程)
验证fork函数第三个特点
#include <stdio.h>
#include <unistd.h>
int b = 9;
int main()
{
int a = 9;
/* 定义进程号 */
pid_t pid;
/* 创建进程 */
pid = fork();
if(pid < 0) /* 返回-1:创建失败 */
{
perror("fork"); /* 输出错误原因 */
return 0; /* 结束程序 */
}
else if(0 ==pid) /* 返回0:创建成功,子进程 */
{
a++;
b++;
printf("son: a=%d, b=%d\n", a, b);
}
else /* 值为子进程进程号:main,父进程 */
{
sleep(1);
printf("father: a=%d, %d\n", a, b);
}
}
验证fork函数第四个特点
#include <stdio.h>
#include <unistd.h>
int b = 9;
int main()
{
int a = 9;
/* 定义进程号 */
pid_t pid;
/* 创建进程 */
pid = fork();
if(pid < 0) /* 返回-1:创建失败 */
{
perror("fork"); /* 输出错误原因 */
return 0; /* 结束程序 */
}
else if(0 ==pid) /* 返回0:创建成功,子进程 */
{
int i;
for(i = 0; i < 5; i++)
{
a++;
b++;
printf("son: a=%d, b=%d\n", a, b);
sleep(1);
}
}
else /* 值为子进程进程号:main,父进程 */
{
printf("father: a=%d, %d\n", a, b);
}
}
4.5 vfork函数特点:
- 一定是子进程先运行,父进程处于挂起的状态直到 子进程调用了exit、exec函数,父进程才会开始
- 子进程会在子进程调用了exit、exec函数的时候复制父进程资源
验证vfork函数特点
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int b = 9;
int main()
{
int a = 9;
/* 定义进程号 */
pid_t pid;
/* 创建进程 */
pid = vfork();
if(pid < 0) /* 返回-1:创建失败 */
{
perror("vfork"); /* 输出错误原因 */
return 0; /* 结束程序 */
}
else if(0 ==pid) /* 返回0:创建成功,子进程 */
{
int i;
for(i = 0; i < 5; i++)
{
a++;
b++;
printf("son: a=%d, b=%d\n", a, b);
sleep(1);
}
exit(0);
}
else /* 值为子进程进程号:main,父进程 */
{
sleep(2);
printf("father: a=%d, %d\n", a, b);
}
}
对比:
- fork
- 数据不变,说明从一开始父子进程就具有独立的物理空间
- 子进程成为孤儿进程,说明父子进程交替进行
- vfork
- 数据改变,说明子进程调用函数之前,父子进程共用地址空间,父进程再次启动才会复制资源
- 子进程没有成为孤儿进程,说明父进程再次启动需要触发条件
5、进程间通信
5.1 无名管道
pipe
,只能实现有亲缘关系的进程间通信
- 变量,有两个文件描述符
- 半双工:数据在某一时刻只能单向传递
- 写端写,读端读
- FIFO
子进程写,父进程读
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
char sendbuf[256]=""; /* 发送缓存区 */
char recvbuf[256]=""; /* 接受缓存区 */
int main()
{
int fd[2]; /* 创建整型变量 */
pipe(fd); /* fd[0]:读端 \ fd[1]:写端 */
/* 定义进程号 */
pid_t pid;
/* 创建进程 */
pid = fork();
if(pid < 0) /* 返回-1:创建失败 */
{
perror("fork"); /* 输出错误原因 */
return 0; /* 结束程序 */
}
else if(0 ==pid) /* 返回0:创建成功,子进程 */
{
printf("请输入需要发送的内容:\n");
scanf("%s", sendbuf);
write(fd[1], sendbuf, sizeof(sendbuf));
/* 写 文件标志位 写缓存区 写缓存区内容多长 */
close(fd[0]);
close(fd[1]);
}
else /* 值为子进程进程号:main,父进程 */
{
read(fd[0], recvbuf, sizeof(recvbuf)); /* 会阻塞进程 */
/* 读 文件标志位 读缓存区 读缓存区内容多长 */
printf("father receive: %s\n", recvbuf);
close(fd[0]);
close(fd[1]);
}
}
5.2 有名管道
fifo
,可以实现没有亲缘关系的进程间通信
-
文件:打开、读、写、关闭
/* 创建有名管道 */ mkfifo("路径/管道名", 权限); /* 当前目录下创建管理员可读可写可执行,其他人可读可写的有名管道 */ mkfifo("a_fifo", 0766); /* 上级目录下创建所有人可读可写的有名管道 */ mkfifo("../a_fifo", 0666);
写进程
#include <stdio.h>
#include <stdlib.h>
int main()
{
mkfifo("a_fifo", 0666);
int fd = open("a_fifo", O_WRONLY); /* O_WRONLY:只写 */
char msg[32]= "";
printf("请输入需要发送的内容:\n");
scanf("%[^\n]", msg); /* [^\n]:正则表达式,即读入换行符就结束输入 */
write(fd, msg, sizeof(msg));
close(fd);
}
读进程
#include <stdio.h>
#include <stdlib.h>
int main()
{
mkfifo("a_fifo", 0666);
int fd = open("a_fifo", O_RDONLY); /* O_RDONLY:只读 */
char msg[32]= "";
read(fd, msg, sizeof(msg));
printf("收到消息:%s\n", msg);
close(fd);
}
理论上
- 不需要再写创建管道的语句,读写使用同根管道,但要保证,写创建管道语句的一边要先运行
实际上
- 为了避免运行顺序导致的读写失败,就会在两边都加上创建管道的语句,但要保证,两边创建、打开的管道名字和路径相同
Example:Jack and Rose
Jack
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
int i;
for (i = 0; i < 2; ++i)
{
pid_t pid = fork();
if (pid < 0)
{
perror("fork");
return 0;
}
else if (pid == 0)
{
break;
}
}
if (i == 0) /* 发送消息的子进程 */
{
mkfifo("jtor",0666);
int fd = open("jtor",O_RDWR);
while(1)
{
printf("jack:\n");
char msg[32]="";
scanf("%[^\n]",msg);
scanf("%*c"); /* 跳过输入的一个字符 */
write(fd,msg,sizeof(msg));
if (strcmp(msg,"bye")==0)
{
break;
}
}
close(fd);
}
else if (i == 1) /* 接受消息的子进程 */
{
mkfifo("rtoj",0666);
int fd = open("rotj",O_RDWR);
while(1)
{
char msg[32]="";
read(fd,msg,sizeof(msg));
printf("rose说:%s\n",msg);
if (strcmp(msg,"bye")==0)
{
break;
}
}
close(fd);
}
else
{
while(1)
{
pid_t pid = waitpid(-1,NULL,WNOHANG);
if (pid > 0) /* 刚结束的进程进程号 */
{
printf("进程%d结束啦\n",pid);
}
else if (pid == 0) /* 还有未结束的进程 */
{
continue;
}
else if(pid == -1) /* 进程全部结束 */
{
break;
}
}
}
return 0;
}
Rose
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
int i;
for (i = 0; i < 2; ++i)
{
pid_t pid = fork();
if (pid < 0)
{
perror("fork");
return 0;
}
else if (pid == 0)
{
break;
}
}
if (i == 0) /* 发送消息的子进程 */
{
mkfifo("rotj",0666);
int fd = open("rotj",O_RDWR);
while(1)
{
printf("rose:\n");
char msg[32]="";
scanf("%[^\n]",msg);
scanf("%*c"); /* 跳过输入的一个字符 */
write(fd,msg,sizeof(msg));
if (strcmp(msg,"bye")==0)
{
break;
}
}
close(fd);
}
else if (i == 1) /* 接受消息的子进程 */
{
mkfifo("jtor",0666);
int fd = open("jtor",O_RDWR);
while(1)
{
char msg[32]="";
read(fd,msg,sizeof(msg));
printf("jack说:%s\n",msg);
if (strcmp(msg,"bye")==0)
{
break;
}
}
close(fd);
}
else
{
while(1)
{
pid_t pid = waitpid(-1,NULL,WNOHANG);
if (pid > 0) /* 刚结束的进程进程号 */
{
printf("进程%d结束啦\n",pid);
}
else if (pid == 0) /* 还有未结束的进程 */
{
continue;
}
else if(pid == -1) /* 进程全部结束 */
{
break;
}
}
}
return 0;
}