Linux开发—基础
一,环境搭建
1,编译器gcc ,g++ 安装
用gcc 编译c++代码,逻辑是gcc调用g++去编译c++代码。
命令:
- 1,设置root 密码: sudo passwd root,请牢记这个密码,后面还需要使用,如果是云服务器密码建议复杂一点。
sudo passwd root
- 2,进入root账户 su,提升权限,($变成#号)
su
- 3,安装gcc 和g++编译器:apt-get install gcc g++,会科技上网的走墙一下,不会的把源切到国内,比如:清华大学的源,可在网上搜索资料。
apt install gcc
apt install g++
- 4,查看当前的gcc g++版本,gcc --version ,g++ --version
gcc --version
g++ --version
- 5,验证:g++ -o test main.cpp ,./test,意思是:g++编译 main.cpp文件, -o 生成一个test
g++ -o test main.cpp
./test
2,ssh服务安装
这个服务 用户后续的代码编写和远程运行、调试。
命令:
- 1,安装服务程序:sudo apt install openssh-server
sudo apt install openssh-server
- 2,安装客户端程序:sudo apt install openssh-client,
因为客户端有密钥生成器,可用去创建一些密钥,证书等
sudo apt install openssh-client
- 3,修改配置文件:在root目录的,etc/目录下,先
su
到root用户,然后进入到etc文件夹cd etc
,查找到sshls -l | grep ssh
,再然后在ssh文件夹里找sshd_config文件,vim 进到文件vim sshd_config
,修改完成后:wq
保存并退出
su
cd etc
ls -l | grep ssh
cd ssh
vim sshd_config
Port
22
LoginGraceTime 2m
PermitRootLogin
yes
PubkeyAuthentication yes
PasswordAuthentication yes
ChallengeResponseAuthentication no
UsePAM yes
X11Forwarding yes
PrintMotd no
AcceptEnv LANG LC_*
Subsystem sftp /usr/lib/openssh/sftp-server
(红色内容是主要修改的地方,其他地方自行对比,如果不一致,请参考上面文档)
3,ssh服务启动:
- 方式一,输入命令
C:\Windows\System32\bash.exe -c "sudo service ssh start"
- 方式二,建立bat文件,将上面方式一的命令写入其中,然后放入文件夹
C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp
或者C:\Users\用户名\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup
,用户名填自己的用户名,这样就可以开机启动服务
二,Windows下开发Linux
1,创建Linux控制台项目
-
1,MicroSoft Visual Studio创建Linux桌面程序
-
2,工具-选项-跨平台-连接管理器添加Linux主机,主机:填Ubuntu所在主机ip,用户名:root,密码:设置的Ubuntu密码,我这里一直连接不上,后来查资料,发现可能是:ipv6的问题
-
3,127.0.0.1是本地localhost主机名,如果对方系统添加了gcc和g++,非Ubuntu系统也可以建立连接。
-
4,成功建立连接(
在成功的路上遇到点小问题,详见总结 一
):
-
5,工程属性配置:
-
6,目前Linux系统目前大部分是 x64的架构,很少有32位的了,不用arm64和x86了。然后生成解决方案:
-
7,生成解决方案,本地会有一份,在Windows环境下无法打开
-
8,编译文件验证,Ubuntu中找到文件:
与visual studio工具中main.cpp文件内容一样:
Ubuntu打开编译产物:
-
8,针对刚才visual studio工具的错误内容,是Linux服务器缺少gdb zip:
切换到Ubuntu服务器,sudo apt install gdb,sudo apt install zip
,执行gdb -v
查看安装版本
sudo apt install gdb
sudo apt install zip
2,Linux系统API认识(标准库函数)-基础
1,字符串函数
- 头文件<ctype.h> 下。判断字符类型,大小写字母,中英文,是否为空格等。
2,数据转换函数
- 头文件<stdlib.h> 下。将字符串转换成浮点数,整型数,无符号整数等等,将浮点数,整数等转换成字符串。注意,有些是C++11标准的东西。
3,控制台函数
- 格式化输入输出,printf,vprintf,scanf,sprintf,snprintf,sscanf,fprintf,scanf
4,权限控制函数
-
头文件:<unistd.h>,<sys/types.h>,
-
Linux的权限:(文件,进程,内存
Linux下一切都是文件
)- 设置用户权限,S 提权和降权
- 设置组权限, s 修改我当前的组
- 仅所有者可删除权限,t
- 读取权限, (r 4)
- 写入权限, (w 2)
- 执行权限, (x 1)
-
(所有者,所在组,其他组)
-
查看进程,
ls -l /proc
-
uid_t geteuid(void),获得有效的用户识别码
-
uid_t getuid(void),获取真实用户的识别码
-
gid_t getegid(void),获取有效的组识别码
-
gid_t getgid(),获取真实的组识别码
-
int setuid(uid_t uid),设置真实的用户识别码
-
int seteuid(uid_t uid),设置有效的用户识别码
-
int setreuid(uid_t ruid, uid_t euid),设置真实及有效的用户识别码
-
用户权限说明:
- 0 root 最高权限,
- 1000~10000(不包含10000) system 数据库 服务 tty 保留用户
- 10000(包括)以上,其他用户 网络用户, Android每个app会分配一个用户(系统应用例外)
-
权限不足时,无法产生效果,提权需要该文件属于高级别的用户或用户组,即有效用户有更高的权限,或以更高权限的用户来执行。
-
int setregid(gid_t rgid,gid_t egid),设置真实及有效的组识别码
-
int setegid(gid_t egid),设置有效的组识别码
-
int setgid(gid_t gid),设置真实的组识别码
-
pid_t setsid(void),创建会话ID,守护进程的关键调用函数,用户和组要有足够的权限。或者创建新会话(当前进程是子进程才可以创建新会话成功)。
查看所有进程:ps -Al
ps -Al
5,I/O函数
- 头文件<sys/types.h>,<unistd.h>,<sys/stat.h>,<fcntl.h>,<stdlib.h>,
- *int open(const char pathname, int flags, mode_t mode),打开文件,相对路径,标识,权限(有O_CREAT时,mode才有作用)
- flags:
多个标识用 | 连接
- O_RDONLY:只读打开
- O_WRONLY:只写打开
- O_RDWR:读,写打开
- O_CREAT:若文件不存在 则创建,需要使用mode选项,来指明新文件的访问权限
- O_APPEND:追加写,如果文件已有内容,这次打开文件所写的数据附加到文件的末尾而不是覆盖原内容
- mode:
多个标识用 | 连接
- S_IRUSR S_IWUSR S_IXUSR 所有者的读写执行
- S_IRGRP S_IWGRP S_IXGRP 所属组的读写执行
- S_IROTH S_IWOTH S_IXOTH 其他用户的读写执行
- flags:
- int creat(const char*pathname, mode_t mode),创建文件
文件最主要的操作:打开、读取、写入、关闭
头文件<unistd.h>
- ssize_t read(int fd, void *buf,size_t count):从打开的文件读取文件数据,允许把多个不同该文件数据,读入到同一个缓冲区,网络读取缓冲区,指向结构体(用于文件格式解析)ELF文件格式,还可以用于网络数据包的解析。
- ssize_t write(int fd, const void *buf, size_t count):从打开的文件写入文件数据,fd可以是网络、串口、文件、内存、其他设备,同一个内存段的不同部分,可以保存到不同的文件中
- int close(int fd):关闭文件,Linux(或其他操作系统)当中可以同时打开的文件数量是有限的,
一般是4096个文件
,不用的时候及时关闭 - int dup(int oldfd):复制文件描述符,输出重定向
- int dup2(int oldfd, int newfd):复制文件描述符到新的文件描述符中去,文件重定向
- int fsync(int fd):文件数据同步(将缓存中的数据写入磁盘),
慎用,比较消耗时间,到磁盘缓存区,一般迁库使用
文件读写位置修改
头文件<sys/types.h>,<unistd.h>
- off_t lseek(int fd, off_t offset, int whence):文件读写位置修改
- whence:
- SEEK_SET:参数offset即为新的读写位置,从开头设置的不能为负数,从SEEK_CUR或SEEK_END时,参数offet允许负值出现
- SEEK_CUR:已目前的读写位置往后增加offset个位移量
- SEEK_END:将读写位置指向文件尾后再增加offset个位移量。
- whence:
头文件<stdlib.h>
- int mkstemp(char *template):创建一个临时文件,template:前缀+XXXXXX,前缀可以是任意字母,后缀必须是六个X,文件是临时的,不保证数据长期有效。
文件锁操作
文件锁是一个建议性的锁,去看文件的时候才有用,不去看时,没什么用。若是要锁有保护效果,先上锁,再写入,写完再解锁,或关闭文件自动解锁。
-
int flock(int fd, int operation):文件锁操作
- operation:
- LOCK_SH:建立共享锁定。多个进程可同时对同一个文件作共享锁定。
- LOCK_EX:建立互斥锁定。一个文件同时只有一个互斥锁定。
- LOCK_UN:解除文件锁定状态。
文件被关闭时,自动解锁
。可能存在时间差。 - LOCK_NB:(NonBlock非阻塞)无法建立锁定时,此操作可不被阻断,马上返回进程。使用于多实例场景。通常与LOCK_SH或LOCK_EX做OR(|)组合。
- operation:
-
意义:
1,防止文件被篡改导致冲突。
2,防止程序多实例(多使用于数据库),要用到LOCK_NB标志来添加锁定
3,多个程序竞争文件控制权(多使用于消息分发、文件处理)
文件控制
头文件<fcntl.h>,control=cntl
- int fcntl(int fd, int cmd, …/* arg */):文件控制
- int fcntl(int fd, int cmd)
- int fcntl(int fd, int cmd, long arg)
- int fcntl(int fd, int cmd, struct flock *lock)==>F_GETLK F_SETLK
- cmd参数:
- F_DUPFD:用来查找 >= 参数arg的最小且仍未使用的文件描述符,且复制参数 fd 的文件描述符。执行成功则返回新复制的文件描述符。新描述符与 fd 共享同一文件表项,但新描述符有它自己的一天文件描述符标志,其中FD_CLOEXEC文件描述标准被清除。参考dup2()函数
- F_GETFD:取得close-on-exec标志。若此标志的FD_CLOEXEC位为0,代表在调用exec()相关函数时 文件将不会关闭。(使用场景:管道)
- F_SETFD:设置close-on-exec标志。该标志以参数 arg 的FD_CLOEXEC位决定。
- F_GETFL:(FL=flags)取得文件描述符状态标志,此标志为open()的参数flags。
- F_SETFL:设置文件描述符状态标志,参数为 arg 新标志,但只允许O_APPEND、O_NONBLOCK和O_ASYNC位的改变,其他位的改变将不受影响。
- F_GETLK:(LK=Lock)取得文件锁定状态。
- F_SETLK:设置文件锁定状态。此时flock结构的 I_type 值必须是F_RDLCK、F_WRLCK或F_UNLCK。若无法建立锁定,返回-1,错误code为:EACCES 或 EAGAIN
- F_SETLKW: 与F_SETLK作用相同,但无法建立锁定时,此调用会一直等到锁定动作成功为止。若再等待锁定的过程中被信号中断,则立即返回-1,错误code为:EINTR
- cmd参数:
6,进程控制函数
进程是操作系统调度的最小单位。
头文件<unistd.h>
1,建立及启动部分
argv的第一个参数,一定要是命令的自身:
- int exec
l
(const char *path, const char *arg, …) - int exec
lp
(const char *file, const char *arg, …) - int exec
le
(const char *path, const char *arg, …, char *const envp[]) - int exec
v
(const char *path , char *const argv[]) - int exec
vp
(const char *file, char *const argv[]) - int exec
ve
(const char *filename, char *const argv[], char *const envp[]),内核级别调用
l
:进程执行的参数,以可变参数的形式给出,这些参数以NULL为最后一个参数。
p
:exec 会将当前的PATH作为一个参考环境变量
e
:进程函数会需要用户来设置这个环境变量
v
:进程函数会用参数数组来传递argv,数组的最后一个成员,必须是NULL,否则无法执行。
- pid_t fork(void),建立一个新的进程,
- 返回值:
- 大于0的数,此时是父进程;
- 等于0的数,此时是子进程;
- 小于0的数,表示调用失败。
- 进程的数量是优先的,一般1 ~ 32768/32767/65535 短整型大小(根据系统),大型服务器可以更多。
- 返回值:
- void abort(void),异常终止,以异常方式结束进程
- void assert(int expression),断言终止,若测试条件不成立则终止进程
- void exit(int status),正常结束进程。会调用atexit和on_exit函数
- void _exit(int status),结束进程执行。
- int atexit(void (*func)(void)),设置程序正常结束前调用的函数
- int on_exit(void (function)(int, void), void *arg),设置程序正常结束前调用的函数。
2,改变堆栈部分
C语言当中处理异常的一种机制,异常捕获,同时清理寄存器内容(做跳转可考虑存放到缓存)。
-
int setjmp(jmp_buf environment),保存目前堆栈环境,jum_buf储存的是寄存器信息
-
void longjmp(jmp_buf environment, int value),跳转到原先setjmp保存的堆栈环境
-
int sigsetjmp(sigjmp_buf env, int savemask),保存目前堆栈环境,可用于线程切换,保存上下文(堆栈、当前寄存器、当前状态、下一条指令位置、栈内存地址),比setjmp内容多了信号量signal。
-
void siglongjmp(sigjmp_buf env, int val),改变进程优先顺序:跳转到原先sigsetjump保存的堆栈环境
3,进程识别部分
头文件<sys/resource.h>
- pid_t getpgid(pid_t pid),获取进程组 识别码
- pid_t getpgrp(void),获取当前进程组 识别码
- pid_t getpid(void),获取进程 识别码
- pid_t getppid(void),获取父进程 识别码
- int getpriority(int which, int who),获取程序进程执行优先权
- int setpgid(pid_t pid, pid_t pgid),设置进程组 识别码
- int setpgrp(void),设置当前进程组 识别码
- int setpriority(int which, int who, int prio),设置程序进程执行优先权
- int nice(int inc),改变进程优先级
设置和修改进程优先级时,需要权限(有效用户的权限)
4,执行shell命令:
头文件<stdlib.h>
- int system(char *command)
5,等待子进程中断或结束
头文件<sys/types.h>,<sys/wait.h>
- int wait(int *status),等待子进程的状态,一般跟fork配合使用。缺陷:无法指定等待子进程
- 原理:
先调用fork,然后调用wait(父进程);
子进程销毁时,向父进程报告(发送SIGCHILD);
若父进程没有接收这个报告,则子进程可能成为僵尸进程(会占用内存和进程ID)
- 原理:
- pid_t waitpid(pid_t pid, int *status, int options),等待子进程中断或结束,(使用场景:逆向破解注入)
- options:
- WNOHANG:非阻塞
- WUNTRACED:被调试
- WCONTINUED:发生了信号导致进程暂停(SIGSTOP,SIGPAUSE,SIGCONT),由系统来处理
- status:
WIFEXITED
:是否退出,枚举status- WEXITSTATUS
WIFSIGNALED
:是否有信号导致暂停,枚举status- WTERMSIG
WIFSTOPPED
:停止状态,枚举status- WSTOPSIG:信号导致结束
- options:
7,文件和目录函数
磁盘结构(old):柱头,盘片,扇区
1,文件操作函数:
//----------------------------打开----------------------------
/*
- stream:标准输出的文件指针,stdout
- mode:
* r:读,
* w:写,
* +:创建,
* a:追加,
* b:二进制,
* t:文本
*/
FILE *fopen(const char* path, const char *mode);//打开文件, 不能指定权限,默认权限:rw-rw-r-- 或 rw-r--r--
int fclose(FILE *fp);//关闭文件,必须要关闭,文件打开的个数有限,用完之后及时关闭
/*用open函数打开文件的时候得到一个fd,
可以把文件描述符fd转换为文件指针FILE*
*/
FILE *freopen(const char *filename, const char *mode, FILE *stream);//打开文件,输入输出重定向,(场景:后台开发,服务器开发)
FILE *fdopen(int fildes, const char *mode);//将文件描述符转换为文件指针
int fileno(FILE *stream);//返回文件流所使用的文件描述符,文件指针FILE* 转换为文件描述符fd
//----------------------------读取----------------------------
/*
- buffer:缓冲区,单次内存分配不要超过2G, 64为系统不要超过8G,因为分配的是连续空间
- size:每次读取的字节数量
- count:读取次数
- stream:文件指针,指明在哪个文件操作
*/
size_t fread(void *buffer, size_t size, size_t count, FILE *stream);//从文件流读取数据
int fgetc(FILE *stream);//由文件中读取一个字符 c=char
/*fgets()
文件必须以t(文本)模式打开,不能以b二进制模式打开,
若以\0或\n为终止符,或发现了EOF(End Of File),会因为不知道什么时候结束,而出现严重问题;
换行符会被读入到str所指向的内存里面;
文件内部是有一个文件指针(文件共用的文件指针);
会记录上次读/写的位置,读写的位置是分开的,但是,是统一记录的;
每次读写,都可以改变文件中指针的位置
*/
char *fgets(char *str, int n, FILE *stream) //由文件中读取一个字符串 s=string
//----------------------------写入----------------------------
int fputc(int c, FILE *fp);//将指定字符写入文件流
/* fputs()
- 1, 遇到str中的\0字符时,终止写入;
- 2, str必须是标准C语言字符串(以0或者空字符串为结尾的字符串);
*/
int fputs(const char *str, FILE *stream);//将指定字符串写入文件,
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE* stream) //将数据写入文件流
int fflush(FILE *stream) //更新缓冲区,文件大的话比较耗费时间,一般,文件是在磁盘上,打开文件时是在内存中,写的过程中也是在内存中去写,是刚写完之后,并没有直接更新到磁盘上,直接去读取的话,读取的还是原来的文件;需要更新内存缓冲区和硬盘缓冲区,最后写到磁盘上。
/*fseek
- fromwhere:
SEEK_SET:从文件开头指针,计算位置,offset不可为负数
SEEK_CUR:从文件当前指针,计算位置,offset可为负数
SEEK_END:从文件尾部指针,计算位置,offset可为负数
*/
int fseek(FILE *stream, long offset, int fromwhere);//移动文件流的读写位置,在跳转的时候,和文件实际的大小不一定一样,可以超过实际大小
int fsetpos(FILE *stream, const fpos_t *pos);//移动文件流的读写位置,通过修改pos.__pos的值 来进行跳转
long ftell(FILE *stream);//获取文件流的读取位置
int fgetpos(FILE* stream, fpos_t *pos);//获取文件流的读取位置,64位文件读写
int feof(FILE *stream);//检查文件流是否读取到了文件尾,文件未到尾部,则为0,否则为非0,文件读到最后一个字节,并不会触发feof,必须要再读取内容,才会触发feof
void clearerr(FILE *stream);//清除文件流的错误标识
int ferror(FILE *stream);//获取指定文件的错误标识
fread 函数中,读取文件数据,如果每次读取的数据量比较大,count不准确,而其返回值跟count有关,因此返回值也不准确
fgets 文件必须以t(文本)模式打开,不能以b二进制模式打开,若以\0或\n为终止符,或发现了EOF(End Of File),会因为不知道什么时候结束,而出现严重问题
fputs 中 str必须是标准C语言字符串(以0或者空字符串为结尾的字符串)
fseek 在跳转的时候,和文件实际的大小不一定一样,可以超过实际大小
feof 文件读到最后一个字节,并不会触发feof,必须要再读取内容,才会触发feof
2,目录的操作函数
<sys/types.h>
<sys/stat.h>
<unistd.h>
<dirent.h>
//----------------------------创建文件夹----------------------------
int mkdir(const char *pathname, mode_t mode);//创建文件夹
//----------------------------删除文件夹----------------------------
int rmdir(const char *pathname);//删除空文件夹
int remove(const char *pathname);//删除文件或者空的文件夹,也可删除链接
//----------------------------修改目录----------------------------
//可用system函数替代:chown 用户.组 目标文件或文件夹
/* 一般而言,用户和组的代码是一样的,eg:root用户有专属的组,就是root组,system用户对应 system组,个人用户对应个人用户组,
owner和group也是一样的
*/
int chown(const char *path, uid_t owner, gid_t group);//修改文件或者目录的用户或组
int chmod(const char *path, mode_t mode);//修改文件或者目录的权限
/* 删除链接,也可以删除文件
* ln -s 源文件地址 链接文件地址
*/
int unlink(const char *pathname);//删除链接
DIR *opendir(const char *name);//打开目录
struct dirent *readdir(DIR *dir);//读取目录
int closedir(DIR *dirp);//关闭目录
struct dirent
{
long d_ino;//inode number 索引节点号
off_t d_off;//offset to this dirent 在目录文件中的偏移
unsigned short d_reclen;//length of this d_name文件名长
unsigned char d_type;//the type of d_name 文件类型
char d_name [NAME_MAX+1];//file name [null-terminated]文件名,最长255字符
}
/*
- d_type:
DT_BLK:块设备
DT_CHR:字符设备
DT_DIR:目录
DT_LNK:软连接
DT_FIFO:管道
DT_REG:普通文件
DT_SOCK:套接字文件
DT_UNKNOWN:未知
*/
- 删除链接:
void mulu_func()
{
DIR* proot = opendir("~");//获取当前目录
if (proot == NULL)
printf("%s(%d):%s ~ is not exist!\n", __FILE__, __LINE__, __func__);
return;
dirent* pCurrent = NULL;
do {
pCurrent = readdir(proot);
if (pCurrent != NULL) {
if (strcmp(pCurrent->d_name, ".") == 0 || strcmp(pCurrent->d_name, "..") == 0)
continue;
if (pCurrent->d_type & DT_DIR) {
printf("%s(%d):%s unlink = %s\n", __FILE__, __LINE__, __func__, pCurrent->d_name);
}
}
} while (proot != NULL);
}
三,总结
一,ssh公钥问题
1,建立连接-公钥问题
- 实操过程中,visual studio添加连接时,一直出现弹窗,
- 后来发现是sshd_config配置文件里面,配置的
PubkeyAuthentication yes
开启公钥验证,的问题,导致启动ssh服务器时:
sshd: no hostkeys available -- exiting
- 注释掉公钥验证,后,重启服务器。
2,查看ssh服务,重启ssh服务
- 通过
ps -e | grep ssh
查看ssh服务是否开启
发现输入命令之后没有反应
- 安装ssh
接着,我们安装下ssh
sudo apt-get install openssh-server openssh-client
提示已安装
3,尝试启动ssh服务
使用
service ssh start
结果报错个问题,提示sshd: no hostkeys available – exiting.
解决办法
查看资料看到如下操作方式可以解决此问题
ssh-keygen -A
/etc/init.d/ssh start
二,权限问题
Linux系统里面,一切皆文件,对文件的操作,都需要相对应的权限。