0%

在Linux C调用系统命令

在开发中时不时会在代码中调用系统命令,Lnux中调用系统命令的方法比较多。是否需要获取命令的输出也需要考虑。

system

函数定义如下:

1
2
#include <stdlib.h>
int system(const char *command);

在其中会使用fork()调用execl()函数,最后执行函数

返回值

  • 如果参数command为NULL,则在shell可用的情况下为非零值,在没有shell可用的情况下为0。
  • 如果无法fork子进程则返回-1,并且将errno设置为错误值。

sudo的工作原理

此文不介绍sudoers文件的配置方法,只是简单介绍sudo工作流程。

工作流程

工作流程如下图,第一次用markdown写流程图,不太精细。

认证文件简介

认证文件可以理解为cookie,里面记录认证时间,在终端的PID,用户ID,终端的ttydev。
解析代码参考Github
认证文件以二进制保存,每个用户会有一个自己的cookie文件,放在/var/run/sudo/ts文件夹下,文件名为用户名。
文件保存结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
struct timestamp_entry_v1 {
unsigned short version; /* version number */
unsigned short size; /* entry size */
unsigned short type; /* TS_GLOBAL, TS_TTY, TS_PPID */
unsigned short flags; /* TS_DISABLED, TS_ANYUID */
uid_t auth_uid; /* uid to authenticate as */
pid_t sid; /* session ID associated with tty/ppid */
struct timespec ts; /* time stamp (CLOCK_MONOTONIC) */
union {
dev_t ttydev; /* tty device number */
pid_t ppid; /* parent pid */
} u;
};

struct timestamp_entry {
unsigned short version; /* version number */
unsigned short size; /* entry size */
unsigned short type; /* TS_GLOBAL, TS_TTY, TS_PPID */
unsigned short flags; /* TS_DISABLED, TS_ANYUID */
uid_t auth_uid; /* uid to authenticate as */
pid_t sid; /* session ID associated with tty/ppid */
struct timespec start_time; /* session/ppid start time */
struct timespec ts; /* time stamp (CLOCK_MONOTONIC) */
union {
dev_t ttydev; /* tty device number */
pid_t ppid; /* parent pid */
} u;
};

有两种保存方式,通过version进行区分,当version值为1时使用上面的结构体;剩下的使用下面的结构体。
type字段表示这下方的union中使用哪个字段,type会使用以下几个值:

1
2
3
4
#define TS_GLOBAL               0x01    /* not restricted by tty or ppid */
#define TS_TTY 0x02 /* restricted by tty */
#define TS_PPID 0x03 /* restricted by ppid */
#define TS_LOCKEXCL 0x04 /* special lock record */

flags字段自行研究吧,没有太关注。
auth_uid这个是授权用户的UID。
sid这个表示所在终端的PID,但是这个有一个东西需要注意,如果这个账户是通过别的账户su过来的,那么他的值为最上层终端的PID,不太清楚原因。

C/C++ const修饰符

写一篇简单点的,最近老是使用const,但是还经常想不起来咋用,老得查,写一篇记录以下,方便下次找。

作用

const说白了就是希望某些值不被改变,对于一般的变量来说很简单const int i = 1即可,但是对于指针来说就没那么简单了。

指针

const int *i = 0;      // 值不能改,指向可以随便改
int *const j = 0;      // 值可以随便改,指向不能改
const int *const k = 0;

前两个很容易分不清楚,首先第一个是指向常量的指针,说人话就是i指向的值不能通过*i改变,但是可以通过别的方法改变,并且i这个指针可以随便指向其他变量。 第二个是常量指针,即指针不能随便指别人,但是指向的值可以随便改。 第三给就是都不嫩改了。

Linux用户信息详解

Linux用户信息很丰富,存储在了多个文件中,其中包括/etc/passwd,/etc/shadow,/var/log/wtmp等文件中,因为在项目中用到了,所以有些了解。下面对这几个文件进行一个详细的介绍。

账户基本信息

文件各字段含有

/etc/passwd存储了账户的基本信息,默认任何用户都可以读取,也是早期存储账户密码的文件,后因任何用户都可读取,造成安全问题,故后期不再用于存储。
/etc/passwd文件采用:分割,共分为了7个字段,下面是文件中的一行。

1
root:x:0:0:root:/root:/usr/bin/bash
  • 第一列:用户名;
  • 第二列:为用户密码Flag,具体信息没有找到,根据个人推测是为了兼容早期版本,早期此字段用于存储用户密码,后期为了安全将密码放至/etc/shadow文件中。推测为了兼容,字段仍然保留。大部分用户此字段值为x,表示密码保存在/etc/shadow文件中,不是x则就是加密后的密码;
  • 第三列:用户ID,这个好理解,每个用户对应一个唯一的ID,就像身份证号一样;
  • 第四列:组ID,也就是用户所属组的ID了。Linux中每个用户只能在一个组中,但可以有多个附加组;
  • 第五列:用户的一些描述,可能是姓名等,可以为空;
  • 第六列:用户的Home目录,用于设置环境变量$HOME
  • 第七列:用户的默认终端,如果用户被禁止登录了此字段为/usr/bin/nologin,只要登录就会得到This account is currently not available.的提示。

C 语言通过函数读取

在头文件pwd.h中的函数可以获取到/etc/passwd文件中的账户信息。
pwd.h中定义了结构体passwd,结构体内容如下:

1
2
3
4
5
6
7
8
9
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 */
};

结构体中的内容与/etc/passwd文件中的内容对应,就不详细介绍了。
头文件中也定义了一些相关的函数。

1
2
3
4
5
6
struct   passwd    *getpwuid(uid_t);
int getpwnam_r(const char *, struct passwd *, char *, size_t, struct passwd **);
int getpwuid_r(uid_t, struct passwd *, char *, size_t, struct passwd **);
void endpwent(void);
struct passwd *getpwent(void);
void setpwent(void);

下面将介绍几个函数的作用:
getpwuid通过UID查找用户信息,返回passwd的结构体;
getpwnam_rgetpwuid_r不知道是做啥的。
setpwentgetpwentendpwent是一套函数,getpwent是用来获取一行数据,每调用一次读取指针就向下走一行,直至读完返回NULL,setpwent将读取指针返回文件头,endpwent将文件指针返回尾部。

账户密码信息

文件各字段含有

/etc/shadow文件存储了账户的密码相关信息,内容还是很丰富的,与passwd相同,使用:分隔字段,/etc/shadow共分成了9个字段,其中一个为保留字段。/etc/shadow中储存了用户的密码,只用拥有root权限的用户才可以读写。

1
root:$6$K9DgMAqih5hXEJgh$fK3s53mo/YqeZe8v1NKuKHpUPdyuHjFT/d9pM0u2xFxMVAOwjX51cCr5BEqmMaAoISd35PA/cow1q.YoiOLTH0:18193:0:99999:7:::

上面就是/etc/shadow文件的一行,下面我将详细介绍各个字段的含义:

  • 第一列:用户名;
  • 第二列:加密后的密码,这里我的密码是1。貌似没有办法破解,但是可以通过字典的方式获得;
    1
    2
    3
    4
    5
    6
    星号代表帐号被锁定,但是还是可以通过其他方式切换进去的,不同理解到底有啥用
    双叹号表示这个密码已经过期了。
    $6$ 开头的,表明是用SHA-512加密的,
    $1$ 表明是用MD5加密的
    $2$ 是用Blowfish加密的
    $5$ 是用SHA-256加密的。
  • 第三列:账户最后一次修改密码的时间,是自1970年1月1日以来天数;
  • 第四列:自上次密码修改以来多少天内不能修改;
  • 第五列:口令保持有效的最大天数,自上次密码修改以来多少天之后必须修改密码的时间;
  • 第六列:警告时间,密码过期前几天提醒用户修改;
  • 第七列:密码无效期,自过期以来的数天之内,密码仍然有效;
  • 第八列:账户过期时间;
  • 第九列:保留字段。

C 语言通过函数读取

在头文件shadow.h中的函数可以获取到/etc/shadow文件中的账户信息。
shadow.h中定义了结构体spwd,结构体内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct spwd {
char *sp_namp; /* Login name */
char *sp_pwdp; /* Encrypted password */
long sp_lstchg; /* Date of last change
(measured in days since
1970-01-01 00:00:00 +0000 (UTC)) */
long sp_min; /* Min # of days between changes */
long sp_max; /* Max # of days between changes */
long sp_warn; /* # of days before password expires
to warn user to change it */
long sp_inact; /* # of days after password expires
until account is disabled */
long sp_expire; /* Date when account expires
(measured in days since
1970-01-01 00:00:00 +0000 (UTC)) */
unsigned long sp_flag; /* Reserved */
};

结构体中的内容与/etc/shadow文件中的内容对应,就不详细介绍了。
头文件中也定义了一些相关的函数。

1
2
3
4
5
6
7
8
9
struct spwd *getspnam(const char *name);
struct spwd *getspent(void);
void setspent(void);
void endspent(void);
struct spwd *fgetspent(FILE *stream);
struct spwd *sgetspent(const char *s);
int putspent(const struct spwd *p, FILE *stream);
int lckpwdf(void);
int ulckpwdf(void);

getspnam函数返回一个指向结构的指针,该结构包含影子密码数据库中与用户名匹配的记录的细分字段。
getspent函数返回一个指向影子密码数据库中下一个条目的指针。输入流中的位置由setspent初始化。完成阅读后,程序可以调用endsend,以便可以释放资源。
fgetspent函数类似于getspent,但使用提供的流,而不是由setpent隐式打开的流。
sgetspent函数将提供的字符串s解析为结构spwd
putspent函数以阴影密码文件格式将提供的struct spwd * p的内容作为文本行写入流中。值为NULL的字符串条目和值为-1的数字条目被写为空字符串。
lckpwdf函数旨在防止影子密码数据库同时访问。它尝试获取锁,并在成功时返回0,或在失败时返回-1(在15秒内未获得锁)。ulckpwdf函数再次释放锁定。请注意,没有针对直接访问影子密码文件的保护措施。只有使用lckpwdf的程序才会注意到该锁定。

账户登录信息

账户登录信息保存在/var/log/wtmp/var/log/btmp文件中,这个文件是一二进制文件,直接通过cat打印,无过的有用信息,通过命令last可以获取登录,通过lastb可以获取未成功登录的信息。

C 语言通过函数读取

同样C语言也可以获取此信息。在utmp.h文件中,定义了以下结构体吗,和一些常量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
struct utmp {
char ut_user[256]; /* User login name */
char ut_id[14]; /* /etc/inittab id */
char ut_line[64]; /* device name (console, lnxx) */
pid_t ut_pid; /* process id */
short ut_type; /* type of entry */
#if !defined(__64BIT__)
int __time_t_space; /* for 32vs64-bit time_t PPC */
#endif
time_t ut_time; /* time entry was made */
struct exit_status
{
short e_termination; /* Process termination status */
short e_exit; /* Process exit status */
}
ut_exit; /* The exit status of a process
* marked as DEAD_PROCESS.
*/
char ut_host[256]; /* host name */
int __dbl_word_pad; /* for double word alignment */
int __reservedA[2];
int __reservedV[6];
};

#define EMPTY 0
#define RUN_LVL 1
#define BOOT_TIME 2
#define OLD_TIME 3
#define NEW_TIME 4
#define INIT_PROCESS 5 /* Process spawned by "init" */
#define LOGIN_PROCESS 6 /* A "getty" process */

/* waitingforlogin */
#define USER_PROCESS 7 /* A user process */
#define DEAD_PROCESS 8
#define ACCOUNTING 9
#define UTMAXTYPE ACCOUNTING /* Largest legal value */
/* of ut_type */

这个函数我也还有很多参数不了解,后面有机会再补上。

Linux命令-top

简介

top相当于是Linux下的任务管理器,能够实时显示系统中各个进程的资源占用状况,MAN手册中的解释如下图。top是一个动态显示过程,即可以通过用户按键来不断刷新当前状态。
Linux MAN手册对top命令的描述

用法

在终端输入top命令,即出现如下效果:
top命令运行效果
使用{q}退出。

参数解释

top命令执行后主要由四个信息区域,从上至下分别表示了系统运行时间和平均负载、任务和CPU状态、内存使用情况以及进程相信信息。

系统运行时间和平均负载

系统运行时间和平均负载:

  • 第一行
    • 14:03:03:运行时间
    • 5:46:系统开机到现在经过了多少时间
    • 4 users:当前4个用户在线
    • load average: 0.00, 0.01, 0.05:最近1分钟、5分钟和15分钟的CPU负载信息

任务和CPU状态

任务和CPU状态由两行构成,显示了系统任务运行情况和CPU的状态

  • 第一行

    • Tasks: 246 total:总共有246个任务在运行
    • 2 running, 244 sleeping, 0 stopped, 0 zombie:其中2个进程处于运行态,244个进程处于睡眠态(等待状态),0个进程停止,0个进程僵死
  • 第二行展示了两次刷新间各类应用占用CPU时间的百分比

    • 1.7 us:用户态进程的时间
    • 4.4 sy:运行内核进程的时间
    • 0.0 ni:运行良好的用户进程的时间
    • 93.7 id:CPU空闲的时间
    • 0.1 wa:等待I/O完成的时间
    • 0.0 hi:硬件中断花费的时间
    • 0.1 si:软中断花费的时间
    • 0.0 st:虚拟机占用的时间

内存使用情况

内存使用情况由两行构成,描述了物理内存及虚拟内存的使用情况

  • 第一行,描述了物理内存的情况

    • 4026224 total:内存总数
    • 2080500 freel:空闲(可用)内存空间
    • 1021168 usedl:已用内存
    • 924556 buff/cache:用作内核缓存的内存量
  • 第二行,描述了虚拟内存的情况

    • 4063228 total:虚拟内存总数
    • 4063228 free:空闲(可用)虚拟内存空间
    • 0 used:已用虚拟内存
    • 2747656 avail Mem

其中单位换算方式如下:

1
2
3
4
5
6
KiB = kibibyte = 1024 bytes
MiB = mebibyte = 1024 KiB = 1,048,576 bytes
GiB = gibibyte = 1024 MiB = 1,073,741,824 bytes
TiB = tebibyte = 1024 GiB = 1,099,511,627,776 bytes
PiB = pebibyte = 1024 TiB = 1,125,899,906,842,624 bytes
EiB = exbibyte = 1024 PiB = 1,152,921,504,606,846,976 bytes

进程信息

进程信息详细列出了进程的相关情况

  • PID:进程的ID
  • USER:进程所有者
  • PR:进程的优先级别,越小越优先被执行
  • NInice:值
  • VIRT:进程占用的虚拟内存
  • RES:进程占用的物理内存
  • SHR:进程使用的共享内存
  • S:进程的状态。S表示休眠,R表示正在运行,Z表示僵死状态,N表示该进程优先值为负数
  • %CPU:进程占用CPU的使用率
  • %MEM:进程使用的物理内存和总内存的百分比
  • TIME+:该进程启动后占用的总的CPU时间,即占用CPU使用时间的累加值。
  • COMMAND:进程启动命令名称

交互操作指令

  • 键入q:退出
  • 键入{space}:立即刷新
  • 键入s:设置刷新时间间隔
  • 键入c:显示命令完全模式
  • 键入t:显示或隐藏进程和CPU状态信息
  • 键入m:显示或隐藏内存状态信息
  • 键入l:显示或隐藏uptime信息
  • 键入f:增加或减少进程显示标志
  • 键入S:累计模式,会把已完成或退出的子进程占用的CPU时间累计到父进程的MITE+
  • 键入P:按%CPU使用率排行
  • 键入T:按MITE+排行
  • 键入M:按%MEM排行
  • 键入u:指定显示用户进程
  • 键入r:修改进程renice值
  • 键入kkill:进程
  • 键入i:只显示正在运行的进程
  • 键入W:保存对top的设置到文件^/.toprc,下次启动将自动调用toprc文件的设置。
  • 键入h:帮助命令。

上路

初识Arch

寒假闲的作死,非要在装了linux的机子上重装一遍windows,作死搞废GRUB引导,在张大师的博客引导下初识Arch,配置了一天脑子都快炸了,最后在临睡觉前搞好了。
前两天没地方呆,各处实验室用不了,闲的无聊,无奈之下,装了个virtualbox虚拟机,奋斗三天,装崩数次,最终装上,虽说用了不超过一小时,又雪崩了吧,好歹成功一回。
Arch并不太懂,官方介绍献上Arch Linux (简体中文),当然英文好的还是建议看英文的(像我的这种英文渣……),英文文档也献上了Arch Linux

安装

初次安装在虚拟机,主要怕把原来的系统搞废了,资料搞丢了。

镜像源下载

这个也不多说了Arch官网下一个啦,越新越好的了。

镜像烧录

虚拟机安装就不要考虑烧录U盘的问题了(貌似简单了不少)。不过要说一句虚拟机的配置,理论上,Arch Linux能在任何内存空间不小于 256MB 的i686兼容机上运行。最基本的base组中包含的包将占用约 800MB 存储空间。

安装开始

小白兔照着官方文档装第一次八成是失败,而且装的脑袋都要炸了。本人英语水平有限所以只能看中文的了Beginners’ guide (简体中文),英语好的还是建议看英文的Beginners’ guide
下面就说正题了,安装:
把镜像挂载到虚拟机上,点击绿色的向右按钮就算是开机了。开机默认进入Arch的镜像安装界面,Arch安装选择界面选择第一个就好了。

连接到因特网

连接网络,这个就不要管了,虚拟机外面上上网了就不用担心了,所以此步骤跳过。

更新系统时间

敲一遍命令就好了

1
# timedatectl set-ntp true

无返回。
然后就是识别设备,分区,格式化分区

识别设备

首先要确定系统安装的目标设备,下面命令会显示所有连接到系统的设备和分区状况:

1
# lsblk

最少会有两个硬盘,一个是镜像盘,另一个是虚拟机虚拟的硬盘,硬盘大小看你虚拟机虚拟的大小啦。

创建新分区表

虚拟机没有使用,自然没有分区了,所以一定要分区的(x按照虚拟机的情况去改就好了):

1
# parted /dev/sdx

为 BIOS 系统创建 MBR/msdos 分区表(这个不用想了,virtualbox虚拟机只支持msdos分区,所以直接敲命令就好了):

1
(parted) mklabel msdos

用 parted 进行分区

因为是虚拟机,而且linux要求把可执行文件放在home文件下,所以只分了一个区:

1
(parted) mkpart primary ext4 1M 100%

退出parted

1
(parted) quit

格式化文件系统

先查看所有分区:

1
# lsblk /dev/sdx

建议用 ext4 文件系统格式化分区(就一个分区,不要想了sda1就行了):

1
# mkfs.ext4 /dev/sdx1

然后就是挂载

1
# mount /dev/sdx1 /mnt

安装

选择安装镜像
编辑文件 /etc/pacman.d/mirrorlist
官方不知道为何使用中科大的镜像源,163的速度比较快,但是据说费流量,中科大的貌似最近学校限制出口速度,所以选择哪个镜像源自己看自家网络情况定了。

1
2
3
# nano /etc/pacman.d/mirrorlist
---------------------------------------
Server = http://mirrors.ustc.edu.cn/archlinux/$repo/os/$arch

留下上面那行,剩下全部删了就好了。
这个留的是中科大的,163的看见里面有163的就是163的镜像站了。
更改镜像列表后请务必使用命令强制刷新,命令如下:

1
# pacman -Syy

当年因为这个细节崩了数次,说出来都是泪啊

安装基本软件包

没啥好说的了,直接上命令:

1
# pacstrap -i /mnt base base-devel

配置

fstab

用以下命令生成 fstab. 之所以用 UUID 是因为它们能唯一且独立地标识(具体是嘛我也不懂,照着输嘛):

1
# genfstab -U -p /mnt > /mnt/etc/fstab

强烈建议 在执行完以上命令后,后检查一下生成的 /mnt/etc/fstab 文件是否正确。若在运行 genfstab 或是之后发生错误,后续修改请直接手动编辑 fstab 文件。装了几次,没遇上过问题(官网上说的跟我能看懂似的)。

chroot

说实话这个是嘛我也不知道,总之不照着输后头就是错(对了,前面务必别忘了挂载,之前干了不知道多少回这个事情了)

1
# arch-chroot /mnt /bin/bash
Locale

本地化的程序与库若要本地化文本,都依赖 Locale, 后者明确规定地域、货币、时区日期的格式、字符排列方式和其他本地化标准等等。在下面两个文件设置:locale.gen 与 locale.conf.
/etc/locale.gen是一个仅包含注释文档的文本文件。指定您需要的本地化类型,只需移除对应行前面的注释符号(#)即可,建议选择帶UTF-8的项:

1
2
3
4
5
# nano /etc/locale.gen
---------------------------------------
en_US.UTF-8 UTF-8
zh_CN.UTF-8 UTF-8
zh_TW.UTF-8 UTF-8

接着执行locale-gen以生成locale讯息:

1
# locale-gen

创建 locale.conf 并提交您的本地化选项:

1
# echo LANG=en_US.UTF-8 > /etc/locale.conf

时间

可用的时区全集中在 /usr/share/Zone/SubZone 目录里了。选择时区:

1
# tzselect

将 /etc/localtime 软链接到 /usr/share/zoneinfo/Zone/SubZone,国内不知道什么情况,没有北京,只有上海,所以软链接到上海就好:

1
# ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

建议设置时间标准为 UTC,并调整 时间漂移:

1
# hwclock --systohc --utc

设置 Root 密码

用 passwd 设置一个 root 密码,root密码就是最高用户权限啦:

1
# passwd

BIOS/MBR

下面介绍在 MBR 系统上使用 Grub。安装 grub 和 os-prober 包,并执行 grub-install. 须根据实际分区自行调整 /dev/sda, 切勿在块设备后附加数字,比如 /dev/sda1 就不对。

1
2
# pacman -S grub os-prober
# grub-install /dev/sda

下面命令会自动生成配置文件

1
# grub-mkconfig -o /boot/grub/grub.cfg

详情参考GRUB (简体中文)官方文档 。

配置网络

该过程与#建立网络连接基本一致,只不过该配置在新系统每次开机时都会自动生效。

主机名

设置个您喜欢的主机名,例如:

1
# echo myhostname > /etc/hostname

myhostname替换为你喜欢的主机名,不是用户名。

有线网络

如果您只用单一且固定的有线网络连接,启动 dhcpcd 服务,interface 是您的网络接口名:

1
# systemctl enable dhcpcd@interface.service

卸载分区并重启系统

设置 root 密码(前面干过一遍了,不知为何官方文档又来一遍,反正我是照做了,差不了几分钟,最后崩了重来更费事):

1
# passwd

离开 chroot 环境:

1
# exit

重启计算机:

1
# reboot

虚拟机里面重启电脑默认进入虚拟光盘,所以啦,建议“关机”后“退出”光盘,再开机;不过鄙人一般先重启摁F12,选硬盘启动就好了。

安装之后

安装之后不要以为就万事大吉了,现在进入系统还是黑底白字的命令行,就像这样,只不过还没有登录安装好后的Arch
而且只有一个root用户,接下来看这篇文章General recommendations (简体中文)
现在心烦意乱的,剩下的以后再写吧。