预编译:将宏注释等进行替换,头文件展开(函数声明) gcc - E main.c -o main.i,编译:检查语法语义错误,生成汇编代码 gcc -S main.i -o main.s 汇编:将汇编代码转变为机器语言(二进制代码) gcc -c main.s -i main.o,链接:将调用的函数和我们的应用程序进行链接 gcc main.o -o main
sudo su :管理员登陆 exit:退出登录 改密码:passwd 用户名 添加新用户:adduser 用户名 boot->内核 etc->配置文件 lib->库文件 uer->安装的软件 dev->配置文件 bin->命令
vim:分为命令模式,编辑模式,末行模式
删除用户:deluser 用户名 (一般在/etc/passwd下查看用户信息)
root:管理员id号为0;stu:用户,id号从1000开始
cd:切换路径 ls:显示当前位置文件 pwd:显示当前位置 touch:创建普通文件 mkdir:创建目录文件
运用ls -l:其中开头为-时普通文件;为d为目录文件;为p为管道;c,b为设备文件;
其中分为属主(u),同组(g),其他(o)
r:4 ;w:2 ;x:1;
chmod:改变权限 如:chmod u+x first.c chmod 775 first.c
cp:拷贝文件 如:普通文件cp a.txt b.txt 目录文件:cp -r tmp dir
mv:重命名或移动文件
cat:打印普通文件夹的内容(一般内荣少)如:cat first.c
more:显示大量文件夹的内容;用法同上
less:显示文件内容可以用滚轮反复看;
head:显示文件前十行
tail:显示文件后十行
前面5个退出观看可以按q
ctrl+d 结束cat输入
ctrl+c 结束进程
vim:进入普通文件夹
set nu :显示行数 n yy :复制行数 p:贴贴 n dd:剪切,删除 u:撤销
find:查询字符串 用法:find 路径 -name '文件名‘ 如:find /home/stu -name first.c
grep:在文件夹中查找相关字符串,常与’ | '连用 用法:grep 字符串 文件名
如: grep "hello" first.c grep -i "hello" first.c 查找大小写的hello
grep -c "hello" first.c 查找含有“hello"的代码行数
grep -v "hello" first.c 查找不含hello的代码
grep -v -i "hello" first.c 查找不含大小写hello的代码
|(管道):如 ls /bin | grep "pwd"查找bin(命令)里有”pwd“的命令。将ls/bin的信息写入管道,在管道中找pwd若找到将其打印在屏幕上
man:显示库文件调用手册,其中1为命令;2为系统调用;3为库函数;如 man 3 printf(进入printf函数的库函数说明);
进程:ps:显示当前进程,显示进程id号,名字时间等
ps -f:显示当前Id号,用户名,副Id号等更详细的数据
ps -e:显示全部进程信息
ps -ef:显示全部进程得详细信息
sleep 300:在前端进程指令,无法进行其他命令
sleep 300&:在后端进行指令,可以进行其他指令
ps -ef | grep sleep:通过管道在全部指令中查找特定的指令
kill 进程id号:结束某项指令
在前端的指令也可以用ctrl+c来结束指令
top:查看当前任务情况(包括数量等等)按q键退出
tar: 压缩与解压 常用 c(创建一个包),v(显示创建包的过程),f(创建一个普通文件),x(释放包的内容),z(用于解压缩包)如:tar cvf first a.c b.c(将a.c与b.c放入一个文件夹中);tar xvf first(释放包的内容);gzip first(将包进行压缩);tar zxf first.gz(将压缩包进行解压缩);
分文件写程序:gcc -c a.c:将a.c文件转变为a.o文件 (可以验证是否有语法错误)
gcc -c b.c 最后进行 gcc -o ab a.o b.o进行链接
makefile文件:管理工程,实现自动化编译 ;vim makefile进行makefile的编写,后进行make进行自动编译;make clean可以自动清理 a.o, b.o, main
all:main
GDB=-g//加入调试信息
main:a.o b.o
gcc -o main a.o b.o
a.o:a.c
gcc -c a.c ${GDB}//时延make时就可以自己调试不用手动加-g
b.o:b.c
gcc -c b.c ${GDB}
clean:
rm -rf *.o main
调试程序:gdb工具 :1.调试的对象:exe文件 2.开发版本,发行版本:开发-->可执行程序中包含调试信息 发行-->不包含(stdin键盘 stdout屏幕)3.常见命令:l-->显示代码 l+行号-->就跳到第几行 l+文件名:1-->跳转到该文件的第一行处(如 l b.c:1) b+行号-->加断点 info break-->显示断点信息 r-->运行程序 n-->单步执行 p-->打印 p+&某变量(p &b)--> 可以查看变量地址 p+某变量(p b)--> 可以查看变量值 s-->进入函数 finish-->跳出函数 c-->从一个断点可以直接运行到下一个断点 bt-->显示函数调用的栈关系(即函数的包含关系,可以知道当前自己在那个函数里面,还是在主函数里面) 4.在编译代码时 gcc -o d d.c -g(其中-g为有调试信息) 5.gcc详细介绍见第四节课1:05:00
库文件:预先编译好的方法的集合(add.c-->add.o) 头文件在:/usr/include /usr/bin(命令,二进制可执行程序) /lib(/usr/lib):存放库文件 1.静态库-->会把用到的方法复制到可执行程序中(即在当前目录下可以执行,不用将文件libfoo.a移动到/lib或/usr/lib) libc.a 制作静态库:①将原文件编译生产目标文件 如a.c-->a.o ②将目标文件打包生产库文件:如ar crv lib库名.a 库名.o 库名.o (c为建立备存文件 r为将文件插入备存文件中) ---->gcc -o main main.c -L路径 -l库名 (如-lfoo--->libfoo.a lib为前缀 .a为后缀 ) 2.共享库(用的多)-->不会把用到的方法复制到可执行程序中(即需要将文件libfoo.so移动到/lib或/usr/lib)-->ldd main(显示main的共享库) libc.so 制作动态库:①将原文件编译生产目标文件 如a.c-->a.o ②将目标文件打包生产库文件:gcc -shared -fPIC -o lib库名.so 库名.o 库名.o ③gcc -o main main.c -L路径 -l库名 ④将建好的库移动到/lib或/usr/lib中⑤./main即可运行程序 3.ldd可以查看已经使用的共享库 (ldd+可执行文件名) (两个库都有优先共享库)
缓冲区:fflush(stdin/stdout/stderr)可以强制刷新,使缓冲区的内容打印到屏幕上 exit(0);//1.刷新缓冲区 2.退出程序 _exit(0)--->头文件#include<unistd.h> return 0:退出程序后系统自动调用 exit(0)后面步骤同上
主函数参数:
#include<stdio.h>
//int argc主函数参数个数,char*argv[]传给主函数的参数内容,char*envp[]环境变量
int main(int argc,char*argv[],char*envp[])
{
printf("argc=%d\n",argc);//int argc主函数参数个数
for(int i=0;i<argc;i++)//char*argv[]传给主函数的参数内容
{
printf("argv[%d]=%s\n",i,argv[i]);
}
for(int i=0;envp[i]!=NULL;i++)//char*envp[]环境变量
{
printf("envp[%d]=%s\n",i,envp[i]);
}
return 0;
}
//主函数传参
./main hello aca//就将hello aca传给了argv[]
//此./main中有三个参数argv[0]=./main argv[1]=hello argv[2]=aca
计算机五大部件:运算器,控制器(cpu中央处理器),存储器(外存与内存),输入设备(I),输出设备 (O) 系统总线:地址总线:传地址,数据总线:传递数据,控制总线:控制逻辑执行
fork:进程:一个正在运行的程序 pcb(描述进程是一个结构体,记录信息,存放进程所需的id号等等) 状态:就绪,运行,阻塞 内存管理:物理内存(1.掉电丢失数据2.访问速度快) 外部硬盘(1.数据永远存放2.掉电不丢失) 虚拟内存:假设物理内存不够,先从硬盘内借用一些内存当作物理内存来使用,访问速度没有物理内存快,但可以解决物理内存不够的情况。原理是将要运行的数据先放在内存中,将先不用的数据放在硬盘划分的区域,就可以运行比物理内存还大的程序。
其中页表左侧为逻辑页,右侧为物理页。看物理页要有逻辑页。一个页面为4K=4x1024=4096。 写时拷贝:在修改父进程或子进程时才进行拷贝,不改变时父子进程共享页面。以页为单位
fork:复制进程(用处:1.fork复制+exec替换 2.处理任务,可以采用多线程方法)(父进程返回值为子进程的ID号,子进程的返回值为0,两者一模一样且同时进行)返回值为pid_t fork();通过判断返回值看是子进程还是父进程。 地址:父子进程共用一块地址空间,但存储的n值不一样,因为打印出来的是逻辑地址,不是物理地址,两者的逻辑地址是相同的但是物理地址是不同的,对于同一个进程来说逻辑地址相同则物理地址相同,但是对于两个不同的进程来说逻辑地址相同物理地址不一定相同。(nm main查询可执行文件main的地址) (见下面代码即day2/fork.c)
此图为逻辑地址解释图。逻辑地址就是对于起始位置的偏移量。 适用于win32(4G)不是 win64(8G)。
#include<stdio.h>//测试fork进程
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
int main()
{
int n=0;//循环次数
char *s=NULL;//存放字符串
pid_t pid=fork();//pid_t相当于int
if(pid==-1)
{
exit(1);
}
if(pid==0)
{
n=3;
s="child";
}
else
{
n=7;
s="parent";
}
for(int i=0;i<n;i++)
{
printf("s=%s curr_pid=%d ppid=%d n=%d n=%p\n",s,getpid(),getppid(),n,&n);//当前进程的id号,getpid()为获取id号,ppid为其父进程id号
sleep(1);
}
exit(0);
}
fork()相关题目:
1:#include<stdio.h>
int main()
{
for(int i=0;i<2;i++)
{
fork();
printf("A\n");//6个A
}
}
2:#include<stdio.h>
int main()
{
for(int i=0;i<2;i++)
{
fork();
printf("A");//8个A ,会先存放在缓冲区内 fork时也会将缓冲区的A复制过去
}
}
3:#include<stdio.h>
int main()
{
fork()||fork();
printf("A\n");//3个A
}
第三题详解:
僵死进程(与fork一起用):子进程先结束,父进程没有调用wait接收它的退出码,子进程就变成僵死进程了。
#include<stdio.h>//测试fork进程
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
int main()
{
int n=0;//循环次数
char *s=NULL;//存放字符串
pid_t pid=fork();//pid_t相当于int
if(pid==-1)
{
exit(1);
}
if(pid==0)
{
n=3;
s="child";
}
else
{
n=7;
s="parent";
//wait(NULL);//不接收返回值,只执行
int val=0;
wait(&val);//获取退出码
if(WIFXITED(val))//在帮助手册中查,正常结束
{
printf("child exit code:%d\n",WEXITSTATUS(val));//获取退出码
}
}
for(int i=0;i<n;i++)
{
printf("s=%s curr_pid=%d ppid=%d n=%d n=%p\n",s,getpid(),getppid(),n,&n);//当前进程的id号,getpid()为获取id号,ppid为其父进程id号
sleep(1);
}
exit(0);
}
文件描述符:fopen,fclose(库函数)|open,read,write,close(系统调用) 在内核中实现, open("路径+名称",r/w):文件存在 open("路径+名称",r/w,文件权限):文件不存在 只读打开:O_RDONLY 只写打开:O_WRONLY 读写打开:O_RDWR 具体看(2)open库函数
#include<fcntl.h>//write写操作
int main()
{
int fd=open("fil.txt",O_WRONLY|O_CREAT,0600);
//fil.txt为要打开的文件 O_WRONLY|O_CREAT为要创建一个文件并且可写 0600为八进制表示属主可读可写但是其他的不可读也不可写见chmod
if(fd==-1)
{
exit(1);
}
write(fd,"hello",5);//在fd中写入"hello"共写入5个字节
close(fd);
exit(0);
}
#include<fcntl.h>
int main()//读操作,每次读完后,下一次可以接着上一次读到的地方继续读
{
int fd=open("fil.txt",O_RDONLY,0600);
if(fd==-1)
{
exit(1);
}
char buff[128]={0};
read(fd,buff,127);
printf("buff=%s",buff);
close(fd);
exit(0);
}
复制操作:
#include<stdio.h>
int main(int argc,char*argv[],char*envp[])
{
if(argc!=3)//判断变量是否为三个
{
printf("error argc");
exit(1);
}
char*s_name=argc[1];//要读的数据
char*t_name=argc[2];//要写的数据
int fdr=open(s_name,O_RDONLY,0600);//打开只读文件
int fdw=open(t_name,O_WRONLY|O_CREAT,0600);//打开只写文件
if(fdr==-1||fdw==-1)//判断两文件是否错误
{
printf("open error");
exit(1);
}
printf("fdr=%d fdw=%d\n",fdr,fdw);//打印fdr,fdw的值为3和4,0,1,2为文件描述符stdin等等,见下图
char buff[1024];//存放数据
int num=0;//表示每次读了多少字节
while((num=read(fdr,buff,1024))>0)//将每次读到的返回到num中,若返回值大于0则继续循环
{
write(fdw,buff,num);//将读到的文件写入buff中
}
close(fdr);
close(fdw);
exit(0);
}
printf-->在底层就是调用write。
系统调用与库函数的区别:
库函数在用户空间,系统调用在内核空间。当应用程序进行系统调用时,会产生中断,陷入内核,根据用户空间的系统调用号和内核空间的系统调用表,执行内核代码。应用程序可以先调用库函数再由库函数进行系统调用,也可以直接由应用程序进行系统调用。
父进程先打开一个文件后,fork后子进程是否可以共享使用:父进程打开文件后再进行fork,父子进程共享该文件描述符。//若父进程在fork后才打开文件,那么父子进程将不会共享文件,而是各自产生单独的文件。验证如下代码:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
int main()
{
//
int buff[128]={0};//父进程先将文件打开后在进行fork,父进程与子进程会共享一个文件
int fd=open("filet.txt",O_RDONLY,0600);//filet.txt自己创建,里面存放hello
if(fd==-1)
{
exit(1);
}
//
pid_t pid=fork();
//
int buff[128]={0};//若父进程在fork后才打开文件,那么父子进程将不会共享文件,而是各自产生单独的文件
int fd=open("filet.txt",O_RDONLY,0600);//filet.txt自己创建,里面存放hello
if(fd==-1)
{
exit(1);
}
//
if(pid==-1)
{
exit(1);
}
if(pid==0)
{
read(fd,buff,1);
printf("child buff=%s\n",buff);
sleep(1);
read(fd,buff,1);
printf("child buff=%s\n",buff);
}
else
{
read(fd,buff,1);
printf("parent buff=%s\n",buff);
sleep(1);
read(fd,buff,1);
printf("parent buff=%s\n",buff);
}
close(fd);
exit(0);
}
替换:exec:有6种:execl,exexlp,execle,execv,execvp, execve(系统调用) 这些只是参数不同
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>//open close read write
#include<assert.h>
int main(int argc,char*argv[],char*envp[])
{
printf("exec pid=%d\n",getpid());//获取父进程id号
execl("/usr/bin/ps","ps","-f",(char*)0);
execlp("ps","ps","-f",(char*)0);//不需要写路径,只需要写要找的名称即可
execle("/usr/bin/ps","ps","-f",(char*)0,envp);//第二个ps只是名字可以改为其他名字,这个名字不重要,但是前面路径不能错
char *my_envp[10]={"ps","-f",0};
execv("/usr/bin/ps",my_envp);
char *my_envp[10]={"ps","-f",0};
execvp("ps",my_envp);
char *my_envp[10]={"ps","-f",0};
execve("/use/bin/ps",my_envp,envp);//系统调用
}
创建bush: