Linux内核分析复习
这段学习旅程做了许多的练习,想再用博客记录一下,特此记录。
gcc测试
gcc编译一个文件可以-E预编译其为预编译文件,-S编译其为汇编文件,-c使其汇编变为.o文件,最后链接为可执行文件。
为了更好记忆,编译命令是ESc,编译的文件是iso
gcc预编译其为预编译文件.i
gcc -E test.c -o test.i
编译的test.c代码如下:
#include<stdio.h>
int main(int argc,char*argv[]){
printf("Hello,17============\n");
}
gcc编译其为汇编文件
gcc -S test.c -o test.s
gcc编译.c为.o
gcc -c test.c -o test.o
最后执行它
gcc test.o -o test
./test
以上其实是一个程序编译的过程
gcc静态链接
准备程序
静态链接就要制作静态链接库。我们这里模拟一个简单计算器的制作(加减乘除运算),有六个文件17_add.c,17_sub.c,17_mul.c和17_div.c,17_cal.c和17_main.c
1.17_add.c
#include<stdio.h>
float add(float a,float b){
return a+b;
}
2.17_sub.c
#include<stdio.h>
float sub(float a,float b){
return a-b;
}
3.17_mul.c
#include<stdio.h>
float mul(float a,float b){
return a*b;
}
4.17_div.c
#include<stdio.h>
float div(float a,float b){
return a/b;
}
5.17_cal.h
#ifndef __CAL_H_
#define __CAL_H_
#include<stdio.h>
float add(float,float);
float sub(float,float);
float mul(float,float);
float div(float,float);
#endif
6.17_main.c
#include "17_cal.h"
int main(){
printf("Please input two numbers:\n");
float a,b;
scanf("%f %f",&a,&b);
printf("\n");
printf("The sum of a and b is %f\n",add(a,b));
printf("The sub of a and b is %f\n",sub(a,b));
printf("The mul of a and b is %f\n",mul(a,b));
printf("The div of a and b is %f\n",div(a,b));
}
这个程序结构如下:
进行编译
第一步是将除了主文件外的所有.c文件编译为.o文件
第二步是将编译出来的.o文件打包为.a文件(静态库)
第三步是编译主文件,需要静态库和头文件的辅助
命令如下:
gcc -c 17_add.c 17_sub.c 17_mul.c 17_div.c
ar rcvs libcal.a *.o
gcc 17_main.c -o 17_main -I. -L. -l cal
这里*是通配符表示所有,-I(大I)是头文件目录,-L是库文件目录,-l(小L)后面接的库文件名,.表示当前目录,库文件必须要有lib前缀,名就是lib前缀后的内容,这里我把.o文件打包为libcal.a,库名就叫cal。以下为演示画面
gcc动态链接
动态链接要制作动态库(文件与之前静态库相同)
gcc -fPIC -c 17_add.c 17_sub.c 17_mul.c 17_div.c
gcc -shared -o libcal.so *.o
sudo mv libcal.so /usr/lib
gcc 17_main.c -o 17_main -I. -L /usr/bin -l cal
动态库一定要移到/usr/lib下,不然会报错。 操作如下:
gdb测试
没有gdb要先下载gdb,若要使程序可以用gdb调试在编译时,要用gcc -g命令命令。
以下测试还是用gcc测试中的代码,在main.c中增加一个空循环,循环次数可以自己定,代码如下:
#include "17_cal.h"
int main(){
int i=0;
for(i=0;i<2817;i++){
}
printf("Please input two numbers:\n");
float a,b;
scanf("%f %f",&a,&b);
printf("The sum of a and b is %f\n",add(a,b));
printf("The sub of a and b is %f\n",sub(a,b));
printf("The mul of a and b is %f\n",mul(a,b));
printf("The div of a and b is %f\n",div(a,b));
}
编译过程如下(目录结构也与之前一样):
gcc -g 17_main.c -o 17_main2 -I. -L /usr/lib -l cal
现在用gdb进行调试,在main函数设置一个断点,在空循环那里设置一个条件断点。
gdb 17_main
b main
b 5 i==1408
以下为调试过程:
按q退出调试。
编写Makefile
以下使用显示规则,编写Makefile时,代码还是之前的,格式可以如下:
需要得到的文件:待处理的文件
语句
注意Makefile中空格要按TAB键,如果按空格会报错。
文件目录如下:
编写Makefile如下:
testmycal:17_main.o 17_add.o 17_sub.o 17_mul.o 17_div.o
gcc 17_main.o 17_add.o 17_sub.o 17_mul.o 17_div.o -o testmycal
17_main.o:17_main.c 17_cal.h
gcc -c 17_main.c 17_cal.h
17_add.o:17_add.c
gcc -c 17_add.c
17_sub.o:17_sub.c
gcc -c 17_sub.c
17_mul.o:17_mul.c
gcc -c 17_mul.c
17_div.o:17_div.c
gcc -c 17_div.c
现在执行:
Makefile深入
其实这个实验改下路径就好了,和之前那个Makefile原理一样。
openssl
可参考openssl安装,来看看我的openssl版本,然后测试程序,可用openssl version查看版本。测试代码如下
#include <stdio.h>
#include <openssl/evp.h>
int main(){
OpenSSL_add_all_algorithms();
return 0;
}
可用如下命令测试openssl是否安装成功
首先查找evp.h libcrypto.a libssl.a libpthread.a libdl.a
locate evp.h
locate libcrypto.a
locate libssl.a
locate libpthread.a
locate libdl.a
用如下命令(每台电脑不同,建议自己搜索):
gcc test_openssl.c -o test_openssl1 -I/usr/include -L/usr/lib/x86_64-linux-gnu -l dl -l crypto -l ssl -l pthread
然后执行,看是否打印0,打印0表示测试成功
main参数传递
要懂argv和argc意义,及传入参数数组和参数的值
编写如下代码(atoi作用是字符串型转为int型):
#include<stdio.h>
int get_sum(int sum,int a){
sum+=a;
return sum;
}
int main(int argc,char*argv[]){
int sum=0;
for(int i=1;i<argc;i++){
sum=get_sum(sum,atoi(argv[i]));
}
printf("输入参数的和为:%d\n",sum);
return 0;
}
编译过程如下:
反汇编
代码如下:(里面有内嵌汇编)
#include<stdio.h>
int main(){
int input,temp,output;
input=1;
__asm__ __volatile__(
"movl $0,%%eax;\n\t"
"movl %%eax,%1;\n\t"
"movl %2,%%eax;\n\t"
"movl %%eax,%0;\n\t"
:"=m"(output),"=m"(temp)
:"r"(input)
:"eax"
);
printf("%d,%d\n",temp,output);
return 0;
}
内嵌汇编中%0是指output,%1是指temp,%2是指input
现在来解析这段内嵌汇编
"movl $0,%%eax;\n\t" //将eax值置为0
"movl %%eax,%1;\n\t" //将eax值即0赋值给%1(temp) 现在temp=0
"movl %2,%%eax;\n\t" //将%2(input=1)的值赋值给eax eax=1
"movl %%eax,%0;\n\t" //将eax的值1赋值给%0(output) output=1
所以temp=0 output=1
执行如下:
汇编混合编程
参考main参数传递代码
main.c
#include<stdio.h>
/*
int get_sum(int sum,int a){
sum+=a;
return sum;
}
*/
extern int get_sum(int a,int b);
int main(int argc,char*argv[]){
int sum=0;
for(int i=1;i<argc;i++){
sum=get_sum(sum,atoi(argv[i]));
}
printf("输入参数的和为:%d\n",sum);
return 0;
}
sum.c
#include<stdio.h>
int get_sum(int sum,int a){
sum+=a;
return sum;
}
可用gcc -c sum.c让sum.c变为sum.o。然后使用
objdump -d sum.o反汇编(可看到其汇编代码)
可用如下命令使其执行:
也可使用内嵌汇编,代码如下:
// An highlighted block
var foo = 'bar';#include<stdio.h>
/*
int get_sum(int sum,int a){
sum+=a;
return sum;
}
*/
//extern int get_sum(int a,int b);
int main(int argc,char*argv[]){
int sum=0;
for(int i=1;i<argc;i++){
//sum=get_sum(sum,atoi(argv[i]));
//AT&T汇编
__asm__ __volatile__(
"addl %2,%1;\n\t"
"movl %1,%%eax;\n\t"
:"=m"(sum)
:"m"(sum),"b"(atoi(argv[i]))
:"eax"
);
}
printf("输入参数的和为:%d\n",sum);
return 0;
}
实现mywho
使用系统调用方式实现who,首先使用man命令查找who (man who)
Linux使用man命令中的(1)是命令,(2)是系统调用,(3)是库的调用,这里是(1)就是个命令,可以直接执行。
who的实现离不开一个结构体utmp
struct utmp {
short ut_type; /* Type of record */
pid_t ut_pid; /* PID of login process */
char ut_line[UT_LINESIZE]; /* Device name of tty - "/dev/" */
char ut_id[4]; /* Terminal name suffix,
or inittab(5) ID */
char ut_user[UT_NAMESIZE]; /* Username */
char ut_host[UT_HOSTSIZE]; /* Hostname for remote login, or
kernel version for run-level
messages */
struct exit_status ut_exit; /* Exit status of a process
marked as DEAD_PROCESS; not
used by Linux init (1 */
/* The ut_session and ut_tv fields must be the same size when
compiled 32- and 64-bit. This allows data files and shared
memory to be shared between 32- and 64-bit applications. */
#if __WORDSIZE == 64 && defined __WORDSIZE_COMPAT32
int32_t ut_session; /* Session ID (getsid(2)),
used for windowing */
struct {
int32_t tv_sec; /* Seconds */
int32_t tv_usec; /* Microseconds */
} ut_tv; /* Time entry was made */
#else
long ut_session; /* Session ID */
struct timeval ut_tv; /* Time entry was made */
#endif
int32_t ut_addr_v6[4]; /* Internet address of remote
host; IPv4 address uses
just ut_addr_v6[0] */
char __unused[20]; /* Reserved for future use */
};
具体可参考博客用系统调用who命令实现mywho具体代码如下:
#include<stdio.h>
#include<utmp.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
#include<time.h>
#define SHOWHOST
long showtime(long time1){
struct tm* tt;
//trans time into ASCII
tt=gmtime(&time1);
printf(" ");
printf("%d-%d-%d %d:%d",tt->tm_year+1900,tt->tm_mon+1,tt->tm_mday,(tt->tm_hour+20)%24,tt->tm_min);
}
int showinfo(struct utmp *ut){
//username
printf("%-8.8s",ut->ut_name);
printf(" ");
//device name
printf("%-8.8s",ut->ut_line);
printf(" ");
//printf("%10ld",ut->ut_time);
showtime(ut->ut_time);
printf(" ");
#ifndef SHOWHOST
printf("%s",ut->ut_host);
#endif
printf("\n");
return 0;
}
int main(){
struct utmp res;
int utmpfd;
int strlen=sizeof(res);
if((utmpfd=open(UTMP_FILE,O_RDONLY))==-1){
perror(UTMP_FILE);
exit(1);
}
while(read(utmpfd,&res,strlen)==strlen){
showinfo(&res);
}
close(utmpfd);
return 0;
}
汇编1
来一段C语言代码
int g(int x){
return x+3;
}
int f(int x){
int i = 学号后两位;
return g(x)+i;
}
int main(void){
return f(8)+1;
}
使用如下命令进行汇编
gcc -S boris01.c -o boris01.s -m32
查看汇编代码会有许多不需要信息,使用如下命令删除不需要行
sed -i '/[.]/d' boris01.s
然后查看汇编代码
g:
endbr32
pushl %ebp
movl %esp, %ebp
addl $_GLOBAL_OFFSET_TABLE_, %eax
movl 8(%ebp), %eax
addl $3, %eax
popl %ebp
ret
f:
endbr32
pushl %ebp
movl %esp, %ebp
subl $16, %esp
addl $_GLOBAL_OFFSET_TABLE_, %eax
movl $17, -4(%ebp)
pushl 8(%ebp)
call g
addl $4, %esp
movl -4(%ebp), %edx
addl %edx, %eax
leave
ret
main:
endbr32
pushl %ebp
movl %esp, %ebp
addl $_GLOBAL_OFFSET_TABLE_, %eax
pushl $8
call f
addl $4, %esp
addl $1, %eax
leave
ret
movl (%esp), %eax
ret
也可用gdb进行调试
gcc -g boris01.c -o boris01 -m32
gdb boris01
可在main那里设置断点 b main
然后r ,可用disassemble查看反汇编代码,可用i r查看寄存器信息
实现cp
cp的功能就是复制。复制文件或者目录,复制目录要加上-r,可用man cp来查看,与cp相关函数可用man -k cp,与如下机构体有关
struct stat
{
dev_t st_dev; /* ID of device containing file -文件所在设备的ID*/
ino_t st_ino; /* inode number -inode节点号*/
mode_t st_mode; /* protection -保护模式?*/
nlink_t st_nlink; /* number of hard links -链向此文件的连接数(硬连接)*/
uid_t st_uid; /* user ID of owner -user id*/
gid_t st_gid; /* group ID of owner - group id*/
dev_t st_rdev; /* device ID (if special file) -设备号,针对设备文件*/
off_t st_size; /* total size, in bytes -文件大小,字节为单位*/
blksize_t st_blksize; /* blocksize for filesystem I/O -系统块的大小*/
blkcnt_t st_blocks; /* number of blocks allocated -文件所占块数*/
time_t st_atime; /* time of last access -最近存取时间*/
time_t st_mtime; /* time of last modification -最近修改时间*/
time_t st_ctime; /* time of last status change - */
};
具体实现函数如下:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
int main(int argc,char *argv[]){
int fd;
int fsize;
char *buffer;
struct stat st;
if(argc!=3){
printf("Error:parameter wrong!\n");
exit(0);
}
fd=open(argv[1],O_RDONLY);
if(fd<0){
printf("Error:can't open the read-file!\n");
exit(0);
}
stat(argv[1],&st);
fsize=st.st_size;
buffer=(char *)malloc((1+fsize)*sizeof(char));
if(!buffer){
printf("Error:memory wrong!\n");
exit(0);
}
read(fd,buffer,fsize);
close(fd);
fd=open(argv[2],O_WRONLY|O_CREAT);
if(fd<0){
printf("Error:can't open the write-file!\n");
exit(0);
}
write(fd,buffer,fsize);
close(fd);
free(buffer);
return 0;
}
然后编辑,现在来测试,创建一个sayhello.c文件,可以打印一串字符,然后复制它,发现仍可以打印,说明复印成功,操作如下:
读者写者模型
这是操作系统问题,可参考博客读者写者问题,主要有读者优先和写者优先
在编译时,要加入-pthread(多线程编译),如:
gcc -pthread reader_first.c -o reader_first
算法测试
Ubuntu下支持哪些C语言的排序算法,查找算法?你是怎么得到的?
可以使用man -k sort | grep 3
qsort就是快排,bsearch就是二分查找,现在我要在一堆数字中查找一个数字,代码如下:
#include<stdio.h>
//便于后面生成随机数
#include<stdlib.h>
//这一段很重要
static int compmi(const void *m1, const void *m2){
// struct mi *mi1 = (struct mi *) m1;
//struct mi *mi2 = (struct mi *) m2;
//return strcmp(mi1, mi2);
return *(int *)m1 - *(int*)m2;
}
int main(){
//定义一个11个元素的数组
int array[11];
for(int i=0;i<11;i++){
array[i]=rand()%1001;
}
//第十个元素是我学号后3位
array[10]=817;
//输出数组
for(int i=0;i<11;i++){
printf("数组第%d个元素是%d\n",i,array[i]);
}
//排序
qsort(array,11,sizeof(array[0]),compmi);
printf("排序后数组为============\n");
for(int i=0;i<11;i++){
printf("数组第%d个元素是%d\n",i,array[i]);
}
//int res=0;
int key=817;
int *res=(int *)bsearch(&key,array,11,sizeof(array[0]),compmi);
//printf("res=%d\n",res);
if(res==NULL){
printf("数组中没有%d\n",817);
}
else{
printf("数组中存在%d\n",817);
}
}
现在进行编译
myshell
利用fork,exec,wait编写一个具有执行命令功能的shell
可分别使用man查找fork,exec族和wait函数功能,里面会有一些实例,最终写出的代码如下:
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/wait.h>
#define TIME 5
int test_fork(){
printf("before,the pid is %d\n",getpid());
int fork_cv;
//later
fork_cv=fork();
sleep(TIME);
wait(getpid());
if(fork_cv==0){
printf("I'm son, my pid is %d\n",getpid());
}
else if(fork_cv==-1){
perror("error\n");
}
else{
printf("I'm father,my pid is %d\n",getpid());
}
}
int main(){
test_fork();
//array
char array[50];
// printf("root@ironman:");
//input command
// scanf("%s",array);
//describe pid-process
pid_t ppid;
int flag=0;
//repeat command
while(1){
printf("root@ironman:");
//input command
scanf("%s",array);
//char* command=array;
/*
for(auto i:array){
printf(i);
}
*/
//printf("\n");
//if command=="exit" exit the loop
if(strcmp(array,"exit")==0){
//exit(0);
break;
}
else{
//generate son_process
ppid=fork();
//-1 is wrong
if(ppid==-1){
perror("wrong!\n");
exit(1);
}
//this is important
else if(ppid==0){
flag=execlp(array,array,NULL);
//error
if(flag==-1){
exit(1);
}
}
else{
wait(NULL);
}
}
}
return 0;
}
现在进行测试:
wait waitpid学习测试
可用man wait和man waitpid来查找使用方法
要能说明wait 的返回值的每一位的含义
进行wait的测试,代码如下:
#include<sys/wait.h>
#include<unistd.h>
#include<stdio.h>
int main(){
int state;
pid_t parent,son;
//generate son process
son=fork();
if(son==-1){
perror("Error\n");
exit(0);
}
//son process
else if(son==0){
printf("The son process of pid is %d\n",getpid());
exit(3);
}
//parent process
else{
//get WIFEXITED
parent=wait(&state);
//non 0
if(WIFEXITED(state)){
printf("This process is correct\n");
printf("The return code of this child process is %d\n",WIFEXITED(state));
}
//0
else{
printf("The child process is wrong\n");
}
}
exit(0);
}
下面进行测试:
Linux批处理
批处理代码如下:
#! /bin/bash
#
# 输出文件的某一行
# 参数1:文件名
# 参数2:输出行数
function printLine() {
pri=`cat $1 | head -n$2 | tail -1f`
#字符串长度是否为0,不为0输出
if [ -n "${pri}" ]
then
echo ${pri}
fi
}
file1=./1.txt
file2=./2.txt
# 得到两个文件的行数
m=`cat ./${file1} | wc -l`
echo "第一个文件行数为:${m}"
n=`cat ./${file2} | wc -l`
echo "第二个文件行数为:${n}"
# 比较两个文件行数大小,记录文件行数大的文件
max=
min=
moreTxt=
if [ ${m} -gt ${n} ]
then
max=${m}
min=${n}
moreTxt="${file1}"
else
max=${m}
min=${n}
moreTxt="${file2}"
fi
echo "行数小为:${min}"
echo "行数大为:${max}"
#输出文件相同行数
i=1
while (( i <= ${min} ))
do
printLine ${file1} ${i}
printLine ${file2} ${i}
let i++
done
#输出文件大的行数
while (( i <= ${max} ))
do
printLine ${moreTxt} ${i}
let i++
done
创建两个文件1.txt和2.txt
内容如下:
接下来执行:
任重而道远,2022 继续加油学习!!!