Linux系统笔记:shell命令、文件系统、Vi编辑器、文件IO、mmap()、bmp图片格式、线程

一、shell命令集

命令功能
pwd当前路径显示命令
ls查看当前目录下的文件
cd目录切换
cat查看文件内容
cp文件拷贝命令
sudo切换用户身份命令
su切换用户
mv移动文件命令mv
mkdir创建文件夹命令
touch创建文件命
rm删除
rmdir删除目录
tar压缩:tar -czvf test.tar.gz a.c 解压:tar -xzvf test.tar.gz
ifconfig显示网络配置信息
reboot重启命令
poweroff关机
man系统帮助
sync数据同步写入磁盘
grep查找内容
find查找文件
du文件夹大小查看
uname系统信息查看
clear清理屏幕
df磁盘空间检查命令
gedit使用gedit编辑器
ps当前的系统进程查看 ps -ef:查看当前系统中,所有运行中的程序(进程)pstree -p:以树状结构查看当前系统进程,包括进程号。
top进程实时运行状态查看
kill结束进程 kill -9 进程ID:向进程ID发生一个9号信号(SIGKILL,杀死信号(kill -l)killall -9 进程名:向进程ID发生一个9号信号,杀死进程。
file文件类型查看命令
export查看当前环境变量 可执行文件路径:PATH 动态库路径:LD_LIBRARY_PATH 静态库路径:LIBRARY_PATH 头文件路径:C_INCLUDE_PATH C++头文件路径:CPLUS_INCLUDE_PATH

二、ubuntu文件系统

(一)、根目录“/”

Linux下“/”就是根目录!所有的目录都是由根目录衍生出来的。

(二)、Ubuntu文件系统结构

/bin 存放二进制可执行文件,这些命令在单用户模式下也 能够使用。可以被root和一般的账号使用。

/boot Ubuntu内核和启动文件,比如vmlinuz-xxx。gurb引导装载程序。

/dev 设备驱动文件

/etc 存放一些系统配置文件,比如用户账号和密码文件,各种服务的起始地址。

/home 系统默认的用户主文件夹,一般创建用户账户的时候,默认的用户主文件夹都会放到此目录下。

/lib 存放库文件

/media 此目录下放置可插拔设备,比如SD卡,或者U盘就是挂载到这个目录中。

/mnt 用户可使用的挂载点,如果要挂载一些额外的设备,那么就可以挂载到此处。

/opt 可选的文件和程序存放目录,给第三方软件放置的目录。

/root root用户目录,也就是系统管理员目录。

/sbin 和/bin类似,也是存放一些二进制可执行文件。sbin下面的一般是系统开机过程中所需要的命令。

/srv 服务相关目录。比如网络服务。

/sys 记录内核信息,虚拟文件系统。

/tmp 临时目录

/var 存放一些变化的文件,比如日志文件

/usr usr不是user的缩写,而是UNIX Software Resource的缩写,存放于系统用户有关的文件,会占用很大的存储空间!

/proc 虚拟文件系统,数据放置到内存中,存放系统运行信息

(三)、绝对路径和相对路径

绝对路径:从根目录“/”算起的路径。

相对路径:相对于目前路径的文件名写法,比如./home/zzk。不是以“/”开头的就行。
. ” 代表当前路径,也可以 用“./”表示
. . ”代表上一层目录,也可以用“…/”表示

三、vi编辑器

一、vi/vim的三种模式

vi编辑器有三种模式:
命令模式(command mode)、
插入模式(Insert mode)、
底行模式(last line mode)。

在命令模式下:输入的字母i,切换插入模式
在插入模式下:点击Esc退出,进入命令模式
在命令模式下:输入:(英文冒号),进入底行模式

插入模式和底行模式不能直接切换,要经由命令模式。

进入底行模式,底部会出现冒号提示:进入插入模式,底部会出现–INSERT–提示。

四、文件IO

1、Linux文件IO接口(API)

open: 打开文件

int open(char *pathname,int flags);
int open(char *pathname,int flagsmode_t mode);

参数:
pathname:文件路径,例如:“./a.txt”
flags:打开指定文件后的权限,分为主类和副类
mode:如果文件被新建,指定其权限位(八进制码)
返回:返回文件句柄

flag主类:

O_RDONLYOWRONLYO_RDWR
只读只写可读可写

flag副类

O_EXCL如果文件存在,则返回错误消息
O_NOCTTY如果文件为终端,那么终端不可以调用open系统调用的那个进程的控制终端
O_TRUNC如果文件已经存在则清除文件中的原有内容
O_APPEND若写文件则在文件内容后追加内容
O_CREAT如果文件不存在则创建新文件

read: 读文件

ssize_t read(int fd, void *buf, size_t count);

参数:
fd:表示要读取的文件句柄,一般由前面的open返回得到。(用来标识不同文件)
buf:是应用程序自己提供的一段内存缓冲区,用来存储读出的内容。
count:是要读取的字节数。返回值ssize_t类型是linux内核用typedef重定义的一个类型(其实就是int),表示成功读取的字节数。
返回:成功返回读字节数,失败返回-1

write: 写文件

ssize_t write(int fd, const void *buf, size_t count);

参数:
fd:表示要写的文件句柄,一般由前面的open返回得到。
buf:要写的内容
count:要写的字节数。
返回:成功返回写入字节,失败返回-1

close:关闭文件

 int close(int fd);

参数:
fd:表示要关闭的文件句柄

打开文件示例代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
   
#define READ_SIZE 20
char *pathname = "./a.txt";     //定义文件路径名
char read_buff[READ_SIZE];          //读取存放数组
 
int main(int argc,char *argv[])
{
 	int fd = -1;                //打开状态标志
	int ret = 0;                //读文件状态标志
 
    fd = open(pathname,O_RDONLY); //打开一个文件/只读
    if(fd == -1)
    {
        printf("open error!\n");//如果打开失败输出提示
        exit(-1);               //退出程序
    }
    ret = read(fd,read_buff,READ_SIZE);//读取文件内容到数组read_buff
    if(ret < 0)
    {
        printf("read error!\n");//读取失败处理
    }
    else                        //读取成功处理
    {
        printf("read success!\n");
        printf("ret = %d,fd = %d,constent is [%s]\n",ret,fd,read_buff);//输出读取数组
    }
     return 0;
  }

lseek():修改文件指针偏移

#include <sys/types.h>
#include <unistd.h>

off_t lseek(int fd, off_t offset, int whence);

参数:
fd:文件描述符
offset:文件偏移量(可以为负数)
whence:

   SEEK_SET(文件开头)
          The offset is set to offset bytes.

   SEEK_CUR(当前位置)
          The offset is set to its current location plus offset bytes.

   SEEK_END(文件末尾)
          The offset is set to the size of the file plus offset bytes.

五、Linux中C库mmap()函数详解

  void *mmap(void *start, size_t length,
  int prot,int flags, int fd, off_t offset);  

系统调用mmap()用于共享内存

int munmap( void * addr, size_t len )

该调用在进程地址空间中解除一个映射关系,addr是调用mmap()时返回的地址,len是映射区的大小。当映射关系解除后,对原来映射地址的访问将导致段错误发生。

 int msync ( void * addr , size_t len, int flags)

一般说来,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作。可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致。

参数:
start: 映射区的开始地址,设置为0时表示由系统决定映射区的起始地址。

length: 映射区的长度。

prot: 期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算(“|”)合理地组合在一起

  PROT_EXEC //页内容可以被执行
  PROT_READ //页内容可以被读取
  PROT_WRITE //页可以被写入
  PROT_NONE //页不可访问

flags: 指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体

  MAP_FIXED //使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。
     //并且起始地址必须落在页的边界上。
  MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。
  MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。
  MAP_DENYWRITE //这个标志被忽略。
  MAP_EXECUTABLE //同上
  MAP_NORESERVE //不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。
  MAP_LOCKED //锁定映射区的页面,从而防止页面被交换出内存。
  MAP_GROWSDOWN //用于堆栈,告诉内核VM系统,映射区可以向下扩展。
  MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联。
  MAP_ANON //MAP_ANONYMOUS的别称,不再被使用。
  MAP_FILE //兼容标志,被忽略。
  MAP_32BIT //将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。
  MAP_POPULATE //为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。
  MAP_NONBLOCK //仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。

fd: 有效的文件描述词。一般是由open()函数返回,其值也可以设置为-1,此时需要指定flags参数中的MAP_ANON,表明进行的是匿名映射。

offset: 被映射对象内容的起点。

函数返回值:
成功执行时,mmap()返回被映射区的指针,munmap()返回0。失败时,mmap()返回MAP_FAILED[其值为(void *)-1],munmap返回-1。
errno被设为以下的某个值:

  EACCES:访问出错
  EAGAIN:文件已被锁定,或者太多的内存已被锁定
  EBADF:fd不是有效的文件描述词
  EINVAL:一个或者多个参数无效
  ENFILE:已达到系统对打开文件的限制
  ENODEV:指定文件所在的文件系统不支持内存映射
  ENOMEM:内存不足,或者进程已超出最大内存映射数量
  EPERM:权能不足,操作不允许
  ETXTBSY:已写的方式打开文件,同时指定MAP_DENYWRITE标志
  SIGSEGV:试着向只读区写入
  SIGBUS:试着访问不属于进程的内存区

系统调用mmap()用于共享内存的方式:
使用普通文件提供的内存映射:适用于任何进程之间;此时,需要打开或创建一个文件,然后再调用mmap();典型调用代码如下:

   fd=open(name, flag, mode); 
   if(fd<0) 
   ... 
   ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0);

程序示例:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#define LCD_SIZE 800*480*4
//主函数
int main(void)
{
	//找到并打开lcd设备文件
	int lcd_fd = open("/dev/ubuntu_lcd",O_RDWR);
	//判断打开成功否
	if(lcd_fd == -1)//失败
	{
		perror("打开ubuntu_lcd失败");
		exit(-1);
	}
	
	/*开启映射*/
	char *map_ptr = mmap(NULL, LCD_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, lcd_fd, 0);//void *的指针可以转换为任意型
	//检测是否映射成功
	if(map_ptr == MAP_FAILED)
	{
		perror("映射失败");
		exit(-1);
	}
	/*解除映射*/
	munmap(map_ptr, LCD_SIZE);
	
	return 0;
}

六、bmp图片格式详解

BMP图片格式
Bmp图片格式组成部分:bmp文件头(14 bytes) + 位图信息头(40 bytes) + 调色板(由颜色索引数决定) + 位图数据(由图像尺寸决定)

程序实例:

typedef struct {			//bmp图片文件头信息封装 
	// 位图文件头 14bytes
	u8  bit_file_type[2];	//位图文件类型:'BM'->0x4d42 
	u32 file_size;	  		//整个文件大小 
	u16 reserved1;	  		//保留 
	u16 reserved2;	  		//保留 
	u32 offset;		  		//文件头到位图数据之间的偏移量 

	// 位图信息头 40 bytes
	u32 head_size;			//位图信息头长度 
	u32 width;		  		//位图宽度 
	u32 height;		  		//位图高度 
	u16 bit_planes;	  		//位图位面数 
	u16 bits_per_pixel; 	//每个像素的位数 
	u32 compression;		//压缩说明 
	u32 image_size;			//位图数据大小 
	u32 h_res;				//水平分辨率 
	u32 v_res;				//垂直分辨率 
	u32 color_palette;		//位图使用的颜色索引数 
	u32 vip_color;			//重要的颜色索引数目 

}bmp_head;

七、C语言笔记

(一)标准IO函数 time()、localtime()、gmtime()获取当前系统时间

time()函数

函数功能:获取从1970年1月1日00:00:00到现在走过的秒数

1、time返回的是:格林尼治时间1970年1月1日00:00:00到当前时刻的时长,时长单位是秒。
2、time函数原型:

time_t time(time_t *tloc);
//返回值与参数作用相同,都用来获取时间
//time_t:长整形long
//1970年1月1日00:00:00到当前时刻的时长,时长单位是秒。

3、time的两种用法:

 time_t t1,t2; //分别声明两种使用方式的赋值对象
 t1=time(0);   //第一种使用方式
 time(&t2);    //第二种使用方式

4、time_t既然是长整形,那么必然有它的计时范围,最晚不能超过2038年1月18日19时14分07秒

ctime()函数

函数功能:将time()函数的返回值进行解析,然后返回一串时间字符串
1、函数原型:

char *ctime(const time_t *timep);

2、用法:将time()函数的返回值作为参数传入,用字符型数组接收返回值
常用代码:

tim = time(NULL);//获取时间

char time_buf[100];
time_buf = ctime(tim);//解析
printf("ctime: %s\n",time_buf);

执行结果:
在这里插入图片描述

localtime()、gmtime函数

函数功能:将time()函数返回的数值解析成当前日期

1、函数原型

struct tm *localtime(const time_t *timep);
struct tm *gmtime(const time_t *timep);

2、参数:timep:为time()函数的返回值

3、返回值:struct tm

struct tm {																																									
          int tm_sec;       /* 秒 – 取值区间为[0,59] */
          int tm_min;       /* 分 - 取值区间为[0,59] */
          int tm_hour;      /* 时 - 取值区间为[0,23] */
          int tm_mday;      /* 一个月中的日期 - 取值区间为[1,31] */
          int tm_mon;       /* 月份(从一月开始,0代表一月) - 取值区间为[0,11] */
          int tm_year;      /* 年份,其值等于实际年份减去1900 */
          int tm_wday;      /* 星期 – 取值区间为[0,6],其中0代表星期天,1代表星期一,以此类推 */
          int tm_yday;      /* 从每年的1月1日开始的天数 – 取值区间为[0,365],其中0代表1月1日,1代表1月2日,以此类推 */
          int tm_isdst;     /* 夏令时标识符,实行夏令时的时候,tm_isdst为正。不实行夏令时的进候,tm_isdst为0;不了解情况时,tm_isdst()为负。*/
};

实例代码

#include <stdio.h>
#include <time.h>

//time返回的是:格林尼治时间1970年1月1日00:00:00到当前时刻的时长,时长单位是秒。
//localtime() 、gmtime() 解析time

int main()
{
	int h, m, s;//时分秒 
	struct tm *local;//用localtime函数获取时间 
    time_t t1,t2; //分别声明两种使用方式的赋值对象
	
    t1=time(0);   //第一种使用方式
    //time(&t2);    //第二种使用方式
    printf("t1=%ld\n",t1);
   
   
	/****传统方法解析*****/
	s = t1 % 60;
    m = (t1 % 3600) / 60;
    h = (t1 % (3600 * 24)) / 3600 + 8;
    h %= 24;
    printf("time: %02d:%02d:%02d\n", h, m, s);
    
    /****localtime()函数解析***/ 
    local=localtime(&t1);//解析t1秒数,保存到结构体loccal
    printf("Localtime is: %d年 %d月 %d日 %d:%d:%d\n",local->tm_year+1900,local->tm_mon+1,
		local->tm_mday,local->tm_hour,local->tm_min,local->tm_sec);
    
     /****gmtime()函数解析***/ 
	local=gmtime(&t1);
	printf("gmtime is:  %d年 %d月 %d日 %d:%d:%d\n",local->tm_year+1900,local->tm_mon+1,
		local->tm_mday,local->tm_hour+8,local->tm_min,local->tm_sec);
    return 0;
}

执行结果:
在这里插入图片描述

(二) 利用opendir函数实现tree功能

在ubuntu中tree的效果图
在这里插入图片描述
编写代码:

#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>


int fun(const char *path_name,int i);

int main(int argc,const char *argv[])
{
	if(argc != 2)
	{
		perror("input error\n");
		return 0;
	}	
	fun(argv[1],0);
	
	return 0;
}


int fun(const char *path_name,int i)
{
	DIR *dp = opendir(path_name);
	struct dirent*ep = NULL;
	char phname[512]={0};
	
	while(1)
	{
		ep = readdir(dp);
		if(ep==NULL)
		{
			return -1;
		}
		if(ep->d_name[0]!='.')
		{
			//&& (readdir(dp)!=NULL)
			for(int n=0; n<i; n++)
			{
				if(n%2==0)
					printf("|");
				printf("  ");	
			}
			printf("|__%s",ep->d_name);	
			printf("\n");
			if(ep->d_type==4)
			{
				sprintf(phname,"%s/%s",path_name,ep->d_name);
				fun(phname,i+2);
			}
		}
		//printf("%d\n",ep->d_type);	
		
		
	}
}

编译运行,虽然不是完全一模一样,但是也实现了tree的效果
在这里插入图片描述

(三)Linux笔记:使用stat函数实现ls -l的功能(getpwuid函数 getgrgid函数使用)

stat函数:获取文件信息

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

int stat(const char *pathname, struct stat *buf);
struct stat {
               dev_t     st_dev;         /* ID of device containing file */
               ino_t     st_ino;         /* inode number */
               mode_t    st_mode;        /* protection */
               nlink_t   st_nlink;       /* number of hard links */
               uid_t     st_uid;         /* user ID of owner */
               gid_t     st_gid;         /* group ID of owner */
               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 512B blocks allocated */
               }

getpwuid函数:解码所有者信息(解析struct stat中的st_uid数据)

#include <sys/types.h>
#include <pwd.h>
struct passwd *getpwuid(uid_t uid);
struct passwd {
               char   *pw_name;       /* username */
               char   *pw_passwd;     /* user password */
               uid_t   pw_uid;        /* user ID */
               gid_t   pw_gid;        /* group ID */
               char   *pw_gecos;      /* user information */
               char   *pw_dir;        /* home directory */
               char   *pw_shell;      /* shell program */
           };

getgrgid函数:解码所属组信息(解析struct stat中的 st_gid数据)

#include <sys/types.h>
#include <grp.h>

struct group *getgrgid(gid_t gid);
struct group {
               char   *gr_name;        /* group name */
               char   *gr_passwd;      /* group password */
               gid_t   gr_gid;         /* group ID */
               char  **gr_mem;         /* NULL-terminated array of pointers
                                          to names of group members */
           };

localtime函数:解码上次访问时间信息 (解析struct stat中的 st_atime数据)

#include <time.h>
struct tm *localtime(const time_t *timep);
struct tm {
               int tm_sec;    /* Seconds (0-60) */
               int tm_min;    /* Minutes (0-59) */
               int tm_hour;   /* Hours (0-23) */
               int tm_mday;   /* Day of the month (1-31) */
               int tm_mon;    /* Month (0-11) */
               int tm_year;   /* Year - 1900 */
               int tm_wday;   /* Day of the week (0-6, Sunday = 0) */
               int tm_yday;   /* Day in the year (0-365, 1 Jan = 0) */
               int tm_isdst;  /* Daylight saving time */
           };

使用stat函数实现ls -l的功能例程:

//使用stat函数实现ls -l的功能
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>

int  main(int argc,const char * argv[])
{
	struct stat info;
	stat(argv[1],&info);
	//打印类型,权限
	switch(info.st_mode & S_IFMT)
	{
		case S_IFREG:printf("-");break;
		case S_IFDIR:printf("d");break;
		case S_IFLNK:printf("l");break;
		case S_IFCHR:printf("c");break;
		case S_IFBLK:printf("b");break;
		case S_IFIFO:printf("p");break;
		case S_IFSOCK:printf("s");break;
	}
	char rwx[]={'r','w','x'};
	for(int i=0; i<10; i++)
	{
		printf("%c",info.st_mode & (0400>>i) ? rwx[i%3] : '-');
	}
	//打印所有者,所属组 
	struct passwd *pswd;
	pswd=getpwuid(info.st_uid);
	printf(" %s",pswd->pw_name);
	
	struct  group  *grp;
	grp=getgrgid(info.st_gid);
	printf(" %s",grp->gr_name);
	
	//打印文件大小
	printf(" %ld ",info.st_size);
	//打印最近时间 
	struct tm *atime;
	atime = localtime(&info.st_mtime);
	printf("%d-%d-%d %d:%d "
		,atime->tm_year+1900
		,atime->tm_mon+1,atime->tm_mday
		,atime->tm_hour,atime->tm_min);
	//打印文件名
	printf(" %s\n",argv[1]);
	
	return 0;
}

编译运行 :
在这里插入图片描述

(四)Linux系统笔记:内核链表

内核链表是在Linux开发中经常用到的一种存储结构,它比普通的单向链表、双向链表更加强大,更加好用。

一、单向链表与双向链表

1、单向链表:
单向链表节点结构体:

struct link_list
{
	int data;//数据域
	struct link_list * next;//指针域
};

单向循环链表结构如图所示:

在这里插入图片描述

2、双向链表
双向链表节点结构体:

struct link_list
{
	int data;//数据域
	struct link_list * next;//指针域,指向下一个节点
	struct link_list * prev;//指针域,指向上一个节点
};

双向循环链表结构如图所示:
在这里插入图片描述
二、内核链表简介

1、内核链表简介:
不难发现,不管是单向链表还是双向链表,数据域的数据类型一定是一致的,两个不同类型的链表节点不能连接在一起,内核链表颠覆了传统的观念,把传统的链表中的“链”抽象出来,成为只包含前后指针的纯粹链表。

标准内核链表结构体:(小结构体)

struct list_head {
	struct list_head *next, *prev;
};

内核链表图示:
在这里插入图片描述
看到这可能有人要问了,内核链表,就这,怎么存储数据呢,还没完。

光有这条链表是毫无意义的,就好比一根绳子,绳子上却没有栓任何东西。于是,我们开始往绳子上栓点东西,也就是将这条绳子嵌入到一个具体节点中。

内核链表节点结构体:(大结构体)

struct kernel_list{
	// 1.数据域,可以自己定义想要的类型
	int data;
	// 2.指针域(小结构体)
	struct list_head list;
};

内核链表结构图示:外面的黄色包含的是大结构体,里面的两个小指针就是小结构体,黄色区域可以认为是存储数据的地方
在这里插入图片描述
2、编写内核链表
编写内核链表之前需要包含一个头文件:list.h
这个头文件并不包含在系统默认头文件路径下,我们用locate命令到ubuntu查找一下

命令: locate list.h

在这里插入图片描述找到白色那个路径下的
/usr/src/linux-headers-4.15.0-99/include/linux/list.h
把 list.h 拷贝到你的代码目录下
在这里插入图片描述

三、list.h 中的函数说明

接下来介绍一下list.h的一些重要函数

1、内核链表的结构体:

	struct list_head {
	struct list_head *next, *prev;
};

这个结构体是用来嵌套到大个数据结构体当中的,专门用来记载链表中的上下具体位置

2、初始化头节点:INIT_LIST_HEAD

#define INIT_LIST_HEAD(ptr) do { \
	(ptr)->next = (ptr); (ptr)->prev = (ptr); \
} while (0)

宏函数功能:初始化头结点,让头结点的next及prev全部指向自己
ptr:传入头结点中的 struct list_head 的地址
比如我们在调用的 &head->list

3、list_add

static inline void list_add
(struct list_head *new, struct list_head *head)

函数功能:将new这个节点添加到head这个节点之后的位置

4、list_add_tail

static inline void list_add_tail
(struct list_head *new, struct list_head *head)

函数功能:将new这个节点,添加到head这个节点之前的位置

5、list_del

static inline void list_del(struct 
list_head *entry)

函数功能:在entry所在的链表中移除entry节点

ps:调用这个函数会将entry的登记链表位置的next,prev清零

6、list_move

static inline void list_move(struct 
list_head *list,struct list_head *head)

函数功能:移动list这个节点到head这个节点之后(不在同一个链表当中也可以)

7、list_move_tail

static inline void list_move_tail(struct
 list_head *list,struct list_head *head)

函数功能:移动list这个节点到head这个节点之前(不在同一个链表当中也可以)

8、list_empty

static inline int list_empty(struct
list_head *head)

函数功能:
判断链表是不是空的

9、list_splice

static inline void list_splice(struct
 list_head *list, struct list_head *head)

函数功能:
合并两条链表,将list这条链表的元素添加到head里,list头结点不会添加进去head

10、list_splice_init

static inline void list_splice_init(struct 
list_head *list,struct list_head *head)

函数功能:
与上面函数一样,但是list这个链表会被清空

11、list_entry

#define list_entry(ptr, type, member)

函数功能:
由小结构地址(struct list_head *)获取到对应的大结构体的地址(包含了这个struct list_head的大结构体)

ptr:小个结构体的地址(struct list_head *)
type:大个结构体的类型,大个结构体的定义的类型的名字
member:小个结构体在大个结构体当中的名字

返回值:返回大个结构体的内存地址

12、list_for_each

#define list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); \
pos = pos->next)

函数功能:
遍历链表(不安全模式,更改pos值就会异常),其实这个宏函数就是个for循环,当成for循环来用

pos:用来记录遍历的哪个节点的地址的,类型为 (struct list_head *)这个pos从head->next开始
ps:此时一定不能更改pos的值,如果更改了pos的值,这条链表就异常了

head:链表的头结点的地址,类型为 (struct list_head *)

13,list_for_each_safe

#define list_for_each_safe(pos, n, head) \
for (pos = (head)->next, n = pos->next; pos != (head); \
pos = n, n = pos->next)

函数功能:
遍历链表(安全模式,可以更改pos的值,但是不能更改n的值),其实这个宏函数就是个for循环,当成for循环来用

pos:用来记录遍历的哪个节点的地址的,类型为 (struct list_head *)这个pos从head->next开始
ps:此时可以更改pos的值

head:链表的头结点的地址,类型为 (struct list_head *)

n:缓冲变量,类型为(struct list_head *),专门用来记录pos的下一个位置,这样使用pos就不会有异常了

四、内核链表实例

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>

#include "list.h"

typedef struct kernel_list{
	// 1.数据域
	int data;
	// 2.指针域(小结构体)
	struct list_head list;
}kl_st, *kl_pt;

// 初始化内核链表头节点
kl_pt kl_list_init(void);
// 添加数据到内核链表尾
void kl_list_add_tail(kl_pt head, int new_data);
// 删除指定数据
void kl_list_add_del(kl_pt head, int del_data);
// 显示链表数据
void kl_list_show(kl_pt head);

int main(int argc, const char *argv[])
{	
	// 1.初始化一条内核链表头节点
	kl_pt head = kl_list_init();
	// 2.操作
	int cmd;
	while(1)
	{
		printf("Pls Input: ");
		scanf("%d", &cmd); while(getchar()!='\n');
		if(cmd > 0)	//添加
		{
			kl_list_add_tail(head, cmd);
		}
		else if(cmd < 0)	//删除
		{
			kl_list_add_del(head, -cmd);
		}
		if(cmd == 0)	//退出
		{
			exit(0);
		}
		kl_list_show(head);	//显示链表数据
	}
	return 0;	
}

// 初始化内核链表头节点
kl_pt kl_list_init(void)
{
	// 1.申请堆空间给头节点
	kl_pt h = malloc(sizeof(kl_st));
	if(h == NULL)
	{
		perror("head malloc failed");
		return NULL;
	}
	// 2.使其指针域,都指向自身
	// h->list.next = &(h->list);
	// h->list.prev = &(h->list);
	INIT_LIST_HEAD(&h->list);
	
	// 3.将头节点地址返回
	return h;
}

// 添加数据到内核链表尾
void kl_list_add_tail(kl_pt head, int new_data)
{
	// 1.申请堆空间给新节点,清空并把数据给入
	kl_pt new_node = malloc(sizeof(kl_st));
	if(new_node == NULL)
	{
		perror("new_node malloc failed");
		return;
	}
	memset(new_node, 0, sizeof(kl_st));
	new_node->data = new_data;
	
	// 2.调用内核链表函数添加函数
	list_add_tail(&new_node->list, &head->list);
}

// 删除指定数据
void kl_list_add_del(kl_pt head, int del_data)
{
	if(list_empty(&head->list))
	{
		printf("链表为空\n");
		return ;
	}
	kl_pt pos;
	pos=head;
	//遍历找数据
	while(1)
	{
		//printf("%d == %d\n",pos->data,del_data);
		if(pos->data!=del_data)
		{
			pos=list_entry(pos->list.next, kl_st, list);
			if(pos == head)
			{
				printf("没找到%d\n",del_data);
				return ;
			}
		}
		else 
		{
			list_del(&pos->list);
			break;
		}
		printf("%d == %d\n",pos->data,del_data);
	}
	

}

// 显示链表数据
void kl_list_show(kl_pt head)
{
	//遍历链表
	kl_pt get_node;
	
	struct list_head *pos;	//遍历小结构体
	list_for_each(pos, &head->list)
	{
		// 通过小结构体地址,获得大结构体地址
		get_node = list_entry(pos, kl_st, list);
		printf("%d ", get_node->data);
	}
	printf("\n");
}

进程与线程的概念

1,进程:

(1)正在运行的一个程序
(2)它代表一种资源的载体(独立的应用的应用程序)
(3)资源管理的最小单位
(4)每一个进程独立包括虚拟内存,文件描述符资源,信号资源等,不与其他进程共享资源
应用场景:
1,调度第三方程序
2,调度其他程序的时候,我们需要传输一定的资源或者是指令过去给另外一个程序时,我们需要应用进程间通信
3,启用服务

2,线程:

(1)系统调度的最小单位(CPU在轮询指令运行的最小单位)
(2)进程下面的一个子级单位(所有的线程都是在进程的基础上运行的),一个进程当中,可以运行多个线程
(3)所有的线程共享进程的所有资源
(4)每一个线程独立一片栈空间(栈空间默认大小为8M)
应用场景:
基本上所有的多任务的开发,优先采用多线程

查看线程命令:

  • ps -Lf 查看进程id +线程号。

3、两者的差别与共同点:

  • 进程:有独立的 进程地址空间。有独立的pcb。 分配资源的最小单位。

  • 线程:有独立的pcb。没有独立的进程地址空间。 最小单位的执行。

线程独享与共享:

  • 独享 栈空间(内核栈、用户栈)

  • 共享 ./text./data ./rodataa ./bsss heap —> 共享【全局变量】(errno)

进程

1,进程的创建:

进程的创建过程我们可以理解成为“分身”的过程
fork:

#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);

函数功能:
创建出一条子进程
函数在执行的过程当中,会将父进程的资源复制一份,放到子进程里面去运行,其中下面是会被子进程继承的资源
1,父进程的运行的用户的ID跟组ID
2,环境变量(库路径,命令路径,命令路径等等)
3,进程组ID跟会话ID
4,打开的文件描述符
5,信号响应函数
6,虚拟内存(堆,栈,程序段落等等)
以下属性就是独立,没有继承的:
1,进程ID
2,记录锁(文件锁)
3,挂起的信号
返回值:
成功创建子进程的情况下会将0返回给子进程,子进程的PID返回父进程,如果失败返回-1,子进程不会被创建

2、回收子进程

等待函数wait:

pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);

函数功能:
回收子进程

pid:>0:等待子进程为pid退出(不是一个进程组也没关系)
pid:0:等待该调用者进程的任意一个子进程(如果不是一个进程组的子进程不接受)
pid:-1:等待任意的一个子进程(就算不是一个进程组的子进程也接收)
pid:<-1:等待pid这个数值的绝对值所对应的进程组ID里面的子进程
参数:

  • wstatus:用来存放子进程当中的返回状态值(exit里面的返回值就会放在这里面),可以通过多个函数来分析这个值:
    在这里插入图片描述

  • options:0则代表正常阻塞等待子进程退出
    在这里插入图片描述

返回值: 成功则返回接收的子进程的ID值,失败则返回-1

获取进程ID系列函数:
getpid:获取当前的程序的进程ID
getppid:获取当前程序的父进程ID
getpgid(pid):获取pid这个进程的组id
setpgrp:设置进程组ID为自己进程的ID

3、进程退出系列函数:

exit:
#include <stdlib.h>
void exit(int status);

参数:
status:会将这个值&0377后传输给等待回收这个进程的人(也就是调用wait系列函数的人)
退出本调用这个函数的进程
退出之前先执行atexit或者是on_exit函数所注册过的退出处理函数群,然后再清楚所有标准IO缓冲区,再退出进程

_exit:

#include <unistd.h>
void _exit(int status);

参数:
status:会将这个值&0377后传输给等待回收这个进程的人(也就是调用wait系列函数的人)
直接退出调用这个函数的进程
直接退出,中间不经过任何操作

atexit:

#include <stdlib.h>
int atexit(void (*function)(void));

简化版本的退出处理函数,在程序调用exit函数或者是main函数return的时候去执行这个函数所注册过的退出处理函数。
参数:
function:这是一个 void (*)(void)函数指针,要求传入函数类型格式,并且将函数名放在这里即可
细节:
按照后入先出的原则执行注册的退出处理函数,可以注册多个

on_exit:

#include <stdlib.h>
int on_exit(void (*function)(int , void *), void *arg);

复杂版本的退出处理函数,在程序调用exit函数或者是main函数return的时候去执行这个函数所注册过的退出处理函数。
参数:
function:这是一个 void (*)(int,void *)函数指针,要求传入函数类型格式,并且将函数名放在这里即可,其中int的这个参数放的是exit或者是main函数return的数值(status值),void *则是on_exit第二个参数。
arg:传输给function的第二个参数
细节:
按照后入先出的原则执行注册的退出处理函数,可以注册多个

4、exec系列函数的用法

exec系列函数:

int execl(const char *path, const char *arg, ...
                       /* (char  *) NULL */);
int execlp(const char *file, const char *arg, ...
                       /* (char  *) NULL */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);

都是用来调度第三方程序:
execl: 调用一个指定路径下的应用程序,传输给指定应用程序的参数通过后面的一个一个字符串传输

execlp: 调用一个命令,传输给命令的参数通过后面的一个一个字符串传输

execv: 调用一个指定路径下的应用程序,传输给指定应用程序的参数通过字符串数组传输

execvp: 调用一个命令,传输给命令的参数通过字符串数组传输
一经过上面的函数调用,本进程会丢失原本的资源(虚拟内存资源等等),会保留类似于进程ID,文件资源(除非一开始打开的时候声明调用exec系列函数的时候自动关闭(open函数可以声明O_CLOEXEC))重新加载新的程序的资源
注意,上面的参数中,如果是execl,跟execlp必须用NULL指针作为最后的参数;如果是execv,execvp里面的argv这个字符串数组,最后一个元素的值也必须是NULL指针。
参数:
path:具体需要调用的程序的位置及名字
arg:传入给应用程序的第一个参数,一般为程序本省的名字
…:需要继续传入应用程序的参数,可以传入多个参数
file:需要调用的命令名字
argv:多个传入参数所形成的一个字符串数组,而且最后一个元素必须以NULL指针作为结尾
返回值:
成功则没有返回值,因为调用这个函数的原本的程序会被覆盖掉,失败则返回-1,errno会被设置。

vfork:
专门为了exec系列函数服务的一个创建进程的函数,功能跟参数与fork是一模一样,只有一下操作是不一样的:
1,vfork成功创建子进程,子进程跑起来,引用的内存是父进程的内存
2,vfork成功创建子进程之后,父进程陷入睡眠
3,当vfork创建出来的子进程调用了exec系列函数,去加载第三方程序的时候或者是子进程结束,父进程才会被唤醒
vfork这样的操作是为了节约fork函数创建子进程而进行的内存拷贝这一环节的时间(vfork子进程是不会拷贝父进程的虚拟内存的内容),

5、守护进程/精灵进程(服务)

1,在后台默默的运行
2,不依赖于所谓的命令终端
3,本身没有什么外界的限制

实现的过程:

/*1,先忽略终端的挂断信号对这个程序的影响*/
	signal(SIGHUP, SIG_IGN);

	/*2,新建一个子进程,让父进程退出,这个时候子进程就不会接收到控制终端的信号跟内容*/
	pid = fork();
	if(pid > 0)
		exit(0);

	/*3,新建一个会话,脱离开原本的会话(也就是这个控制终端)*/
	setsid();

	/*下面的动作都是为了让我们的程序更加的纯净*/

	/*4,新建多一个子进程,脱离开会话管理的权限*/
	pid = fork();
	if(pid > 0)
		exit(0);

	/*5,再让这个子进程脱离开原本的进程组*/
	setpgrp();


	/*6,关闭掉原本的所有文件描述符*/
	max_fd = sysconf(_SC_OPEN_MAX);
	for(i=0; i<max_fd; i++)
		close(i);

	/*7,改变工作路径到根目录*/
	chdir("/");

	/*8,改变原有的掩码,成为没有任何权限影响的0*/
	umask(0);

系统日志:
openlog函数打开日志,syslog写入日志,closelog关闭日志。

#include <syslog.h>
void openlog(const char *ident, int option, int facility);
void syslog(int priority, const char *format, ...);
void closelog(void);

ident:打开日志文件的时候,跟日志文件说你是谁

openlog函数发起到系统日志服务器的连接,参数ident是要向每个消息加入的字符串,典型的情况是要设置成程序的名称。
参数option是下面一个或多个值的“或”
LOG_CONS 如果系统日志服务器不能用,写入控制台
LOG_NDELAY 立即打开连接,正常情况下,直到发送第一条消息才打开连接
LOG_PERROR 打印输出到stderr
LOG_PID 每条消息中包含进程 PID

参数facitity指定程序发送消息的类型。
LOG_AUTHPRIV 安全授权消息
LOG_CRON 时钟守护进程:cron和at
LOG_DAEMON 其他系统守护进程
LOG_KERN 内核消息
LOG_LPR 打印机子系统
LOG_MAIL 邮件子系统
LOG_USER 默认

参数priority指定消息的重要性。
LOG_EMERG 系统不能使用
LOG_ALERT 立即采取措施
LOG_CRIT 紧急事件

LOG_ERR 出错条件
LOG_WARNING 警告条件
LOG_NOTICE 正常但重大事件
LOG_INFO 信息消息
LOG_DEBUG 调试信息

syslog代码例子:
yslog(LOG_INFO, “my daemin is OK”);

严格的说,openlog和closelog是可选的,因为函数syslog在首次使用的时候自动打开日志文件。
linux系统上日志文件通常是/var/log/syslog。

线程

1、创建线程

函数原型 :

 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); 

函数功能: 创建一条新线程
头文件: #include <pthread.h>

参数:

  • thread: 新线程的 TID(线程号,传出参数)
  • attr: 线程属性
  • start_routine: 线程例程
  • arg: 线程例程的参数
  • 返回值 :成功 0 失败 errno

pthread_create函数 创建一个新线程。 其作用,对应进程中fork() 函数。

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

返回值:成功:0; 失败:错误号 -----Linux环境下,所有线程特点,失败均直接返回错误号。
参数:

pthread_t:当前Linux中可理解为:typedef unsigned long int pthread_t;
参数1:传出参数,保存系统为我们分配好的线程ID
参数2:通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。
参数3:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。
参数4:线程主函数执行期间所使用的参数,如要传多个参数, 可以用结构封装。

#include <pthread.h>//线程头文件
#include <unistd.h>//sleep()头文件
#include <stdio.h>
//线程执行函数
void *fun(void *arg)
{
	while(1)
	{
		printf("this is pthread!\n");
		sleep(1);
	}
}

int main(void)
{
    pthread_t p_id;//创建线程
	pthread_create(&p_id,NULL,fun,NULL);
	while(1)
	{
		printf("this is main!\n");
		sleep(1);
	}
}

编译命令必须加-lpthread,链接线程库
gcc text.c -lpthread
会自动生成a.out可执行文件
在这里插入图片描述

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

化作尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值