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 继续加油学习!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值