一、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_RDONLY | OWRONLY | O_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可执行文件